结论先行
先确认管理员允许客户端访问、成员启用对应协议;开启安全登录后,使用客户端专用密码;最后填写完整邮箱地址与 SSL/TLS 服务器参数。发送采用 smtp.exmail.qq.com:465,常规多端收信采用 imap.exmail.qq.com:993。
企业微信现行官方说明列出了上述 SSL 参数,并明确海外也使用通用服务器,无需添加 hw 前缀。客户端界面虽然可能显示“SSL”,实际加密协议由客户端与服务器协商;不要为了排障切换到明文连接。参见企业微信官方参数与 FAQ。
535 Authentication failed 不能单独证明“把登录密码当成了专用密码”。它表明认证未通过,用户名错误、凭据失效或访问策略也需要排查。JavaMail 的异常说明同样把用户名或密码错误列为认证失败示例。参见 API 说明。
问题背景:网页能登录,为什么程序不能发信?
网页登录、扫码登录与邮件协议登录属于不同入口。网页能正常打开,只能证明网页端登录成功,不能证明该成员已经获准通过 SMTP、IMAP 或 POP3 访问。以 server@sparrowzoo.com 为例,Java 程序发信需要 SMTP;Foxmail 或 Outlook 同时收发邮件通常需要 IMAP + SMTP。
| 协议 | 负责什么 | 适用场景 | 容易混淆的地方 |
|---|---|---|---|
| SMTP | 提交待发送邮件 | Java 服务通知、客户端发信 | SMTP 成功不代表 IMAP 可用。 |
| IMAP | 访问和同步服务器邮件 | 电脑、手机、网页多端使用 | 删除等操作可能同步到服务器,先理解客户端行为。 |
| POP3 | 将邮件下载到客户端 | 明确需要本地下载的旧系统 | 是否保留服务器副本取决于客户端设置;不能假设永远保留。 |
本文处理已有企业邮箱账号的客户端接入。域名开通、MX 迁移和域名级投递认证应按企业邮箱管理后台单独配置;调整这些 DNS 记录不能直接修复 SMTP 的 535 认证失败。本文中的账号仅用于说明,未对该账号的后台状态作实测判断。
管理员服务范围
成员协议开关
安全登录状态
客户端专用密码
完整邮箱地址
正确服务器与端口
认证通过
测试邮件到达
详细配置:从权限到程序接入
1. 管理员与成员,两侧分别确认
1.1 管理员:把成员加入协议服务范围
- 由有邮件管理权限的管理员登录企业微信管理端。
- 在相应版本的后台进入 协作 → 邮件 → 安全管理 → 客户端访问权限。
- 查看 IMAP/SMTP 或 POP/SMTP 的服务范围,确认
server@sparrowzoo.com对应成员或其所在组织在范围内。 - 保存配置,检查是否同时存在客户端类型、IP 或账号状态方面的限制。
1.2 成员:启用需要的协议组合
- 在电脑浏览器登录自己的企业邮箱。
- 进入 设置 → 收发信设置 → 开启服务。
- 常规收发勾选 开启 IMAP/SMTP 服务;旧系统要求 POP3 时选择 开启 POP/SMTP 服务。
- 点击 保存更改,重新进入页面,确认状态已保存。
菜单会随邮箱版本、管理入口及企业策略变化。上述为常见路径;找不到入口时,由管理员在当前后台搜索“客户端访问权限”或“协议服务”。开关灰色或无法保存,优先核对管理员服务范围和当前账号状态,不能仅凭界面现象断定唯一原因。
只需要程序发信时,Java 代码只配置 SMTP 即可;后台如果以 IMAP/SMTP 或 POP/SMTP 组合提供开关,仍需按后台实际支持的组合授权。无需为了 SMTP 发信在程序中建立 IMAP 连接。
两侧开关及路径依据:企业微信 · 常用邮件客户端软件 POP/IMAP/EXCHANGE 协议设置。
2. 客户端专用密码:与网页登录密码分清
在启用安全登录的账号中,第三方客户端使用的是客户端专用密码,也常被称作客户端授权码。企业微信扫码凭证、手机验证码、网页登录密码不能直接替代它。
2.1 生成步骤
- 电脑网页版登录邮箱,进入 设置 → 邮箱绑定;部分界面可能使用“微信绑定”或安全相关标签。
- 按页面要求完成身份绑定与验证,并确认 安全登录 已开启。
- 在 客户端专用密码 区域选择 生成新密码。
- 如果支持名称,使用便于识别的名称,例如
sparrow-notification-prod或office-foxmail。 - 生成后立即保存到受控的密码或密钥管理位置,填入客户端的“密码”字段;不要期待以后能再次查看原文。
| 凭据或状态 | 如何使用 | 需要注意 |
|---|---|---|
| 网页登录密码 | 用于支持账号密码的网页入口 | 安全登录开启后,不能作为第三方客户端的替代密码。 |
| 客户端专用密码 | 填入 SMTP / IMAP / POP3 的密码字段 | 必须属于正在认证的那个完整邮箱账号。 |
| 安全登录未开启 | 以当前企业策略和页面说明为准 | 部分账号可能允许登录密码;不要写成所有企业一律允许或一律禁止。 |
| 生成入口缺失 | 检查绑定、安全登录、企业策略与账号类型 | 不能直接认定只缺“绑定微信”这一个条件。 |
2.2 失效、更新与保存
- 停用某个专用密码后,该密码立即失效;关闭安全登录后,所有已生成的专用密码都会失效。不要把关闭安全登录当作排障手段。
- 更换密码后,同步更新应用的密钥配置和所有运行实例;仅修改本地配置文件,不会自动改变已启动进程中的配置。
- 检查复制时是否夹带空格、换行或引号;按生成结果准确保存,不要在业务代码中随意改写密码。
- 为不同应用分别生成凭据(若后台支持),便于独立撤销。密码不写入 Java 源码、Git、文章截图或公开日志。
生成路径与失效规则依据:企业微信 · 邮箱绑定微信与获取客户端专用密码。生成后立即保存是操作建议;本文不把“只显示一次”作为已核验的官方规则。
3. 服务器参数,一次填对
| 服务 | 通用服务器(含海外) | 信创邮箱服务器 | 端口 | 加密 |
|---|---|---|---|---|
| SMTP 发信 | smtp.exmail.qq.com | xcsmtp.exmail.qq.com | 465 | SSL/TLS |
| IMAP 收信 | imap.exmail.qq.com | xcimap.exmail.qq.com | 993 | SSL/TLS |
| POP3 收信 | pop.exmail.qq.com | xcpop.exmail.qq.com | 995 | SSL/TLS |
参数来源:企业微信现行官方设置说明。先确认企业是否启用信创邮箱;启用时应使用相应 xc 主机,且不支持 Exchange。旧版腾讯云指南曾列出 hwsmtp / hwimap / hwpop;现行 FAQ 已明确海外无需加 hw,本文不将旧地址作为默认方案。
- 用户名:完整邮箱地址,如
server@sparrowzoo.com,不是server、显示姓名或企业微信用户 ID。 - 密码:安全登录场景下填写该成员生成的客户端专用密码。
- 发信认证:启用“SMTP 服务器需要身份验证”;基线配置中收发信使用同一账号与有效凭据。
- 服务器类型:不要把腾讯企业邮箱的
*.exmail.qq.com与个人 QQ 邮箱的服务器混用。
465 的 SSL/TLS 与 STARTTLS 有什么区别?
本指南使用 465 的隐式 TLS:连接建立后直接进行 TLS 握手,对应 mail.smtp.ssl.enable=true。STARTTLS 则是在另一种连接模式中通过 SMTP 命令升级加密,不是将 465 上的 SSL 开关换一个名字。本文不把 587 当作已核验的腾讯企业邮箱推荐参数。
示例统一使用 smtp 协议配合 mail.smtp.* 属性。如果你改用 smtps,必须检查实现要求的 mail.smtps.* 属性前缀,避免端口、认证和超时配置没有生效。参见 JavaMail SMTP 属性说明。
4. Foxmail、Outlook 与其他客户端
客户端版本和操作系统不同,按钮名称会变化。下面以手动配置 IMAP + SMTP为统一核对方式;如果自动识别失败,手动填写服务器与端口。
4.1 Foxmail
- 新建账号,选择腾讯企业邮箱或手动配置入口。
- 使用账号密码方式时,输入完整邮箱地址及客户端专用密码;若当前版本提供企业邮箱扫码登录,可按其引导操作。
- 收信类型选择 IMAP:服务器
imap.exmail.qq.com,端口993,开启 SSL/TLS。 - 发信服务器
smtp.exmail.qq.com,端口465,开启 SSL/TLS 和身份验证。 - 保存后分别验证收信和发信,不要只看账号添加成功的提示。
4.2 Outlook
- 添加账户,选择高级设置、手动设置或 IMAP 入口(具体名称依版本而定)。
- 收信服务器和发信服务器分别填写上述 IMAP、SMTP 参数。
- 在提供相关选项的经典版本中,勾选“发送服务器需要验证”,使用与接收服务器相同的账号凭据。
- 高级设置中核对 IMAP
993、SMTP465,加密选择 SSL/TLS。 - 测试收发。若版本不提供这些字段,使用该版本的手动 IMAP 指引,不照搬其他版本截图。
4.3 手机邮箱或 POP3 客户端
支持手动 IMAP/SMTP 的手机邮件应用使用同一组参数。确需 POP3 时,将收信改为 pop.exmail.qq.com:995,同时开启对应 POP/SMTP 权限;发信仍为 SMTP 465。第一次同步前,核对“保留服务器邮件副本”和删除策略,避免误把下载当成多端同步。
官方示例可参考腾讯云操作指南中的 Foxmail、Outlook 与移动客户端设置;本文按通用字段重组步骤,当前客户端界面优先。
5. JavaMail / Jakarta Mail:完整发送示例
5.1 先确认依赖属于哪个命名空间
下面代码使用 javax.mail,适用于已有 JavaMail 1.6.x API 的项目。采用 Jakarta Mail 2.x 的项目,应将全部 javax.mail 导入改为 jakarta.mail,并使用相匹配的运行时实现,例如 Eclipse Angus Mail。不要只加入 API 包后遗漏 SMTP 实现,也不要同时混用两套命名空间。Spring Boot 项目优先使用下一节的 starter 和项目自身的版本管理。
运行前,在应用实际启动环境中配置以下变量。本文不提供真实密码;环境变量也应通过受控部署配置注入,避免把密钥写进命令历史。
| 变量 | 值或用途 | 是否必需 |
|---|---|---|
MAIL_USERNAME | 完整邮箱地址,例如 server@sparrowzoo.com | 是 |
MAIL_CLIENT_PASSWORD | 该账号生成的客户端专用密码 | 是 |
MAIL_TEST_TO | 由你控制、用于验收的收件邮箱 | 发送测试邮件时需要 |
ExmailSmtpExample.java · 运行后发送一封测试邮件
import java.util.Date;
import java.util.Properties;
import javax.mail.Message;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;
public final class ExmailSmtpExample {
private static String requiredEnv(String name) {
String value = System.getenv(name);
if (value == null || value.trim().isEmpty()) {
throw new IllegalArgumentException("Missing environment variable: " + name);
}
return value; // 原样使用凭据,不修改密码内容
}
public static void main(String[] args) throws Exception {
String username = requiredEnv("MAIL_USERNAME");
String password = requiredEnv("MAIL_CLIENT_PASSWORD");
String recipient = requiredEnv("MAIL_TEST_TO");
Properties props = new Properties();
props.setProperty("mail.smtp.host", "smtp.exmail.qq.com");
props.setProperty("mail.smtp.port", "465");
props.setProperty("mail.smtp.auth", "true");
props.setProperty("mail.smtp.ssl.enable", "true");
props.setProperty("mail.smtp.ssl.checkserveridentity", "true");
props.setProperty("mail.smtp.starttls.enable", "false");
props.setProperty("mail.smtp.connectiontimeout", "10000");
props.setProperty("mail.smtp.timeout", "10000");
props.setProperty("mail.smtp.writetimeout", "10000");
props.setProperty("mail.debug.auth", "false");
Session session = Session.getInstance(props);
session.setDebug(false);
MimeMessage message = new MimeMessage(session);
message.setFrom(new InternetAddress(username));
message.setRecipient(Message.RecipientType.TO,
new InternetAddress(recipient, true));
message.setSubject("腾讯企业邮箱 SMTP 配置测试", "UTF-8");
message.setText("如果收到此邮件,说明本次 SMTP 提交与投递链路已完成。", "UTF-8");
message.setSentDate(new Date());
Transport.send(message, username, password);
System.out.println("SMTP submission completed; check the recipient inbox.");
}
}
示例发件人 From 与认证账号一致,便于先建立可工作的基线。需要别名或代表其他地址发信时,先确认相应的代发权限。超时单位为毫秒,10000 是示例值,可按网络环境调整。不要设置 mail.smtp.ssl.trust=* 或关闭证书校验来“解决”TLS 错误。
属性依据:SMTP provider 配置;发送方法依据:Transport.send API。正常返回表示提交成功,最终是否到达收件箱仍需收件端确认。
5.2 只验证 SMTP 登录,不发送邮件
排查认证时,可在上例创建 session 后,以以下代码替换创建消息及发送部分;同时去掉读取 MAIL_TEST_TO 的那一行。这样可以将“认证成功”与“投递成功”分开验证。
try (Transport transport = session.getTransport("smtp")) {
transport.connect("smtp.exmail.qq.com", 465, username, password);
System.out.println("SMTP authentication succeeded.");
}
5.3 如果程序还需要 IMAP 收信
收信另建一个 Store;以下片段复用前面的 username、password,并额外导入 javax.mail.Store(Jakarta 项目使用对应包)。它只验证连接,不读取或删除邮件。
Properties imapProps = new Properties();
imapProps.setProperty("mail.imap.ssl.enable", "true");
imapProps.setProperty("mail.imap.ssl.checkserveridentity", "true");
imapProps.setProperty("mail.imap.connectiontimeout", "10000");
imapProps.setProperty("mail.imap.timeout", "10000");
Session imapSession = Session.getInstance(imapProps);
try (Store store = imapSession.getStore("imap")) {
store.connect("imap.exmail.qq.com", 993, username, password);
System.out.println("IMAP authentication succeeded.");
}
6. Spring Boot:交给 JavaMailSender 管理
在已经使用 Spring Boot parent 或 BOM 管理依赖版本的项目中,加入 mail starter;版本沿用当前项目,避免另引入一套不兼容的邮件实现。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>
application.yml · 密码来自运行环境
spring:
mail:
host: smtp.exmail.qq.com
port: 465
protocol: smtp
username: ${MAIL_USERNAME}
password: ${MAIL_CLIENT_PASSWORD}
default-encoding: UTF-8
properties:
"[mail.smtp.auth]": true
"[mail.smtp.ssl.enable]": true
"[mail.smtp.ssl.checkserveridentity]": true
"[mail.smtp.starttls.enable]": false
"[mail.smtp.connectiontimeout]": 10000
"[mail.smtp.timeout]": 10000
"[mail.smtp.writetimeout]": 10000
"[mail.debug.auth]": false
方括号包围的键会作为完整 JavaMail 属性名传递。Spring Boot 官方特别提醒,部分邮件超时默认可能为无限等待,因此建议显式设置。若项目配置了 spring.mail.jndi-name,JNDI Session 会优先于其他 Session 设置,排查时也要确认是否存在自定义 JavaMailSender 覆盖自动配置。参见 Spring Boot Sending Email。
ExmailTestService.java · 在需要测试时显式调用
import org.springframework.beans.factory.annotation.Value;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.stereotype.Service;
@Service
public class ExmailTestService {
private final JavaMailSender sender;
private final String from;
public ExmailTestService(JavaMailSender sender,
@Value("${spring.mail.username}") String from) {
this.sender = sender;
this.from = from;
}
public void sendTest(String recipient) {
SimpleMailMessage message = new SimpleMailMessage();
message.setFrom(from);
message.setTo(recipient);
message.setSubject("腾讯企业邮箱配置测试");
message.setText("请确认这封测试邮件已到达收件箱。");
sender.send(message);
}
}
将 recipient 设为你控制的测试邮箱,再由测试入口调用该方法;不要把示例直接做成无需鉴权、可任意指定收件人的公开接口。部署后还要确认实际启用的 profile、容器或服务进程已读取新的配置。
排查流程:先定位失败发生在哪一层
固定同一个账号、服务器和运行环境,每次只调整一个因素。先保存脱敏后的完整错误信息,包括 SMTP 返回码、失败阶段和发生时间;只提供“发送失败”四个字无法区分网络、认证与投递问题。
7. DNS、TCP 与 TLS:先验证连接
以下命令应在真正运行 Java 服务的机器或容器网络环境中执行。Mac 本机能连通,不代表部署服务器也能连通。命令只检查解析、端口或 TLS,不执行账号认证,也不发送邮件。
# macOS / Linux;需要安装对应工具
nslookup smtp.exmail.qq.com
nc -vz -w 5 smtp.exmail.qq.com 465
# 需要收信时再检查 IMAP
nc -vz -w 5 imap.exmail.qq.com 993
# Windows PowerShell
Resolve-DnsName smtp.exmail.qq.com
Test-NetConnection smtp.exmail.qq.com -Port 465
端口不通,检查 DNS、防火墙、云出口策略、容器网络或代理路径。不要只用 ping 判断邮件端口是否可用。
# OpenSSL 1.1.1+ / 3.x;检查 TLS 与服务端身份
openssl s_client -connect smtp.exmail.qq.com:465 \
-servername smtp.exmail.qq.com \
-verify_hostname smtp.exmail.qq.com \
-verify_return_error -crlf
正常情况下可以看到 TLS 协商信息、证书校验通过,以及 SMTP 的 220 问候;输入 QUIT 退出。工具版本、系统 CA 路径不同可能导致额外配置需求。即使这里成功,Java 的 truststore 或 JDK 环境仍可能不同,必须结合 Java 的异常继续检查。不要在终端手工粘贴密码执行 AUTH。
8. 535 认证失败:按这个顺序检查
- 确认真实账号:认证用户名是否是完整邮箱地址,是否误用了显示名、别名或其他成员生成的密码。
- 确认密码类型与状态:安全登录开启时使用专用密码;无法确认凭据有效性时,在后台重新生成并更新受控配置。
- 确认两级权限:管理员服务范围包含该成员,成员协议开关已保存。
- 确认程序生效配置:排除旧环境变量、错误 profile、自定义 Session / Sender、旧进程或某个实例尚未重启。
- 确认账号与访问策略:让管理员核对账号停用、风险限制、客户端限制和适用的 IP 访问策略;以完整服务端返回为准。
- 最小化对照:在同一网络使用客户端或本节认证探针验证;若只有应用失败,重点对比它实际加载的配置与依赖。
RFC 4954 将 535 5.7.8 定义为认证凭据无效或不足。它不是“网页登录密码被误用”的专属错误码。没有完整返回信息和后台检查结果时,应写“可能原因”,不能写“已确定直接原因”。参见 RFC 4954 §6。
9. 常见现象与下一步动作
| 现象 / 日志 | 优先检查 | 下一步 |
|---|---|---|
UnknownHostException | 域名拼写、DNS、运行环境解析 | 在实际服务器解析域名,不先重置密码。 |
连接超时 / Connection refused | 出站网络、端口、路由及防火墙 | 检测 465 / 993;区分连接阶段超时与读写阶段超时。 |
SSLHandshakeException / PKIX | JDK 信任库、证书链、系统时间、TLS 拦截 | 检查证书与运行环境;不要关闭校验或信任所有主机。 |
535 / AuthenticationFailedException | 用户名、专用密码、权限和账号策略 | 按上方六步逐项核对,保留脱敏后的服务端原文。 |
| 无法勾选协议服务 | 管理员服务范围、当前账号和页面权限 | 由管理员核对授权;保存后重新登录检查。 |
| 没有生成密码按钮 | 账号绑定、安全登录及企业策略 | 检查当前版本的绑定 / 安全设置,不照搬旧界面。 |
| 之前能用,突然失效 | 凭据撤销、安全登录变化、账号策略及部署变更 | 对照变更时间;必要时生成新凭据并同步各实例。 |
| 终端启动正常,supervisord 启动后读不到环境变量 | systemd 服务环境与应用变量名是否一致 | 在 supervisord 的 service 中配置 Environment=,重新加载 unit 并重启服务,详见环境变量不生效。 |
| 明确提示 IP 不在允许范围 | 企业是否启用适用的 IP 访问限制 | 由管理员核对服务器实际公网出口 IP;容器私网 IP 不能替代公网出口。 |
| 能收不能发 | SMTP 主机、465、TLS、发信认证 | 单独测试 SMTP,不能用 IMAP 登录成功代替发信验证。 |
| 能发不能收 | IMAP / POP 权限、收信主机与端口 | 按实际协议分别检查 993 或 995。 |
认证成功,但 550 / 553 等被拒 | 完整错误文本、发件人权限、收件地址、投递策略 | 先确定拒绝发生在 MAIL FROM、RCPT TO 还是 DATA;错误码不与单一原因绑定。 |
| 只收到近 30 天邮件 / 文件夹不同步 | 收取时间范围、自定义文件夹与文件夹锁设置 | 在网页版收发信设置核对收取范围及“收取我的文件夹”;已发送不同步时检查“保存已发送至服务器”。官方 FAQ |
| 程序提示成功,收件人未收到 | 延迟、退信、垃圾箱、隔离区、收件方策略 | 查看消息 ID、退信和管理后台日志;提交成功不等于最终送达。 |
NoSuchProviderException / 类缺失 | 是否只有 API、是否混用了 javax / jakarta | 检查依赖树及运行时邮件实现,不先修改服务器参数。 |
诊断日志应该保留什么?
可临时设置 session.setDebug(true),并保持 mail.debug.auth=false;Spring Boot 可临时添加 spring.mail.properties[mail.debug]=true。即使关闭认证调试,日志仍可能包含地址、主机和会话信息,分享前要脱敏,排障后关闭。
提交给管理员的信息应包括:发生时间与时区、脱敏账号、源环境与出口 IP、主机和端口、使用的 TLS 模式、JDK / 邮件库版本、完整错误码及失败阶段。不要附带专用密码或完整邮件正文。
10. 常见问题:supervisord 启动后环境变量不生效
现象:终端中手动启动 Java 程序可以读取密码,通过 systemd 启动 supervisord 后,程序读不到 email_password 等变量,或仍使用旧值。
原因:systemd 管理的系统服务不会自动继承当前终端的 export,也不会自动加载该用户的 .bashrc、.bash_profile。环境应沿着 systemd → supervisord → Java 子进程传递。Supervisor 会继承启动环境,其全局或 [program:x] 中的 environment= 还可能覆盖同名值。参见 Supervisor 环境继承说明。
10.1 在 systemd 服务中声明变量
/usr/lib/systemd/system/supervisord.service 通常是安装包提供的原始文件;本机自定义完整 unit 放在 /etc/systemd/system/supervisord.service,同名文件优先。无需同时修改两处。先确认当前加载的文件及附加配置:
sudo systemctl show supervisord.service -p FragmentPath -p DropInPaths
# 查看安装包提供的原始配置
sudo vi /usr/lib/systemd/system/supervisord.service
# 编辑本机自定义配置;若文件不存在,先从原始 unit 复制并保留必要设置
sudo vi /etc/systemd/system/supervisord.service
以下沿用当前部署的路径与 Type=forking,关键是 [Service] 内的 Environment=。已有 unit 应保留原有 PIDFile、停止方式、用户和其他必要设置;如果只需追加变量,也可以执行 sudo systemctl edit supervisord.service,在 drop-in 中仅填写 [Service] 与 Environment=。
[Unit]
Description=Process Monitoring and Control Daemon
After=rc-local.service
[Service]
Type=forking
ExecStart=/usr/bin/supervisord -c /root/supervisord/supervisord.conf
RuntimeDirectory=supervisor
RuntimeDirectoryMode=755
Environment="authenticator_encrypt_key=111111" "email_password=111111" "mysql_sparrow_password=111111"
[Install]
WantedBy=multi-user.target
After=rc-local.service 只声明启动顺序,不会继承 rc.local 中导出的变量。RuntimeDirectory=supervisor 只负责创建运行目录,PID 文件和 socket 路径仍由 supervisord 配置决定;使用 PIDFile= 时须与 supervisord 的 pidfile 路径一致。
111111 全部为占位值,部署时替换;其中 email_password 应填写邮箱客户端专用密码。unit 中的 Environment= 是明文配置,实际密钥应使用受限的部署配置管理,不提交到公开仓库。Type=forking 要求 supervisord 以后台模式启动;若原服务使用 -n 或 nodaemon=true,保留匹配的前台服务类型,不要直接套用这里的 forking 设置。
10.2 变量名必须与程序读取方式一致
环境变量名区分大小写。当前部署使用 email_password,而本文前面的示例读取 MAIL_CLIENT_PASSWORD,两者不会自动对应。沿用当前部署变量名时,Java 读取改为 System.getenv("email_password");Spring Boot 配置改为:
spring:
mail:
password: ${email_password}
上面的 YAML 只替换原配置中的 password 字段,其他 SMTP 参数和用户名配置保持完整。也可以保留原代码,在 service 中直接声明 MAIL_CLIENT_PASSWORD。不要写成 Environment=MAIL_CLIENT_PASSWORD=$email_password,systemd 的这个字段不进行 shell 变量展开。
10.3 重新加载 unit,再重启 supervisord
仅保存文件、执行 source、supervisorctl reread/update 或仅重启受管程序,都不能让已运行的 supervisord 获得新的 systemd 环境。需要让 systemd 重新读取 unit,再重新创建 supervisord 及其子进程:
sudo systemctl daemon-reload
sudo systemctl restart supervisord.service
sudo systemctl status supervisord.service --no-pager
sudo supervisorctl -c /root/supervisord/supervisord.conf status
重启 supervisord 会影响它管理的程序,应在可接受的重启窗口执行。检查各程序是否重新进入 RUNNING;未配置自动启动的程序需按实际服务名启动。daemon-reload 只加载配置,不会单独改变正在运行的进程环境。
10.4 在目标 Java 进程验证,避免只检查终端
在实际受 supervisord 管理的应用中临时检查变量是否存在,输出布尔值即可;终端执行 echo 得到的值不能证明服务进程已经收到配置。
String emailPassword = System.getenv("email_password");
System.out.println("email_password configured: "
+ (emailPassword != null && !emailPassword.trim().isEmpty()));
确认变量已传入后,再做 SMTP 认证测试。若仍读到旧值,核对是否连到了另一套 supervisord 实例、是否存在同名 environment= 覆盖,以及业务程序是否真正重启。
验收与日常维护
按下面顺序验收,避免只看到“已连接”就认为配置全部完成。
- 权限:记录管理员服务范围与成员协议开关的检查结果。
- 认证:在实际部署环境完成 SMTP 登录;需要收信时另验 IMAP 或 POP3。
- 投递:给可控邮箱发送一封带时间标识的测试邮件,确认收件箱或垃圾箱中的实际到达情况;必要时同时验证企业内和企业外收件地址。
- 收信:使用客户端刷新收件箱,确认邮件可读;IMAP 场景再检查已读状态同步是否符合预期。
- 持久化:重启应用或客户端后复测,确认配置与密钥没有只存在于临时会话。
- 维护:记录凭据用途和责任人;轮换时先部署并验证新凭据,再按管理策略撤销旧凭据。
生产通知应设置合理超时、失败告警和有界重试。认证失败时不要高频重试;在提交结果不明确的超时场景下,重试可能导致重复邮件,应使用业务消息标识辅助去重。批量发送能力与额度以企业实际套餐和后台策略为准,本文不假定固定限额。
总结:权限、凭据、连接、投递逐层确认
管理员允许访问 + 成员启用协议 + 使用当前策略要求的有效凭据 + 正确的 SSL/TLS 参数,构成客户端配置的基础。开启安全登录时,凭据就是客户端专用密码。
遇到 535,优先核对完整账号、专用密码和两级权限,再看运行配置与安全策略;连接超时先查网络,TLS 异常先查证书,认证成功后的退信再查投递。每一步都用对应的检查结果验证,直到测试邮件真实到达。
从权限检查开始官方资料与适用边界
本指南整理于 2026 年 9 月 26 日。服务器参数参考腾讯官方资料;管理菜单、绑定方式、密码生命周期及企业策略以当前账号后台为准。以下示例为接入方法,不表示已经对示例账号进行真实登录或发送测试。
- 企业微信 · 常用邮件客户端软件 POP/IMAP/EXCHANGE 协议设置:两级权限、通用与信创参数、海外无需 hw 前缀及客户端常见问题。
- 企业微信 · 邮箱绑定微信与获取客户端专用密码:生成路径、安全登录及密码失效规则。
- 腾讯云 · 腾讯企业邮操作指南(旧版 PDF):历史参数及客户端示例;海外 hw 主机说明与现行帮助不同,以现行帮助为准。
- JavaMail · SMTP provider 属性:认证、SSL、STARTTLS、超时和属性前缀。
- JavaMail FAQ:SSL 配置、依赖和常见接入问题。
- JavaMail · Transport API:带用户名和密码的发送方法,以及提交成功与最终送达的区别。
- JavaMail · AuthenticationFailedException:认证失败异常语义。
- Spring Boot · Sending Email:mail starter、自动配置、超时和 JNDI Session 优先级。
- RFC 4954 · SMTP Authentication:535 等认证响应码的标准语义。
- systemd.exec · Environment、systemctl:服务环境配置、daemon-reload 与重启。
- Supervisor · Subprocess Environment:子进程环境继承与 environment 配置覆盖关系。