Skip to content
清晨的一缕阳光
返回

Spring Boot 邮件发送实战

前言

邮件发送是应用中的常见需求,如用户注册验证、密码重置、通知告警等。Spring Boot 通过 spring-boot-starter-mail 提供了便捷的邮件发送功能。

快速开始

1. 添加依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-mail</artifactId>
</dependency>

2. 基础配置

spring:
  mail:
    host: smtp.qq.com
    port: 465
    username: your-email@qq.com
    password: your-auth-code
    properties:
      mail:
        smtp:
          auth: true
          ssl:
            enable: true
        socketFactory:
          class: javax.net.ssl.SSLSocketFactory

3. 发送简单邮件

@Service
@RequiredArgsConstructor
public class EmailService {
    
    private final JavaMailSender mailSender;
    @Value("${spring.mail.username}")
    private String from;
    
    /**
     * 发送简单邮件
     */
    public void sendSimpleEmail(String to, String subject, String content) {
        SimpleMailMessage message = new SimpleMailMessage();
        message.setFrom(from);
        message.setTo(to);
        message.setSubject(subject);
        message.setText(content);
        
        mailSender.send(message);
        
        log.info("简单邮件发送成功:{}", to);
    }
    
    /**
     * 发送验证码
     */
    public void sendVerificationCode(String to, String code) {
        String subject = "验证码";
        String content = String.format(
            "您的验证码是:%s,有效期 10 分钟。",
            code
        );
        
        sendSimpleEmail(to, subject, content);
    }
}

4. 发送 HTML 邮件

@Service
@RequiredArgsConstructor
public class EmailService {
    
    private final JavaMailSender mailSender;
    private final String from;
    
    /**
     * 发送 HTML 邮件
     */
    public void sendHtmlEmail(String to, String subject, String htmlContent) {
        MimeMessage message = mailSender.createMimeMessage();
        
        try {
            MimeMessageHelper helper = new MimeMessageHelper(message, true);
            helper.setFrom(from);
            helper.setTo(to);
            helper.setSubject(subject);
            helper.setText(htmlContent, true); // true 表示 HTML
            
            mailSender.send(message);
            
            log.info("HTML 邮件发送成功:{}", to);
        } catch (MessagingException e) {
            log.error("HTML 邮件发送失败", e);
            throw new RuntimeException("邮件发送失败", e);
        }
    }
    
    /**
     * 发送欢迎邮件
     */
    public void sendWelcomeEmail(String to, String username) {
        String subject = "欢迎注册";
        
        String htmlContent = String.format("""
            <html>
            <body>
                <h1>欢迎 %s 加入我们!</h1>
                <p>感谢您注册我们的平台。</p>
                <p>如有任何问题,请随时联系客服。</p>
                <br/>
                <p>此致</p>
                <p>团队</p>
            </body>
            </html>
            """, username);
        
        sendHtmlEmail(to, subject, htmlContent);
    }
}

5. 发送带附件的邮件

@Service
@RequiredArgsConstructor
public class EmailService {
    
    /**
     * 发送带附件的邮件
     */
    public void sendAttachmentEmail(
        String to, 
        String subject, 
        String content,
        File attachment
    ) {
        MimeMessage message = mailSender.createMimeMessage();
        
        try {
            MimeMessageHelper helper = new MimeMessageHelper(message, true);
            helper.setFrom(from);
            helper.setTo(to);
            helper.setSubject(subject);
            helper.setText(content, true);
            
            // 添加附件
            helper.addAttachment(attachment.getName(), attachment);
            
            mailSender.send(message);
            
            log.info("带附件邮件发送成功:{}", to);
        } catch (MessagingException e) {
            log.error("带附件邮件发送失败", e);
            throw new RuntimeException("邮件发送失败", e);
        }
    }
    
    /**
     * 发送多个附件
     */
    public void sendMultipleAttachments(
        String to,
        String subject,
        String content,
        List<File> attachments
    ) {
        MimeMessage message = mailSender.createMimeMessage();
        
        try {
            MimeMessageHelper helper = new MimeMessageHelper(message, true);
            helper.setFrom(from);
            helper.setTo(to);
            helper.setSubject(subject);
            helper.setText(content, true);
            
            // 添加多个附件
            for (File attachment : attachments) {
                helper.addAttachment(attachment.getName(), attachment);
            }
            
            mailSender.send(message);
            
            log.info("多附件邮件发送成功:{}", to);
        } catch (MessagingException e) {
            log.error("多附件邮件发送失败", e);
            throw new RuntimeException("邮件发送失败", e);
        }
    }
}

模板邮件

1. 集成 Thymeleaf

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

2. 创建邮件模板

<!-- templates/email/verification.html -->
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>验证码</title>
    <style>
        body {
            font-family: Arial, sans-serif;
            line-height: 1.6;
            color: #333;
        }
        .container {
            max-width: 600px;
            margin: 0 auto;
            padding: 20px;
        }
        .code {
            font-size: 24px;
            font-weight: bold;
            color: #007bff;
            padding: 10px 20px;
            background: #f0f0f0;
            display: inline-block;
            margin: 20px 0;
        }
        .footer {
            margin-top: 30px;
            padding-top: 20px;
            border-top: 1px solid #ddd;
            font-size: 12px;
            color: #999;
        }
    </style>
</head>
<body>
    <div class="container">
        <h2>验证码</h2>
        <p>您好 <span th:text="${username}">用户</span></p>
        <p>您的验证码是:</p>
        <div class="code" th:text="${code}">123456</div>
        <p>验证码有效期 10 分钟,请尽快使用。</p>
        <p>如果不是您本人操作,请忽略此邮件。</p>
        <div class="footer">
            <p>此邮件由系统自动发送,请勿回复。</p>
            <p>&copy; 2024 Demo Company. All rights reserved.</p>
        </div>
    </div>
</body>
</html>

3. 发送模板邮件

@Service
@RequiredArgsConstructor
public class TemplateEmailService {
    
    private final JavaMailSender mailSender;
    private final TemplateEngine templateEngine;
    private final String from;
    
    /**
     * 发送模板邮件
     */
    public void sendTemplateEmail(
        String to, 
        String subject, 
        String templateName,
        Map<String, Object> variables
    ) {
        try {
            // 渲染模板
            Context context = new Context();
            variables.forEach(context::setVariable);
            
            String htmlContent = templateEngine.process(templateName, context);
            
            // 发送邮件
            MimeMessage message = mailSender.createMimeMessage();
            MimeMessageHelper helper = new MimeMessageHelper(message, true);
            
            helper.setFrom(from);
            helper.setTo(to);
            helper.setSubject(subject);
            helper.setText(htmlContent, true);
            
            mailSender.send(message);
            
            log.info("模板邮件发送成功:{}", to);
        } catch (MessagingException | RuntimeException e) {
            log.error("模板邮件发送失败", e);
            throw new RuntimeException("邮件发送失败", e);
        }
    }
    
    /**
     * 发送验证码邮件
     */
    public void sendVerificationCodeEmail(String to, String username, String code) {
        Map<String, Object> variables = new HashMap<>();
        variables.put("username", username);
        variables.put("code", code);
        
        sendTemplateEmail(to, "验证码", "email/verification", variables);
    }
    
    /**
     * 发送订单确认邮件
     */
    public void sendOrderConfirmationEmail(String to, Order order) {
        Map<String, Object> variables = new HashMap<>();
        variables.put("order", order);
        variables.put("username", order.getUsername());
        
        sendTemplateEmail(
            to, 
            "订单确认", 
            "email/order-confirmation", 
            variables
        );
    }
}

批量邮件

1. 批量发送

@Service
@RequiredArgsConstructor
public class BatchEmailService {
    
    private final JavaMailSender mailSender;
    private final String from;
    
    /**
     * 批量发送简单邮件
     */
    public void sendBatchEmails(
        List<String> recipients,
        String subject,
        String content
    ) {
        for (String to : recipients) {
            try {
                sendSimpleEmail(to, subject, content);
            } catch (Exception e) {
                log.error("发送邮件失败:{}", to, e);
            }
        }
    }
    
    /**
     * 异步批量发送
     */
    @Async
    public void sendBatchEmailsAsync(
        List<String> recipients,
        String subject,
        String content
    ) {
        CountDownLatch latch = new CountDownLatch(recipients.size());
        
        for (String to : recipients) {
            CompletableFuture.runAsync(() -> {
                try {
                    sendSimpleEmail(to, subject, content);
                } catch (Exception e) {
                    log.error("发送邮件失败:{}", to, e);
                } finally {
                    latch.countDown();
                }
            });
        }
        
        try {
            latch.await();
            log.info("批量邮件发送完成");
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }
}

2. 限流发送

@Service
@RequiredArgsConstructor
public class RateLimitEmailService {
    
    private final JavaMailSender mailSender;
    private final RateLimiter rateLimiter = RateLimiter.create(10); // 每秒 10 封
    
    public void sendWithRateLimit(String to, String subject, String content) {
        // 获取许可(限流)
        rateLimiter.acquire();
        
        sendSimpleEmail(to, subject, content);
    }
}

最佳实践

1. 邮件队列

@Component
public class EmailQueueConsumer {
    
    @Autowired
    private EmailService emailService;
    
    @RabbitListener(queues = "email.queue")
    public void consume(EmailMessage emailMessage) {
        try {
            emailService.sendSimpleEmail(
                emailMessage.getTo(),
                emailMessage.getSubject(),
                emailMessage.getContent()
            );
        } catch (Exception e) {
            log.error("邮件发送失败", e);
            
            // 重试或记录失败
            handleFailure(emailMessage, e);
        }
    }
}

2. 错误重试

@Service
@RequiredArgsConstructor
public class RetryEmailService {
    
    private final JavaMailSender mailSender;
    
    @Retryable(
        value = {MailSendException.class},
        maxAttempts = 3,
        backoff = @Backoff(delay = 1000)
    )
    public void sendWithRetry(String to, String subject, String content) {
        sendSimpleEmail(to, subject, content);
    }
    
    @Recover
    public void recover(MailSendException e, String to, String subject, String content) {
        log.error("邮件发送最终失败:{}", to, e);
        
        // 记录到失败队列
        failedEmailRepository.save(new FailedEmail(to, subject, content, e.getMessage()));
    }
}

3. 邮件追踪

@Service
public class TrackingEmailService {
    
    @Autowired
    private JavaMailSender mailSender;
    
    public void sendTrackedEmail(String to, String subject, String content) {
        MimeMessage message = mailSender.createMimeMessage();
        
        try {
            MimeMessageHelper helper = new MimeMessageHelper(message, true);
            helper.setFrom(from);
            helper.setTo(to);
            helper.setSubject(subject);
            helper.setText(content, true);
            
            // 添加追踪头
            message.setHeader("X-Mailer", "Demo-Mailer");
            message.setHeader("X-Priority", "3");
            
            // 记录发送日志
            emailLogRepository.save(new EmailLog(to, subject, LocalDateTime.now()));
            
            mailSender.send(message);
            
            log.info("追踪邮件发送成功:{}", to);
        } catch (MessagingException e) {
            log.error("追踪邮件发送失败", e);
            throw new RuntimeException("邮件发送失败", e);
        }
    }
}

4. 配置优化

spring:
  mail:
    host: smtp.qq.com
    port: 465
    username: your-email@qq.com
    password: your-auth-code
    
    # 连接池配置
    properties:
      mail:
        smtp:
          auth: true
          ssl:
            enable: true
          connectiontimeout: 5000
          timeout: 3000
          writetimeout: 5000
        
        # 连接池
        smtps:
          connectionpool:
            enable: true
            timeout: 300000
            idleTimeout: 60000

5. 多邮箱服务商

@Configuration
public class MultiMailConfig {
    
    @Bean
    @Primary
    @ConfigurationProperties("spring.mail")
    public JavaMailSender primaryMailSender() {
        return new JavaMailSenderImpl();
    }
    
    @Bean
    @ConfigurationProperties("spring.mail.backup")
    public JavaMailSender backupMailSender() {
        return new JavaMailSenderImpl();
    }
}
spring:
  mail:
    host: smtp.qq.com
    port: 465
    username: primary@qq.com
    password: xxx
  
  mail:
    backup:
      host: smtp.163.com
      port: 465
      username: backup@163.com
      password: xxx

总结

邮件发送要点:

邮件是用户触达的重要渠道。


分享这篇文章到:

上一篇文章
Spring Boot 统一异常处理
下一篇文章
Spring Boot Docker 容器化部署