Browse Source

增加邮件中间状态记录

chenkq 2 weeks ago
parent
commit
e7c0751918

+ 14 - 2
storlead-mail/README.md

@@ -65,8 +65,20 @@ API:
 
 ## 库表变更
 
-执行:`storlead-mail-common/src/main/resources/sql/mail_scene_and_mkt.sql`
+执行:`storlead-mail-common/src/main/resources/sql/mail_scene_and_mkt.sql`  
+状态表:`storlead-mail-common/src/main/resources/sql/mkt_mail_status.sql`
 
 - `smtp_pop_settings` 增加 `scene` / `account_scope` / `org_id` 等
-- 新建 `mkt_mail_message`、`mkt_mail_attachment`
+- 新建 `mkt_mail_message`、`mkt_mail_attachment`、`mkt_mail_status`
 - `mail_temp_attachment` 增加 `scene`(默认 `CRM`)
+
+### mkt_mail_status 埋点
+
+| 事件 | 更新字段 |
+|------|----------|
+| 草稿/发送落库 | `ensureStatus` 初始化行 |
+| SMTP 成功/失败 | `send_status` = 1 / 2 |
+| 像素打开 | `opened` / `first_open_time`,并同步 `is_read` / `first_read_time` |
+| 收信绑定回复 | 原信 `replied` / `first_reply_time` |
+
+任意变更刷新 `status_updated_at`,供第三方按该字段增量拉取。

+ 26 - 0
storlead-mail/storlead-mail-common/src/main/resources/sql/mkt_mail_recreate.sql

@@ -4,6 +4,7 @@
 -- ============================================================
 
 DROP TABLE IF EXISTS mkt_mail_attachment;
+DROP TABLE IF EXISTS mkt_mail_status;
 DROP TABLE IF EXISTS mkt_mail_message;
 
 -- 营销邮件投递/收信表(与 CRM emails 分表)
@@ -80,3 +81,28 @@ CREATE TABLE mkt_mail_attachment (
     KEY idx_mkt_att_mail (mail_id),
     KEY idx_mkt_att_account (account_id)
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='营销邮件正式附件';
+
+-- 营销邮件状态(供第三方增量同步)
+CREATE TABLE mkt_mail_status (
+    id                bigint       NOT NULL AUTO_INCREMENT COMMENT '主键',
+    mail_id           bigint       NOT NULL COMMENT '邮件ID,对应 mkt_mail_message.id',
+    send_status       tinyint      NOT NULL DEFAULT 0 COMMENT '发送状态:-2草稿 -1定时 0发送中 1成功 2失败',
+    opened            tinyint      NOT NULL DEFAULT 0 COMMENT '是否已打开(像素):0否 1是',
+    first_open_time   datetime     NULL COMMENT '首次打开时间',
+    is_read           tinyint      NOT NULL DEFAULT 0 COMMENT '是否已读:0否 1是',
+    first_read_time   datetime     NULL COMMENT '首次已读时间',
+    replied           tinyint      NOT NULL DEFAULT 0 COMMENT '是否已回复:0否 1是',
+    first_reply_time  datetime     NULL COMMENT '首次回复时间',
+    status_updated_at datetime     NOT NULL COMMENT '状态最后更新时间(第三方增量游标)',
+    owner_by          bigint       NULL COMMENT '数据归属人',
+    create_by         bigint       NULL COMMENT '创建人',
+    update_by         bigint       NULL COMMENT '最后更新人',
+    create_time       datetime     NULL COMMENT '创建时间',
+    update_time       datetime     NULL COMMENT '更新时间',
+    is_delete         tinyint      NOT NULL DEFAULT 0 COMMENT '逻辑删除:0未删除;1已删除',
+    enabled           tinyint      NOT NULL DEFAULT 1 COMMENT '是否启用:1启用;0禁用',
+    sort              int          NULL COMMENT '排序号',
+    PRIMARY KEY (id),
+    UNIQUE KEY uk_mkt_mail_status_mail (mail_id),
+    KEY idx_mkt_status_updated (status_updated_at, id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='营销邮件状态(供第三方增量同步)';

+ 28 - 0
storlead-mail/storlead-mail-common/src/main/resources/sql/mkt_mail_status.sql

@@ -0,0 +1,28 @@
+-- 营销邮件对外状态表(供第三方增量同步)
+CREATE TABLE IF NOT EXISTS mkt_mail_status (
+    id                bigint       NOT NULL AUTO_INCREMENT COMMENT '主键',
+    mail_id           bigint       NOT NULL COMMENT '邮件ID,对应 mkt_mail_message.id',
+
+    send_status       tinyint      NOT NULL DEFAULT 0 COMMENT '发送状态:-2草稿 -1定时 0发送中 1成功 2失败',
+    opened            tinyint      NOT NULL DEFAULT 0 COMMENT '是否已打开(像素):0否 1是',
+    first_open_time   datetime     NULL COMMENT '首次打开时间',
+    is_read           tinyint      NOT NULL DEFAULT 0 COMMENT '是否已读:0否 1是',
+    first_read_time   datetime     NULL COMMENT '首次已读时间',
+    replied           tinyint      NOT NULL DEFAULT 0 COMMENT '是否已回复:0否 1是',
+    first_reply_time  datetime     NULL COMMENT '首次回复时间',
+
+    status_updated_at datetime     NOT NULL COMMENT '状态最后更新时间(第三方增量游标)',
+
+    owner_by          bigint       NULL COMMENT '数据归属人',
+    create_by         bigint       NULL COMMENT '创建人',
+    update_by         bigint       NULL COMMENT '最后更新人',
+    create_time       datetime     NULL COMMENT '创建时间',
+    update_time       datetime     NULL COMMENT '更新时间',
+    is_delete         tinyint      NOT NULL DEFAULT 0 COMMENT '逻辑删除:0未删除;1已删除',
+    enabled           tinyint      NOT NULL DEFAULT 1 COMMENT '是否启用:1启用;0禁用',
+    sort              int          NULL COMMENT '排序号',
+
+    PRIMARY KEY (id),
+    UNIQUE KEY uk_mkt_mail_status_mail (mail_id),
+    KEY idx_mkt_status_updated (status_updated_at, id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='营销邮件状态(供第三方增量同步)';

+ 66 - 0
storlead-mail/storlead-mail-mkt/src/main/java/com/storlead/sales/mail/mkt/entity/MktMailStatusEntity.java

@@ -0,0 +1,66 @@
+package com.storlead.sales.mail.mkt.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.storlead.framework.mybatis.entity.SysBaseField;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.experimental.Accessors;
+
+import java.util.Date;
+
+/**
+ * 营销邮件状态快照(供第三方增量同步)。
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@Accessors(chain = true)
+@TableName("mkt_mail_status")
+@ApiModel(value = "MktMailStatusEntity", description = "营销邮件状态表")
+public class MktMailStatusEntity extends SysBaseField {
+
+    private static final long serialVersionUID = 1L;
+
+    @TableId(value = "id", type = IdType.AUTO)
+    private Long id;
+
+    @ApiModelProperty("邮件ID,对应 mkt_mail_message.id")
+    @TableField("mail_id")
+    private Long mailId;
+
+    @ApiModelProperty("发送状态:-2草稿 -1定时 0发送中 1成功 2失败")
+    @TableField("send_status")
+    private Integer sendStatus;
+
+    @ApiModelProperty("是否已打开(像素)")
+    @TableField("opened")
+    private Integer opened;
+
+    @ApiModelProperty("首次打开时间")
+    @TableField("first_open_time")
+    private Date firstOpenTime;
+
+    @ApiModelProperty("是否已读")
+    @TableField("is_read")
+    private Integer isRead;
+
+    @ApiModelProperty("首次已读时间")
+    @TableField("first_read_time")
+    private Date firstReadTime;
+
+    @ApiModelProperty("是否已回复")
+    @TableField("replied")
+    private Integer replied;
+
+    @ApiModelProperty("首次回复时间")
+    @TableField("first_reply_time")
+    private Date firstReplyTime;
+
+    @ApiModelProperty("状态最后更新时间(第三方增量游标)")
+    @TableField("status_updated_at")
+    private Date statusUpdatedAt;
+}

+ 10 - 0
storlead-mail/storlead-mail-mkt/src/main/java/com/storlead/sales/mail/mkt/mapper/MktMailStatusMapper.java

@@ -0,0 +1,10 @@
+package com.storlead.sales.mail.mkt.mapper;
+
+import com.storlead.framework.mybatis.mapper.MyBaseMapper;
+import com.storlead.sales.mail.mkt.entity.MktMailStatusEntity;
+
+/**
+ * 营销邮件状态 Mapper。
+ */
+public interface MktMailStatusMapper extends MyBaseMapper<MktMailStatusEntity> {
+}

+ 9 - 0
storlead-mail/storlead-mail-mkt/src/main/java/com/storlead/sales/mail/mkt/receive/MktMailReceivePersist.java

@@ -17,6 +17,7 @@ import com.storlead.sales.mail.mkt.entity.MktMailMessageEntity;
 import com.storlead.sales.mail.mkt.enums.MktMailFolderEnum;
 import com.storlead.sales.mail.mkt.service.MktMailAttachmentService;
 import com.storlead.sales.mail.mkt.service.MktMailMessageService;
+import com.storlead.sales.mail.mkt.service.MktMailStatusService;
 import com.storlead.sales.mail.mkt.support.MktMailHeaders;
 import lombok.extern.log4j.Log4j2;
 import org.apache.commons.io.FilenameUtils;
@@ -52,6 +53,9 @@ public class MktMailReceivePersist implements MailReceivePersistSpi {
     @Resource
     private MktMailAttachmentService mktMailAttachmentService;
 
+    @Resource
+    private MktMailStatusService mktMailStatusService;
+
     @Resource
     private MailFileProperties mailFileProperties;
 
@@ -302,6 +306,11 @@ public class MktMailReceivePersist implements MailReceivePersistSpi {
         if (!StringUtils.hasText(entity.getBizBatchNo()) && StringUtils.hasText(parent.getBizBatchNo())) {
             entity.setBizBatchNo(parent.getBizBatchNo());
         }
+        try {
+            mktMailStatusService.markReplied(parent.getId());
+        } catch (Exception e) {
+            log.error("mkt_mail_status markReplied error, parentMailId={}", parent.getId(), e);
+        }
     }
 
     private MktMailMessageEntity findByMessageId(Long accountId, String messageId) {

+ 31 - 0
storlead-mail/storlead-mail-mkt/src/main/java/com/storlead/sales/mail/mkt/service/MktMailStatusService.java

@@ -0,0 +1,31 @@
+package com.storlead.sales.mail.mkt.service;
+
+import com.storlead.framework.mybatis.service.MyBaseService;
+import com.storlead.sales.mail.mkt.entity.MktMailMessageEntity;
+import com.storlead.sales.mail.mkt.entity.MktMailStatusEntity;
+
+/**
+ * 营销邮件状态(mkt_mail_status)埋点写入。
+ */
+public interface MktMailStatusService extends MyBaseService<MktMailStatusEntity> {
+
+    /**
+     * 确保状态行存在;发信/草稿落库后调用。
+     */
+    void ensureStatus(MktMailMessageEntity mail);
+
+    /**
+     * 发送成功/失败/发送中等:更新 send_status。
+     */
+    void markSendStatus(Long mailId, Integer sendStatus);
+
+    /**
+     * 像素打开:更新 opened / first_open_time,并同步 is_read / first_read_time。
+     */
+    void markOpened(Long mailId);
+
+    /**
+     * 收到针对原信的回复:更新 replied / first_reply_time(仅首次)。
+     */
+    void markReplied(Long mailId);
+}

+ 17 - 0
storlead-mail/storlead-mail-mkt/src/main/java/com/storlead/sales/mail/mkt/service/impl/MktMailMessageServiceImpl.java

@@ -27,6 +27,7 @@ import com.storlead.sales.mail.mkt.pojo.MktOpenStatsVO;
 import com.storlead.sales.mail.mkt.pojo.MktSendMailDTO;
 import com.storlead.sales.mail.mkt.service.MktMailAttachmentService;
 import com.storlead.sales.mail.mkt.service.MktMailMessageService;
+import com.storlead.sales.mail.mkt.service.MktMailStatusService;
 import com.storlead.sales.mail.mkt.support.MktMailHeaders;
 import com.storlead.sales.mail.mkt.util.MktMailSender;
 import lombok.extern.log4j.Log4j2;
@@ -88,6 +89,9 @@ public class MktMailMessageServiceImpl extends MyBaseServiceImpl<MktMailMessageM
     @Resource
     private MktMailAttachmentService mktMailAttachmentService;
 
+    @Resource
+    private MktMailStatusService mktMailStatusService;
+
     @Resource
     private MailFileProperties mailFileProperties;
 
@@ -122,6 +126,7 @@ public class MktMailMessageServiceImpl extends MyBaseServiceImpl<MktMailMessageM
         entity.setDelaySendTime(dto.getDelaySendTime());
         ensureTrackToken(entity, dto.getIsTrack());
         this.saveOrUpdate(entity);
+        mktMailStatusService.ensureStatus(entity);
         bindTempAttachments(entity.getId(), dto.getFileIds(), operatorId);
         copyFormalToTemp(entity.getId(), dto.getOldFileIds(), operatorId);
         return entity;
@@ -153,6 +158,7 @@ public class MktMailMessageServiceImpl extends MyBaseServiceImpl<MktMailMessageM
             entity.setStatus(STATUS_DELAY);
             entity.setDelaySendTime(dto.getDelaySendTime());
             this.saveOrUpdate(entity);
+            mktMailStatusService.ensureStatus(entity);
             bindTempAttachments(entity.getId(), dto.getFileIds(), operatorId);
             copyFormalToTemp(entity.getId(), dto.getOldFileIds(), operatorId);
             return entity;
@@ -161,6 +167,7 @@ public class MktMailMessageServiceImpl extends MyBaseServiceImpl<MktMailMessageM
         entity.setStatus(STATUS_SENDING);
         entity.setDelaySendTime(null);
         this.saveOrUpdate(entity);
+        mktMailStatusService.ensureStatus(entity);
         bindTempAttachments(entity.getId(), dto.getFileIds(), operatorId);
         // 正式附件拷入临时表,MIME 与成功后转正式统一按 mailId 处理,避免重复挂载
         copyFormalToTemp(entity.getId(), dto.getOldFileIds(), operatorId);
@@ -201,6 +208,9 @@ public class MktMailMessageServiceImpl extends MyBaseServiceImpl<MktMailMessageM
         claiming.in(MktMailMessageEntity::getId, ids);
         claiming.eq(MktMailMessageEntity::getStatus, STATUS_DELAY);
         this.update(claiming);
+        for (Long id : ids) {
+            mktMailStatusService.markSendStatus(id, STATUS_SENDING);
+        }
 
         int success = 0;
         for (MktMailMessageEntity item : dueList) {
@@ -222,6 +232,7 @@ public class MktMailMessageServiceImpl extends MyBaseServiceImpl<MktMailMessageM
                 fail.set(MktMailMessageEntity::getStatus, STATUS_FAIL);
                 fail.set(MktMailMessageEntity::getFailReason, ex.getMessage());
                 this.update(fail);
+                mktMailStatusService.markSendStatus(item.getId(), STATUS_FAIL);
             }
         }
         return success;
@@ -314,6 +325,11 @@ public class MktMailMessageServiceImpl extends MyBaseServiceImpl<MktMailMessageM
         }
         MktMailMessageEntity latest = this.getById(before.getId());
         fireOpenCallbacks(latest == null ? before : latest, firstOpen);
+        try {
+            mktMailStatusService.markOpened(before.getId());
+        } catch (Exception e) {
+            log.error("mkt_mail_status markOpened error, mailId={}", before.getId(), e);
+        }
         return true;
     }
 
@@ -421,6 +437,7 @@ public class MktMailMessageServiceImpl extends MyBaseServiceImpl<MktMailMessageM
         fail.set(MktMailMessageEntity::getStatus, STATUS_FAIL);
         fail.set(MktMailMessageEntity::getFailReason, reason);
         this.update(fail);
+        mktMailStatusService.markSendStatus(mailId, STATUS_FAIL);
     }
 
     private MimeMessage buildMimeMessage(Session session,

+ 136 - 0
storlead-mail/storlead-mail-mkt/src/main/java/com/storlead/sales/mail/mkt/service/impl/MktMailStatusServiceImpl.java

@@ -0,0 +1,136 @@
+package com.storlead.sales.mail.mkt.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
+import com.storlead.framework.common.constant.CommonConstant;
+import com.storlead.framework.mybatis.service.impl.MyBaseServiceImpl;
+import com.storlead.sales.mail.mkt.entity.MktMailMessageEntity;
+import com.storlead.sales.mail.mkt.entity.MktMailStatusEntity;
+import com.storlead.sales.mail.mkt.mapper.MktMailStatusMapper;
+import com.storlead.sales.mail.mkt.service.MktMailStatusService;
+import lombok.extern.log4j.Log4j2;
+import org.springframework.dao.DuplicateKeyException;
+import org.springframework.stereotype.Service;
+
+import java.util.Date;
+
+/**
+ * mkt_mail_status 埋点:发送态 / 打开 / 回复。
+ */
+@Log4j2
+@Service
+public class MktMailStatusServiceImpl extends MyBaseServiceImpl<MktMailStatusMapper, MktMailStatusEntity>
+        implements MktMailStatusService {
+
+    @Override
+    public void ensureStatus(MktMailMessageEntity mail) {
+        if (mail == null || mail.getId() == null) {
+            return;
+        }
+        MktMailStatusEntity existing = getByMailId(mail.getId());
+        if (existing != null) {
+            if (mail.getStatus() != null && !mail.getStatus().equals(existing.getSendStatus())) {
+                markSendStatus(mail.getId(), mail.getStatus());
+            }
+            return;
+        }
+        Date now = new Date();
+        MktMailStatusEntity row = new MktMailStatusEntity();
+        row.setMailId(mail.getId());
+        row.setSendStatus(mail.getStatus() == null ? 0 : mail.getStatus());
+        row.setOpened(0);
+        row.setIsRead(0);
+        row.setReplied(0);
+        row.setStatusUpdatedAt(now);
+        row.setOwnerBy(mail.getOwnerBy());
+        row.setCreateBy(mail.getCreateBy() == null ? mail.getOwnerBy() : mail.getCreateBy());
+        row.setIsDelete(CommonConstant.DEL_FLAG_0);
+        row.setEnabled(Boolean.TRUE);
+        try {
+            this.save(row);
+        } catch (DuplicateKeyException e) {
+            log.debug("mkt_mail_status already exists, mailId={}", mail.getId());
+        } catch (Exception e) {
+            log.error("ensureStatus error, mailId={}", mail.getId(), e);
+        }
+    }
+
+    @Override
+    public void markSendStatus(Long mailId, Integer sendStatus) {
+        if (mailId == null || sendStatus == null) {
+            return;
+        }
+        ensureMinimalRow(mailId);
+        Date now = new Date();
+        LambdaUpdateWrapper<MktMailStatusEntity> update = new LambdaUpdateWrapper<>();
+        update.eq(MktMailStatusEntity::getMailId, mailId);
+        update.eq(MktMailStatusEntity::getIsDelete, CommonConstant.DEL_FLAG_0);
+        update.set(MktMailStatusEntity::getSendStatus, sendStatus);
+        update.set(MktMailStatusEntity::getStatusUpdatedAt, now);
+        this.update(update);
+    }
+
+    @Override
+    public void markOpened(Long mailId) {
+        if (mailId == null) {
+            return;
+        }
+        ensureMinimalRow(mailId);
+        Date now = new Date();
+        LambdaUpdateWrapper<MktMailStatusEntity> update = new LambdaUpdateWrapper<>();
+        update.eq(MktMailStatusEntity::getMailId, mailId);
+        update.eq(MktMailStatusEntity::getIsDelete, CommonConstant.DEL_FLAG_0);
+        update.set(MktMailStatusEntity::getOpened, 1);
+        update.set(MktMailStatusEntity::getIsRead, 1);
+        update.setSql("first_open_time = IFNULL(first_open_time, NOW())");
+        update.setSql("first_read_time = IFNULL(first_read_time, NOW())");
+        update.set(MktMailStatusEntity::getStatusUpdatedAt, now);
+        this.update(update);
+    }
+
+    @Override
+    public void markReplied(Long mailId) {
+        if (mailId == null) {
+            return;
+        }
+        ensureMinimalRow(mailId);
+        Date now = new Date();
+        LambdaUpdateWrapper<MktMailStatusEntity> update = new LambdaUpdateWrapper<>();
+        update.eq(MktMailStatusEntity::getMailId, mailId);
+        update.eq(MktMailStatusEntity::getIsDelete, CommonConstant.DEL_FLAG_0);
+        update.set(MktMailStatusEntity::getReplied, 1);
+        update.setSql("first_reply_time = IFNULL(first_reply_time, NOW())");
+        update.set(MktMailStatusEntity::getStatusUpdatedAt, now);
+        this.update(update);
+    }
+
+    private MktMailStatusEntity getByMailId(Long mailId) {
+        return this.getOne(new LambdaQueryWrapper<MktMailStatusEntity>()
+                .eq(MktMailStatusEntity::getMailId, mailId)
+                .eq(MktMailStatusEntity::getIsDelete, CommonConstant.DEL_FLAG_0)
+                .last("limit 1"));
+    }
+
+    private void ensureMinimalRow(Long mailId) {
+        if (getByMailId(mailId) != null) {
+            return;
+        }
+        Date now = new Date();
+        MktMailStatusEntity row = new MktMailStatusEntity();
+        row.setMailId(mailId);
+        row.setSendStatus(0);
+        row.setOpened(0);
+        row.setIsRead(0);
+        row.setReplied(0);
+        row.setStatusUpdatedAt(now);
+        row.setIsDelete(CommonConstant.DEL_FLAG_0);
+        row.setEnabled(Boolean.TRUE);
+        try {
+            this.save(row);
+        } catch (DuplicateKeyException ignore) {
+            // concurrent insert
+        } catch (Exception e) {
+            log.error("ensureMinimalRow error, mailId={}", mailId, e);
+        }
+    }
+}

+ 14 - 0
storlead-mail/storlead-mail-mkt/src/main/java/com/storlead/sales/mail/mkt/util/MktMailSender.java

@@ -12,6 +12,7 @@ import com.storlead.sales.mail.mkt.entity.MktMailAttachmentEntity;
 import com.storlead.sales.mail.mkt.entity.MktMailMessageEntity;
 import com.storlead.sales.mail.mkt.service.MktMailAttachmentService;
 import com.storlead.sales.mail.mkt.service.MktMailMessageService;
+import com.storlead.sales.mail.mkt.service.MktMailStatusService;
 import lombok.extern.log4j.Log4j2;
 import org.springframework.beans.factory.ObjectProvider;
 import org.springframework.context.annotation.Lazy;
@@ -56,6 +57,9 @@ public class MktMailSender {
     @Resource
     private MailTempAttachmentService tempAttachmentService;
 
+    @Resource
+    private MktMailStatusService mktMailStatusService;
+
     private final List<MktMailSendCallback> sendCallbacks;
 
     public MktMailSender(ObjectProvider<MktMailSendCallback> callbackProvider) {
@@ -187,6 +191,11 @@ public class MktMailSender {
                 entity.setMessageId(messageId);
             }
             promoteTempAttachments(entity);
+            try {
+                mktMailStatusService.markSendStatus(entity.getId(), STATUS_SUCCESS);
+            } catch (Exception ex) {
+                log.error("mkt_mail_status markSendStatus success error, mailId={}", entity.getId(), ex);
+            }
             return true;
         } catch (Exception e) {
             log.error("mkt markDelivered error, mailId={}, partial={}", entity.getId(), partial, e);
@@ -211,6 +220,11 @@ public class MktMailSender {
             }
             entity.setStatus(STATUS_FAIL);
             entity.setFailReason(failReason);
+            try {
+                mktMailStatusService.markSendStatus(entity.getId(), STATUS_FAIL);
+            } catch (Exception ex) {
+                log.error("mkt_mail_status markSendStatus fail error, mailId={}", entity.getId(), ex);
+            }
             fireFailure(entity, failReason);
         } catch (Exception e) {
             log.error("mkt onEmailSentFailure error, mailId={}", entity.getId(), e);