Servlet 发送电子邮件
SMTP 会话、JavaMail 发送纯文本与 HTML 邮件、附件与常见失败原因。
Key Points发信要连 SMTP 服务器并认证;密码用授权码而非登录密码,配置放外部不写死在代码里。
流程
Text
你的应用 --SMTP--> 邮件服务器 --SMTP--> 收件人服务器 --> 收件箱
(认证、投递) (DNS MX 查询)Your app does not deliver to the recipient; it hands the message to its own SMTP server, which handles queueing, retries and bounces.
纯文本邮件
Java
import jakarta.mail.*;
import jakarta.mail.internet.*;
import java.util.Properties;
public class Mailer {
public static void send(String to, String subject, String body) throws Exception {
Properties p = new Properties();
p.put("mail.smtp.host", "smtp.example.com");
p.put("mail.smtp.port", "587");
p.put("mail.smtp.auth", "true");
p.put("mail.smtp.starttls.enable", "true");
Session session = Session.getInstance(p, new Authenticator() {
@Override
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(
System.getenv("MAIL_USER"),
System.getenv("MAIL_PASS"));
}
});
Message msg = new MimeMessage(session);
msg.setFrom(new InternetAddress(System.getenv("MAIL_USER")));
msg.setRecipients(Message.RecipientType.TO, InternetAddress.parse(to));
msg.setSubject(subject, "UTF-8");
msg.setText(body, "UTF-8");
Transport.send(msg);
}
}HTML 邮件与附件
Java
MimeMessage msg = new MimeMessage(session);
msg.setFrom(new InternetAddress(from));
msg.setRecipients(Message.RecipientType.TO, InternetAddress.parse(to));
msg.setSubject("月度报告", "UTF-8");
Multipart multipart = new MimeMultipart("mixed");
// 正文:纯文本 + HTML 两段,邮件客户端自己挑
MimeBodyPart text = new MimeBodyPart();
text.setText("您的客户端不支持 HTML。", "UTF-8");
MimeBodyPart html = new MimeBodyPart();
html.setContent("<h1>月度报告</h1><p>详见附件。</p>", "text/html;charset=UTF-8");
MimeMultipart alt = new MimeMultipart("alternative");
alt.addBodyPart(text);
alt.addBodyPart(html);
MimeBodyPart bodyPart = new MimeBodyPart();
bodyPart.setContent(alt);
multipart.addBodyPart(bodyPart);
// 附件
MimeBodyPart file = new MimeBodyPart();
file.attachFile("/data/report.pdf");
file.setFileName(MimeUtility.encodeText("月度报告.pdf", "UTF-8", "B"));
multipart.addBodyPart(file);
msg.setContent(multipart);
Transport.send(msg);multipart/alternative means "same content, different formats" and the client picks one; multipart/mixed means "several parallel parts" for attachments. Nesting them the wrong way shows two bodies.
常见失败原因
| Symptom | Cause |
|---|---|
| 535 auth failed | 用了登录密码而非授权码;或未开 SMTP |
| Connection timed out | 端口被封(25 常被云厂商禁用) |
| 550 relay denied | 发件人域名与认证账号不一致 |
| Subject garbled | 未指定 UTF-8 编码 |
| Lands in spam | SPF/DKIM/DMARC 未配置 |
Sending synchronously on the request thread lets one slow SMTP server stall the whole request. In production, enqueue and send asynchronously with retries; email must not affect response time.
流程
Text
你的应用 --SMTP--> 邮件服务器 --SMTP--> 收件人服务器 --> 收件箱
(认证、投递) (DNS MX 查询)应用不直接投递到收件人,只负责把邮件交给自己的 SMTP 服务器。之后的排队、重试、退信都由邮件服务器处理。
纯文本邮件
Java
import jakarta.mail.*;
import jakarta.mail.internet.*;
import java.util.Properties;
public class Mailer {
public static void send(String to, String subject, String body) throws Exception {
Properties p = new Properties();
p.put("mail.smtp.host", "smtp.example.com");
p.put("mail.smtp.port", "587");
p.put("mail.smtp.auth", "true");
p.put("mail.smtp.starttls.enable", "true");
Session session = Session.getInstance(p, new Authenticator() {
@Override
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(
System.getenv("MAIL_USER"),
System.getenv("MAIL_PASS"));
}
});
Message msg = new MimeMessage(session);
msg.setFrom(new InternetAddress(System.getenv("MAIL_USER")));
msg.setRecipients(Message.RecipientType.TO, InternetAddress.parse(to));
msg.setSubject(subject, "UTF-8");
msg.setText(body, "UTF-8");
Transport.send(msg);
}
}HTML 邮件与附件
Java
MimeMessage msg = new MimeMessage(session);
msg.setFrom(new InternetAddress(from));
msg.setRecipients(Message.RecipientType.TO, InternetAddress.parse(to));
msg.setSubject("月度报告", "UTF-8");
Multipart multipart = new MimeMultipart("mixed");
// 正文:纯文本 + HTML 两段,邮件客户端自己挑
MimeBodyPart text = new MimeBodyPart();
text.setText("您的客户端不支持 HTML。", "UTF-8");
MimeBodyPart html = new MimeBodyPart();
html.setContent("<h1>月度报告</h1><p>详见附件。</p>", "text/html;charset=UTF-8");
MimeMultipart alt = new MimeMultipart("alternative");
alt.addBodyPart(text);
alt.addBodyPart(html);
MimeBodyPart bodyPart = new MimeBodyPart();
bodyPart.setContent(alt);
multipart.addBodyPart(bodyPart);
// 附件
MimeBodyPart file = new MimeBodyPart();
file.attachFile("/data/report.pdf");
file.setFileName(MimeUtility.encodeText("月度报告.pdf", "UTF-8", "B"));
multipart.addBodyPart(file);
msg.setContent(multipart);
Transport.send(msg);multipart/alternative 表示「同一内容的不同格式」,客户端选最合适的显示;multipart/mixed 表示「多个并列部分」,用于附件。嵌套顺序不能反,否则邮件会显示成两个正文。
常见失败原因
| 现象 | 原因 |
|---|---|
| 认证失败 | 用了登录密码而非授权码;或未开 SMTP |
| 连接超时 | 端口被封(25 常被云厂商禁用) |
| 拒绝转发 | 发件人域名与认证账号不一致 |
| 主题乱码 | 未指定 UTF-8 编码 |
| 进垃圾箱 | SPF/DKIM/DMARC 未配置 |
在 Servlet 的请求线程里同步发信,一个慢 SMTP 就能拖住整个请求。生产环境应该丢进队列异步发,失败重试;邮件不该影响用户操作的响应时间。