Servlet 发送电子邮件

SMTP 会话、JavaMail 发送纯文本与 HTML 邮件、附件与常见失败原因。

Intermediate
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.

常见失败原因

SymptomCause
535 auth failed用了登录密码而非授权码;或未开 SMTP
Connection timed out端口被封(25 常被云厂商禁用)
550 relay denied发件人域名与认证账号不一致
Subject garbled未指定 UTF-8 编码
Lands in spamSPF/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.

Friends & Partners