Skip to content

邮件配置 ​

这篇解决什么 ​

业务模块要发邮件时,不要自己拼 SMTP。先在后台配好账号和模板,再调 MailSendApi。读完能改账号字段、模板占位符、发送链路和日志状态。

默认端口 48080,管理端前缀 /admin-api。菜单在系统管理 → 消息中心 → 邮箱管理。真正发信走 SMTP,落地用 Hutool MailUtil。

示意图:后台维护账号和模板;业务只调发送 API。真正连 SMTP 在异步线程里完成。

名称说明仓库路径
邮箱账号SMTP 主机、端口、SSL / STARTTLS、授权密码ruoyi-office/yudao-module-system/.../dal/dataobject/mail/MailAccountDO.java
邮件模板code、标题、HTML 正文、绑定账号.../dal/dataobject/mail/MailTemplateDO.java
邮件日志每次发送的收件人、渲染结果、状态.../dal/dataobject/mail/MailLogDO.java
发送 API给管理员 / 会员各一条ruoyi-office/yudao-module-system-api/.../api/mail/MailSendApi.java
后台页账号、模板、记录三张列表ruoyi-office-vben/apps/web-antd/src/views/system/mail/
前端 API/system/mail-account、mail-template、mail-logruoyi-office-vben/apps/web-antd/src/api/system/mail/

官方菜单 id:2130 邮箱管理,2131 邮箱账号,2136 邮件模版,2141 邮件记录。路由是 /system/messages/mail/mail-account、mail-template、mail-log。

邮箱账号

本系统截图:系统管理 → 消息中心 → 邮箱管理 → 邮箱账号。

邮件模板

本系统截图:系统管理 → 消息中心 → 邮箱管理 → 邮件模板。

邮件记录

本系统截图:邮件记录。筛选是发送时间、用户、状态、账号、模板编号。

和 OA 办公邮箱不是同一套

OA 里的收件箱绑定 IMAP/SMTP,表是 oa_mail_account。这里只负责系统代发(验证码、告警、通知)。不要把办公邮箱账号填进 system_mail_account 当个人邮箱用。

表结构 ​

三张表都带 @TenantIgnore,库里也没有 tenant_id。账号、模板、日志全实例共用。

按本仓库生成的 ER:模板必须挂一个账号;日志同时冗余账号和模板快照。

名称说明仓库路径
system_mail_account@TableName("system_mail_account")。密码明文存.../dal/dataobject/mail/MailAccountDO.java
system_mail_template@TableName("system_mail_template")。code 全局唯一.../dal/dataobject/mail/MailTemplateDO.java
system_mail_log@TableName("system_mail_log")。to_mails / cc_mails / bcc_mails 是地址列表.../dal/dataobject/mail/MailLogDO.java

MailTemplateDO.params 不是手填。保存时用正则 \{(.*?)\} 扫标题和正文,去重后写入。发送时按这个列表校验,缺 key 抛 MAIL_SEND_TEMPLATE_PARAM_MISS。

密码按明文落库

password 没有加密字段。库备份、导出、越权查账号接口都能看到 SMTP 授权码。账号页权限只给运维,不要把授权码写进文档或日志。

实现原理 ​

其它模块只依赖 yudao-module-system-api,注入 MailSendApi。单体里 MailSendApiImpl 就是本地 Bean;微服务里走 Feign,服务名 system-server。

发送先同步落日志,再异步连 SMTP。接口马上返回日志 id,不堵业务线程。

示意图:MailProducer 发的是 Spring 事件,不是 RocketMQ。MailSendConsumer 上的 @Async 才把 SMTP 挪到别的线程。

名称说明仓库路径
管理端测试POST /system/mail-template/send-mail.../controller/admin/mail/MailTemplateController.java
RPC/rpc-api/system/mail/send/send-single-admin、send-single-member.../api/mail/MailSendApi.java
同步编排校验模板 / 账号 / 参数,拼收件人.../service/mail/MailSendServiceImpl.java
异步投递applicationContext.publishEvent.../mq/producer/mail/MailProducer.java
真正发信@EventListener + @Async.../mq/consumer/mail/MailSendConsumer.java
SMTPorg.dromara.hutool.extra.mail.MailUtil,isHtml = trueMailSendServiceImpl#doSendMail
账号缓存Redis mail_account:{id}RedisKeyConstants.MAIL_ACCOUNT
模板缓存Redis mail_template:{code}RedisKeyConstants.MAIL_TEMPLATE
统一消息邮件渠道场景绑定后仍进 MailSendService.../service/msg/channel/MailChannelAdapter.java

发件人显示名来自模板 nickname:有值时 from 写成 昵称 <mail@example.com>,没有就只用账号邮箱。

模板关掉也不会报错

status 不是启用时,只写日志,状态 IGNORE = 30,不发事件。产品改文案或临时停发,不用改调用方、不用重启。

邮箱账号 ​

菜单:系统管理 → 消息中心 → 邮箱管理 → 邮箱账号。接口 @RequestMapping("/system/mail-account")。

新增邮箱账号

本系统截图:新增邮箱账号。SSL 默认开,STARTTLS 默认关。

名称说明仓库路径
列表 / 新增GET /page,POST /createMailAccountController
精简列表模板下拉用 GET /simple-list同上
权限system:mail-account:create 等同上
请求体mail / username / password / host / port / sslEnable / starttlsEnable.../vo/account/MailAccountSaveReqVO.java

mail 是对外显示的发件地址,username 是 SMTP 登录名,多数邮箱两者相同。password 填服务商 SMTP 授权码,不是网页登录密码。

常见组合:465 + SSL;587 + STARTTLS。两个都开或端口和开关对不上,Hutool 会连不上。

有模板挂着的账号删不掉,错误码 MAIL_ACCOUNT_RELATE_TEMPLATE_EXISTS。

本地先验证 SMTP 是否通,可以用 Hutool 直发一封(不要把授权码提交进仓库):

java
MailAccount account = new MailAccount()
        .setFrom("noreply@example.com")
        .setHost("smtp.example.com").setPort(465).setSslEnable(true)
        .setAuth(true).setUser("noreply@example.com").setPass("your-auth-code");
MailUtil.send(account, "you@example.com", "连通性测试", "正文", false);

类在 org.dromara.hutool.extra.mail。后台保存成功不代表 SMTP 已通,要用模板「测试」或上面这段确认。

邮件模板 ​

菜单:邮件模板。接口 @RequestMapping("/system/mail-template")。

新增邮件模板

本系统截图:新增邮件模板。正文是富文本,发送时按 HTML 发出。

名称说明仓库路径
邮箱账号下拉来自账号精简列表accountId → MailAccountDO.id
模板编码API 只认这个 code,不是主键MailTemplateDO.code
发送人名称可选,写入 SMTP From 显示名nickname
模板标题支持 {name} 这类占位符title
模板内容富文本;占位符同样是 {var}content
开启状态CommonStatusEnum,关则只记日志status

code 必须唯一,重复抛 MAIL_TEMPLATE_CODE_EXISTS。改 code 或内容后会清空 mail_template 整组缓存。

占位符用 Hutool StrUtil.format。标题和正文里出现的 {key} 都会进 params。调用方 templateParams 必须带齐这些 key,值不能是 null。

渲染后还会做一次 HTML 反转义,并把最外层 <pre> 换成 <div>,避免富文本编辑器带出来的标签把邮件撑坏。

为什么要模板

标题、正文、甚至发件账号经常被产品改。改模板行就行,调用方继续传同一个 templateCode。

邮件日志 ​

有模板后,行上点「测试」。权限 system:mail-template:send-mail。弹窗在 template/modules/send-form.vue。

名称说明仓库路径
收件邮箱必填,标签输入,Enter 添加MailTemplateSendReqVO.toMails
抄送 / 密送可选ccMails / bccMails
模板参数按 params 补齐,不能为 nulltemplateParams
接口POST /system/mail-template/send-mailMailTemplateController#sendMail

当前登录人会作为 userId 再走管理员发送。测完先看自己的收件箱,再回邮件记录对状态。收件箱不是本系统页面。

名称说明仓库路径
列表GET /system/mail-log/page.../controller/admin/mail/MailLogController.java
详情GET /get?id=同上
INIT0,已入队,还没走到 SMTPMailSendStatusEnum
SUCCESS10,写下 SMTP messageId同上
FAILURE20,sendException 是根因短句同上
IGNORE30,模板关闭,故意不发同上

日志里的 templateTitle 存的是模板原文,templateContent 是已经替换参数后的 HTML。fromMail、templateCode 是发送当时的冗余,账号后来改了也能回看。

system_mail_log.template_params 列只有 varchar(255)。参数很多或很长时可能被库截断,排查以当时能否发出为准。

邮件发送 ​

MailSendApi ​

名称说明仓库路径
sendSingleMailToAdminuserType = 2。userId 有值就追加 AdminUserDO.emailMailSendApi / MailSendService
sendSingleMailToMemberuserType = 1。邮箱来自 MemberService.getMemberUserEmail同上
请求体userId、toMails / ccMails / bccMails、templateCode、templateParams、attachments.../api/mail/dto/MailSendSingleToUserReqDTO.java
返回日志 id,CommonResult<Long>MailSendApiImpl

userId 可空。为空时必须自己传合法 toMails。用户邮箱和 toMails 会去重合并;抄送、密送同样只保留能通过邮箱校验的地址。

合并后收件人为空,抛 MAIL_SEND_MAIL_NOT_EXISTS(文案是「邮箱不存在」)。管理员没填邮箱、会员没有邮箱、又没传 toMails,都会走到这里。

java
@Resource
private MailSendApi mailSendApi;

public void sendDemo() {
    Map<String, Object> templateParams = new HashMap<>();
    templateParams.put("name", "张三");
    templateParams.put("code", "847291");
    mailSendApi.sendSingleMailToAdmin(new MailSendSingleToUserReqDTO()
            .setUserId(1L)
            .setTemplateCode("test_01")
            .setTemplateParams(templateParams));
}

微服务模块的 pom.xml 要依赖 yudao-module-system-api,并在 RPC 配置里声明 MailSendApi。单体 yudao-server 已经在同一进程,直接 @Resource 即可。

现成调用在 IoT 告警:场景动作按接收方式选短信 / 邮件 / 站内信,邮件分支就是 sendSingleMailToAdmin。

路径:ruoyi-office/yudao-module-iot/.../rule/scene/action/IotAlertTriggerSceneRuleAction.java。

管理员邮箱为空不会自动改发短信

getUserMail 只读用户资料上的邮箱。没填就只会用你传入的 toMails。不要假设「有 userId 就一定能发出去」。

SMTP 附录 ​

授权码、安全开关以各服务商控制台为准。下面只列常见默认值。

名称适用限制
QQ 邮箱smtp.qq.com,常用 465 + SSL先开 SMTP,密码填授权码。见 QQ 邮箱帮助
网易 163smtp.163.com,常用 465 + SSL同上。见 163 邮箱帮助
腾讯企业邮smtp.exmail.qq.com,常用 465 + SSL企业管理员要先开通客户端授权。见 企业邮帮助

Hutool 发送参数见 MailUtil。本系统调用时 HTML 模式为 true,和后台富文本一致。

配置与操作 ​

管理员先在 消息中心 → 邮箱账号 填 SMTP 主机、端口、账号密码,再在模板里绑账号、改标题和 HTML。保存后下一次 MailSendApi 就用新账号。

发信记录只读。改密码立刻生效,不必重启。开启见 系统功能设计。截图见上文账号和模板。

相关篇 ​

联系我们

获取报价、演示和二开方案

微信咨询二维码

微信咨询

17156169080

添加时备注「RuoYi Office」

在线体验商业版