1. 项目概述:用Java发送带附件的邮件
在业务系统开发中,邮件通知是常见的功能需求。相比普通文本邮件,带附件的邮件发送需要处理更复杂的MIME协议封装和编码转换。最近在开发一个合同管理系统时,就遇到了需要自动发送PDF格式电子合同的需求。
传统做法是调用第三方邮件服务商的API,但这样会引入额外依赖。其实用Java标准库就能实现完整的带附件邮件发送功能,核心只需要掌握三个技术点:JavaMail API的Session配置、MIME消息体的构造、以及附件文件的编码处理。下面通过一个生产级案例,详细说明实现过程和避坑要点。
2. 环境准备与依赖配置
2.1 必备组件说明
发送邮件需要两个核心组件:
- JavaMail API:提供SMTP协议实现和邮件构造接口
- JAF(JavaBeans Activation Framework):处理附件类型识别和编码转换
在Maven项目中添加以下依赖(以Spring Boot为例):
<dependency> <groupId>com.sun.mail</groupId> <artifactId>javax.mail</artifactId> <version>1.6.2</version> </dependency> <dependency> <groupId>javax.activation</groupId> <artifactId>activation</artifactId> <version>1.1.1</version> </dependency>注意:Java 9+版本已内置JAF,如果使用新版JDK可以省略activation依赖
2.2 SMTP服务器配置
以网易企业邮箱为例的配置参数:
mail.smtp.host=smtp.qiye.163.com mail.smtp.port=465 mail.smtp.ssl.enable=true mail.smtp.auth=true mail.user=yourname@company.com mail.password=yourpassword常见问题排查:
- 端口冲突:SSL加密用465,TLS加密用587
- 认证失败:检查是否开启SMTP服务密码(与企业邮箱登录密码可能不同)
- 连接超时:检查防火墙是否放行出站连接
3. 核心实现代码解析
3.1 创建邮件会话
Properties props = new Properties(); props.put("mail.smtp.auth", "true"); props.put("mail.smtp.starttls.enable", "true"); // 启用TLS props.put("mail.smtp.host", smtpHost); props.put("mail.smtp.port", smtpPort); Session session = Session.getInstance(props, new Authenticator() { protected PasswordAuthentication getPasswordAuthentication() { return new PasswordAuthentication(username, password); } });关键参数说明:
mail.smtp.timeout:建议设为30000ms(30秒)mail.smtp.connectiontimeout:建议15000msmail.debug:设为true可输出协议交互日志
3.2 构建MIME消息体
带附件邮件需要使用MimeMultipart构造混合内容:
MimeMessage message = new MimeMessage(session); message.setFrom(new InternetAddress(from)); message.setRecipients(Message.RecipientType.TO, InternetAddress.parse(to)); // 设置主题(处理中文编码) message.setSubject(MimeUtility.encodeText(subject, "UTF-8", "B")); // 创建多部分消息 MimeMultipart multipart = new MimeMultipart(); // 添加文本内容 MimeBodyPart textPart = new MimeBodyPart(); textPart.setContent(textContent, "text/html; charset=utf-8"); multipart.addBodyPart(textPart); // 添加附件 MimeBodyPart attachPart = new MimeBodyPart(); attachPart.attachFile(file); attachPart.setFileName(MimeUtility.encodeText(file.getName())); multipart.addBodyPart(attachPart); message.setContent(multipart);实测发现:Windows系统下中文附件名必须用
MimeUtility.encodeText处理,否则接收方可能显示乱码
4. 高级功能实现
4.1 发送多个附件
File[] attachments = new File("path").listFiles(); for (File file : attachments) { if(file.isFile()) { MimeBodyPart attachPart = new MimeBodyPart(); attachPart.attachFile(file); attachPart.setFileName(MimeUtility.encodeText(file.getName())); multipart.addBodyPart(attachPart); } }4.2 内嵌图片的HTML邮件
// HTML内容包含cid引用 String html = "<html><body><img src='cid:image1'></body></html>"; // 创建HTML部分 MimeBodyPart htmlPart = new MimeBodyPart(); htmlPart.setContent(html, "text/html; charset=utf-8"); // 创建图片部分 MimeBodyPart imagePart = new MimeBodyPart(); imagePart.attachFile(new File("logo.png")); imagePart.setContentID("<image1>"); imagePart.setDisposition(MimeBodyPart.INLINE); // 添加到multipart multipart.addBodyPart(htmlPart); multipart.addBodyPart(imagePart);4.3 大文件分块传输
对于超过10MB的附件,建议启用分块传输:
props.put("mail.smtp.chunksize", "1048576"); // 1MB分块5. 生产环境注意事项
5.1 性能优化建议
- 连接池配置:使用Spring的
JavaMailSenderImpl
@Bean public JavaMailSenderImpl mailSender() { JavaMailSenderImpl sender = new JavaMailSenderImpl(); sender.setHost("smtp.xxx.com"); sender.setSession(session); // 复用Session return sender; }- 异步发送:结合线程池处理
ExecutorService executor = Executors.newFixedThreadPool(5); executor.submit(() -> { try { Transport.send(message); } catch (Exception e) { logger.error("邮件发送失败", e); } });5.2 常见问题排查表
| 现象 | 可能原因 | 解决方案 |
|---|---|---|
| 连接超时 | 防火墙拦截/网络问题 | 检查telnet smtp端口是否通 |
| 认证失败 | 密码错误/SMTP服务未开启 | 在邮箱设置中开启SMTP服务 |
| 附件丢失 | 大小超过限制 | 检查服务器附件大小限制(如Exchange默认10MB) |
| 中文乱码 | 未设置编码 | 对所有文本使用MimeUtility.encodeText |
| 被识别为垃圾邮件 | 内容格式问题 | 添加DKIM签名和SPF记录 |
5.3 安全建议
- 密码存储:使用Jasypt等工具加密配置
- 附件扫描:集成病毒扫描服务(如ClamAV)
- 频率限制:控制每分钟发送量(避免被判定为垃圾邮件)
6. 完整工具类实现
以下是经过生产验证的邮件工具类:
public class MailUtil { private static final Logger logger = LoggerFactory.getLogger(MailUtil.class); public static void sendEmailWithAttachments(String to, String subject, String text, List<File> files) throws Exception { Properties props = loadSmtpConfig(); Session session = createSession(props); MimeMessage message = new MimeMessage(session); message.setFrom(new InternetAddress(props.getProperty("mail.from"))); message.setRecipients(Message.RecipientType.TO, InternetAddress.parse(to)); message.setSubject(MimeUtility.encodeText(subject, "UTF-8", "B")); MimeMultipart multipart = new MimeMultipart(); // 文本内容 MimeBodyPart textPart = new MimeBodyPart(); textPart.setContent(text, "text/html; charset=utf-8"); multipart.addBodyPart(textPart); // 附件处理 for (File file : files) { try { MimeBodyPart attachPart = new MimeBodyPart(); attachPart.attachFile(file); attachPart.setFileName(MimeUtility.encodeText(file.getName())); multipart.addBodyPart(attachPart); } catch (IOException e) { logger.warn("附件添加失败: {}", file.getName(), e); } } message.setContent(multipart); Transport.send(message); } private static Session createSession(Properties props) { return Session.getInstance(props, new Authenticator() { protected PasswordAuthentication getPasswordAuthentication() { return new PasswordAuthentication( props.getProperty("mail.user"), props.getProperty("mail.password") ); } }); } }7. 测试验证方案
建议使用MailHog搭建本地测试环境:
- 启动MailHog服务
docker run -d -p 1025:1025 -p 8025:8025 mailhog/mailhog- 修改SMTP配置指向localhost:1025
mail.smtp.host=localhost mail.smtp.port=1025 mail.smtp.auth=false- 访问http://localhost:8025查看收到的邮件
8. 扩展应用场景
8.1 合同管理系统
典型流程:
- 生成PDF合同(使用Apache PDFBox)
- 添加数字签名(集成CA证书服务)
- 通过邮件发送给多方签署
8.2 监控告警系统
结合Prometheus AlertManager:
- 将告警信息生成HTML报告
- 附加相关日志文件
- 发送给值班人员
8.3 报表自动分发
定时任务实现:
- 每天凌晨生成Excel报表
- 按部门拆分不同sheet
- 分别发送给各部门负责人
在最近的一个电商项目中,我们通过优化附件发送逻辑,将月结账单的发送时间从平均12秒缩短到3秒。关键改进点是:
- 使用BufferedInputStream预读附件
- 对相同附件启用内存缓存
- 采用ZIP压缩多个小文件