
1. 项目概述用Java发送带附件的邮件在业务系统开发中邮件通知是常见的功能需求。相比普通文本邮件带附件的邮件发送需要处理更复杂的MIME协议封装和编码转换。最近在开发一个合同管理系统时就遇到了需要自动发送PDF格式电子合同的需求。传统做法是调用第三方邮件服务商的API但这样会引入额外依赖。其实用Java标准库就能实现完整的带附件邮件发送功能核心只需要掌握三个技术点JavaMail API的Session配置、MIME消息体的构造、以及附件文件的编码处理。下面通过一个生产级案例详细说明实现过程和避坑要点。2. 环境准备与依赖配置2.1 必备组件说明发送邮件需要两个核心组件JavaMail API提供SMTP协议实现和邮件构造接口JAF(JavaBeans Activation Framework)处理附件类型识别和编码转换在Maven项目中添加以下依赖以Spring Boot为例dependency groupIdcom.sun.mail/groupId artifactIdjavax.mail/artifactId version1.6.2/version /dependency dependency groupIdjavax.activation/groupId artifactIdactivation/artifactId version1.1.1/version /dependency注意Java 9版本已内置JAF如果使用新版JDK可以省略activation依赖2.2 SMTP服务器配置以网易企业邮箱为例的配置参数mail.smtp.hostsmtp.qiye.163.com mail.smtp.port465 mail.smtp.ssl.enabletrue mail.smtp.authtrue mail.useryournamecompany.com mail.passwordyourpassword常见问题排查端口冲突SSL加密用465TLS加密用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建议设为30000ms30秒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; charsetutf-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 htmlbodyimg srccid:image1/body/html; // 创建HTML部分 MimeBodyPart htmlPart new MimeBodyPart(); htmlPart.setContent(html, text/html; charsetutf-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的JavaMailSenderImplBean 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, ListFile 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; charsetutf-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:1025mail.smtp.hostlocalhost mail.smtp.port1025 mail.smtp.authfalse访问http://localhost:8025查看收到的邮件8. 扩展应用场景8.1 合同管理系统典型流程生成PDF合同使用Apache PDFBox添加数字签名集成CA证书服务通过邮件发送给多方签署8.2 监控告警系统结合Prometheus AlertManager将告警信息生成HTML报告附加相关日志文件发送给值班人员8.3 报表自动分发定时任务实现每天凌晨生成Excel报表按部门拆分不同sheet分别发送给各部门负责人在最近的一个电商项目中我们通过优化附件发送逻辑将月结账单的发送时间从平均12秒缩短到3秒。关键改进点是使用BufferedInputStream预读附件对相同附件启用内存缓存采用ZIP压缩多个小文件