Java模板异步邮件发送

高效、灵活的邮件发送解决方案

📖 概述

Java模板异步邮件发送是一种高效的邮件处理方案,它结合了以下几个关键技术:

🎨 模板引擎

使用Thymeleaf或Freemarker等模板引擎,支持动态内容生成,提供丰富的邮件样式。

⚡ 异步处理

采用异步发送机制,避免阻塞主线程,提高系统响应速度和用户体验。

📧 JavaMail API

基于标准的JavaMail API,支持多种邮件协议,兼容性强。

🔧 Spring Boot集成

与Spring Boot无缝集成,配置简单,支持自动配置和依赖注入。

🔄 发送流程

1
准备邮件数据和模板参数
2
模板引擎渲染邮件内容
3
异步队列接收邮件任务
4
后台线程处理邮件发送
5
记录发送结果和异常处理

📦 依赖配置

Maven依赖

在pom.xml中添加以下依赖:

<dependencies>
    <!-- Spring Boot邮件启动器 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-mail</artifactId>
    </dependency>
    
    <!-- Thymeleaf模板引擎 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-thymeleaf</artifactId>
    </dependency>
    
    <!-- 异步支持 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    
    <!-- 可选:Redis支持(用于邮件队列) -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>
</dependencies>

⚙️ 邮件配置

application.yml配置

spring:
  # 邮件配置
  mail:
    host: smtp.gmail.com
    port: 587
    username: your-email@gmail.com
    password: your-app-password
    properties:
      mail:
        smtp:
          auth: true
          starttls:
            enable: true
            required: true
  
  # 模板配置
  thymeleaf:
    prefix: classpath:/templates/
    suffix: .html
    mode: HTML
    encoding: UTF-8
    cache: false
    
  # 异步配置
  task:
    execution:
      pool:
        core-size: 5
        max-size: 20
        queue-capacity: 200
      thread-name-prefix: mail-task-

Java配置类

@Configuration
@EnableAsync
public class MailConfig {
    
    @Bean
    public TaskExecutor taskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(5);
        executor.setMaxPoolSize(20);
        executor.setQueueCapacity(200);
        executor.setThreadNamePrefix("mail-task-");
        executor.initialize();
        return executor;
    }
    
    @Bean
    public TemplateEngine templateEngine() {
        SpringTemplateEngine templateEngine = new SpringTemplateEngine();
        templateEngine.setTemplateResolver(templateResolver());
        return templateEngine;
    }
    
    @Bean
    public ITemplateResolver templateResolver() {
        ClassLoaderTemplateResolver resolver = new ClassLoaderTemplateResolver();
        resolver.setPrefix("templates/");
        resolver.setSuffix(".html");
        resolver.setTemplateMode(TemplateMode.HTML);
        resolver.setCharacterEncoding("UTF-8");
        resolver.setCacheable(false);
        return resolver;
    }
}

📝 邮件模板

创建邮件模板

src/main/resources/templates/ 目录下创建邮件模板:

欢迎邮件模板 (welcome.html)

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>欢迎加入我们</title>
    <style>
        .email-container {
            max-width: 600px;
            margin: 0 auto;
            font-family: Arial, sans-serif;
        }
        .header {
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            color: white;
            padding: 30px;
            text-align: center;
        }
        .content {
            padding: 30px;
            background: #ffffff;
        }
        .button {
            display: inline-block;
            padding: 12px 30px;
            background: #667eea;
            color: white;
            text-decoration: none;
            border-radius: 5px;
            margin: 20px 0;
        }
        .footer {
            background: #f8f9fa;
            padding: 20px;
            text-align: center;
            color: #666;
        }
    </style>
</head>
<body>
    <div class="email-container">
        <div class="header">
            <h1>欢迎加入我们!</h1>
        </div>
        
        <div class="content">
            <h2>亲爱的 <span th:text="${username}">用户</span>,</h2>
            <p>感谢您注册我们的服务!您的账户已经成功创建。</p>
            
            <div th:if="${activationRequired}">
                <p>请点击下面的按钮激活您的账户:</p>
                <a th:href="${activationLink}" class="button">激活账户</a>
            </div>
            
            <div th:if="${!activationRequired}">
                <p>您现在可以开始使用我们的服务了!</p>
                <a th:href="${loginLink}" class="button">立即登录</a>
            </div>
            
            <h3>账户信息:</h3>
            <ul>
                <li>邮箱:<span th:text="${email}">email@example.com</span></li>
                <li>注册时间:<span th:text="${#dates.format(registerTime, 'yyyy-MM-dd HH:mm')}">2024-01-01 12:00</span></li>
            </ul>
        </div>
        
        <div class="footer">
            <p>如果您有任何问题,请联系我们的客服团队。</p>
            <p>© 2024 我们的公司. 保留所有权利。</p>
        </div>
    </div>
</body>
</html>

🎯 邮件服务

邮件服务接口

public interface MailService {
    
    /**
     * 发送简单文本邮件
     */
    void sendSimpleMail(String to, String subject, String content);
    
    /**
     * 发送HTML邮件
     */
    void sendHtmlMail(String to, String subject, String content);
    
    /**
     * 发送模板邮件
     */
    void sendTemplateMail(String to, String subject, String templateName, Map<String, Object> variables);
    
    /**
     * 异步发送模板邮件
     */
    CompletableFuture<Boolean> sendTemplateMailAsync(String to, String subject, String templateName, Map<String, Object> variables);
    
    /**
     * 批量发送邮件
     */
    void sendBatchMail(List<String> toList, String subject, String templateName, Map<String, Object> variables);
}

邮件服务实现

@Service
@Slf4j
public class MailServiceImpl implements MailService {
    
    @Autowired
    private JavaMailSender mailSender;
    
    @Autowired
    private TemplateEngine templateEngine;
    
    @Value("${spring.mail.username}")
    private String from;
    
    @Override
    public void sendSimpleMail(String to, String subject, String content) {
        try {
            SimpleMailMessage message = new SimpleMailMessage();
            message.setFrom(from);
            message.setTo(to);
            message.setSubject(subject);
            message.setText(content);
            mailSender.send(message);
            log.info("简单邮件发送成功:{}", to);
        } catch (Exception e) {
            log.error("简单邮件发送失败:{}", e.getMessage());
            throw new RuntimeException("邮件发送失败", e);
        }
    }
    
    @Override
    public void sendHtmlMail(String to, String subject, String content) {
        try {
            MimeMessage message = mailSender.createMimeMessage();
            MimeMessageHelper helper = new MimeMessageHelper(message, true, "UTF-8");
            helper.setFrom(from);
            helper.setTo(to);
            helper.setSubject(subject);
            helper.setText(content, true);
            mailSender.send(message);
            log.info("HTML邮件发送成功:{}", to);
        } catch (Exception e) {
            log.error("HTML邮件发送失败:{}", e.getMessage());
            throw new RuntimeException("邮件发送失败", e);
        }
    }
    
    @Override
    public void sendTemplateMail(String to, String subject, String templateName, Map<String, Object> variables) {
        try {
            Context context = new Context();
            context.setVariables(variables);
            String content = templateEngine.process(templateName, context);
            sendHtmlMail(to, subject, content);
            log.info("模板邮件发送成功:{}", to);
        } catch (Exception e) {
            log.error("模板邮件发送失败:{}", e.getMessage());
            throw new RuntimeException("邮件发送失败", e);
        }
    }
    
    @Override
    @Async
    public CompletableFuture<Boolean> sendTemplateMailAsync(String to, String subject, String templateName, Map<String, Object> variables) {
        try {
            sendTemplateMail(to, subject, templateName, variables);
            return CompletableFuture.completedFuture(true);
        } catch (Exception e) {
            log.error("异步邮件发送失败:{}", e.getMessage());
            return CompletableFuture.completedFuture(false);
        }
    }
    
    @Override
    @Async
    public void sendBatchMail(List<String> toList, String subject, String templateName, Map<String, Object> variables) {
        toList.parallelStream().forEach(to -> {
            try {
                sendTemplateMail(to, subject, templateName, variables);
            } catch (Exception e) {
                log.error("批量邮件发送失败,收件人:{},错误:{}", to, e.getMessage());
            }
        });
    }
}

⚡ 异步处理

异步配置

@Configuration
@EnableAsync
public class AsyncConfig implements AsyncConfigurer {
    
    @Override
    public Executor getAsyncExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(5);
        executor.setMaxPoolSize(20);
        executor.setQueueCapacity(200);
        executor.setThreadNamePrefix("async-mail-");
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        executor.initialize();
        return executor;
    }
    
    @Override
    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
        return (throwable, method, objects) -> {
            log.error("异步执行异常:", throwable);
            log.error("异步方法:{}", method.getName());
            log.error("异步参数:{}", Arrays.toString(objects));
        };
    }
}

邮件队列服务

@Service
@Slf4j
public class MailQueueService {
    
    @Autowired
    private MailService mailService;
    
    @Autowired
    private StringRedisTemplate redisTemplate;
    
    private static final String MAIL_QUEUE_KEY = "mail:queue";
    
    /**
     * 将邮件任务加入队列
     */
    public void pushToQueue(MailTask mailTask) {
        try {
            String taskJson = JSON.toJSONString(mailTask);
            redisTemplate.opsForList().leftPush(MAIL_QUEUE_KEY, taskJson);
            log.info("邮件任务已加入队列:{}", mailTask.getTo());
        } catch (Exception e) {
            log.error("邮件任务加入队列失败:{}", e.getMessage());
        }
    }
    
    /**
     * 从队列中消费邮件任务
     */
    @Scheduled(fixedDelay = 1000)
    public void consumeMailQueue() {
        try {
            String taskJson = redisTemplate.opsForList().rightPop(MAIL_QUEUE_KEY);
            if (taskJson != null) {
                MailTask mailTask = JSON.parseObject(taskJson, MailTask.class);
                processMailTask(mailTask);
            }
        } catch (Exception e) {
            log.error("消费邮件队列失败:{}", e.getMessage());
        }
    }
    
    @Async
    private void processMailTask(MailTask mailTask) {
        try {
            mailService.sendTemplateMail(
                mailTask.getTo(),
                mailTask.getSubject(),
                mailTask.getTemplateName(),
                mailTask.getVariables()
            );
            log.info("邮件任务处理成功:{}", mailTask.getTo());
        } catch (Exception e) {
            log.error("邮件任务处理失败:{}", e.getMessage());
            // 可以实现重试机制
            retryMailTask(mailTask);
        }
    }
    
    private void retryMailTask(MailTask mailTask) {
        if (mailTask.getRetryCount() < 3) {
            mailTask.setRetryCount(mailTask.getRetryCount() + 1);
            pushToQueue(mailTask);
            log.info("邮件任务重试:{},次数:{}", mailTask.getTo(), mailTask.getRetryCount());
        } else {
            log.error("邮件任务重试次数超限:{}", mailTask.getTo());
        }
    }
}

🚀 使用示例

控制器示例

@RestController
@RequestMapping("/api/mail")
@Slf4j
public class MailController {
    
    @Autowired
    private MailService mailService;
    
    @Autowired
    private MailQueueService mailQueueService;
    
    /**
     * 发送欢迎邮件
     */
    @PostMapping("/welcome")
    public ResponseEntity<String> sendWelcomeMail(@RequestBody WelcomeMailRequest request) {
        try {
            Map<String, Object> variables = new HashMap<>();
            variables.put("username", request.getUsername());
            variables.put("email", request.getEmail());
            variables.put("registerTime", new Date());
            variables.put("activationRequired", request.isActivationRequired());
            variables.put("activationLink", request.getActivationLink());
            variables.put("loginLink", request.getLoginLink());
            
            // 异步发送
            CompletableFuture<Boolean> future = mailService.sendTemplateMailAsync(
                request.getEmail(),
                "欢迎加入我们!",
                "welcome",
                variables
            );
            
            return ResponseEntity.ok("邮件发送任务已提交");
        } catch (Exception e) {
            log.error("发送欢迎邮件失败:{}", e.getMessage());
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                .body("邮件发送失败");
        }
    }
    
    /**
     * 批量发送邮件
     */
    @PostMapping("/batch")
    public ResponseEntity<String> sendBatchMail(@RequestBody BatchMailRequest request) {
        try {
            Map<String, Object> variables = new HashMap<>();
            variables.put("content", request.getContent());
            variables.put("sendTime", new Date());
            
            mailService.sendBatchMail(
                request.getToList(),
                request.getSubject(),
                request.getTemplateName(),
                variables
            );
            
            return ResponseEntity.ok("批量邮件发送任务已提交");
        } catch (Exception e) {
            log.error("批量发送邮件失败:{}", e.getMessage());
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                .body("批量邮件发送失败");
        }
    }
    
    /**
     * 使用队列发送邮件
     */
    @PostMapping("/queue")
    public ResponseEntity<String> sendMailWithQueue(@RequestBody MailTask mailTask) {
        try {
            mailQueueService.pushToQueue(mailTask);
            return ResponseEntity.ok("邮件已加入发送队列");
        } catch (Exception e) {
            log.error("邮件加入队列失败:{}", e.getMessage());
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                .body("邮件队列处理失败");
        }
    }
}

数据传输对象

@Data
public class WelcomeMailRequest {
    private String username;
    private String email;
    private boolean activationRequired;
    private String activationLink;
    private String loginLink;
}

@Data
public class BatchMailRequest {
    private List<String> toList;
    private String subject;
    private String templateName;
    private String content;
}

@Data
public class MailTask {
    private String to;
    private String subject;
    private String templateName;
    private Map<String, Object> variables;
    private int retryCount = 0;
    private Date createTime = new Date();
}

💡 最佳实践

🔧 配置管理

  • 使用配置文件管理邮件服务器设置
  • 支持多环境配置(开发、测试、生产)
  • 敏感信息使用环境变量或配置中心
  • 支持动态配置更新

📈 性能优化

  • 使用连接池管理邮件连接
  • 合理设置线程池大小
  • 批量发送时控制并发数
  • 模板缓存提高渲染性能

🛡️ 安全考虑

  • 使用应用密码而非账户密码
  • 启用SSL/TLS加密传输
  • 验证邮件地址格式
  • 防止邮件注入攻击

📊 监控告警

  • 记录邮件发送成功率
  • 监控队列长度和处理速度
  • 设置异常告警机制
  • 定期清理失效任务

💎 核心建议

  • 异步处理:邮件发送应该异步进行,避免阻塞用户操作
  • 失败重试:实现合理的重试机制,处理网络波动和临时故障
  • 限流控制:避免短时间内大量邮件发送被邮件服务商限制
  • 模板管理:统一管理邮件模板,支持版本控制和预览功能

🔧 高级功能

邮件监控服务

@Component
@Slf4j
public class MailMonitorService {
    
    @Autowired
    private StringRedisTemplate redisTemplate;
    
    private static final String MAIL_STATS_KEY = "mail:stats";
    private static final String MAIL_FAILED_KEY = "mail:failed";
    
    /**
     * 记录邮件发送统计
     */
    public void recordMailStats(String type, boolean success) {
        try {
            String date = LocalDate.now().toString();
            String key = MAIL_STATS_KEY + ":" + date;
            
            if (success) {
                redisTemplate.opsForHash().increment(key, type + ":success", 1);
            } else {
                redisTemplate.opsForHash().increment(key, type + ":failed", 1);
                // 记录失败详情
                recordFailedMail(type, new Date());
            }
            
            // 设置过期时间30天
            redisTemplate.expire(key, Duration.ofDays(30));
        } catch (Exception e) {
            log.error("记录邮件统计失败:{}", e.getMessage());
        }
    }
    
    /**
     * 记录失败的邮件
     */
    private void recordFailedMail(String type, Date failTime) {
        try {
            Map<String, Object> failedInfo = new HashMap<>();
            failedInfo.put("type", type);
            failedInfo.put("failTime", failTime);
            failedInfo.put("timestamp", System.currentTimeMillis());
            
            redisTemplate.opsForList().leftPush(MAIL_FAILED_KEY, JSON.toJSONString(failedInfo));
            
            // 只保留最近1000条失败记录
            redisTemplate.opsForList().trim(MAIL_FAILED_KEY, 0, 999);
        } catch (Exception e) {
            log.error("记录失败邮件失败:{}", e.getMessage());
        }
    }
    
    /**
     * 获取邮件发送统计
     */
    public Map<String, Object> getMailStats(String date) {
        try {
            String key = MAIL_STATS_KEY + ":" + date;
            return redisTemplate.opsForHash().entries(key);
        } catch (Exception e) {
            log.error("获取邮件统计失败:{}", e.getMessage());
            return new HashMap<>();
        }
    }
    
    /**
     * 健康检查
     */
    @Scheduled(fixedRate = 300000) // 5分钟检查一次
    public void healthCheck() {
        try {
            // 检查队列长度
            Long queueSize = redisTemplate.opsForList().size("mail:queue");
            if (queueSize != null && queueSize > 1000) {
                log.warn("邮件队列积压严重,当前长度:{}", queueSize);
                // 这里可以发送告警
            }
            
            // 检查最近的失败率
            Map<String, Object> todayStats = getMailStats(LocalDate.now().toString());
            if (!todayStats.isEmpty()) {
                calculateAndLogFailureRate(todayStats);
            }
        } catch (Exception e) {
            log.error("邮件服务健康检查失败:{}", e.getMessage());
        }
    }
    
    private void calculateAndLogFailureRate(Map<String, Object> stats) {
        int totalSuccess = 0;
        int totalFailed = 0;
        
        for (Map.Entry<String, Object> entry : stats.entrySet()) {
            String key = entry.getKey();
            int value = Integer.parseInt(entry.getValue().toString());
            
            if (key.endsWith(":success")) {
                totalSuccess += value;
            } else if (key.endsWith(":failed")) {
                totalFailed += value;
            }
        }
        
        if (totalSuccess + totalFailed > 0) {
            double failureRate = (double) totalFailed / (totalSuccess + totalFailed) * 100;
            log.info("今日邮件发送统计 - 成功:{},失败:{},失败率:{:.2f}%", 
                totalSuccess, totalFailed, failureRate);
            
            if (failureRate > 10) { // 失败率超过10%告警
                log.warn("邮件失败率过高:{:.2f}%", failureRate);
            }
        }
    }
}

邮件模板管理器

@Component
@Slf4j
public class MailTemplateManager {
    
    @Autowired
    private TemplateEngine templateEngine;
    
    @Autowired
    private StringRedisTemplate redisTemplate;
    
    private static final String TEMPLATE_CACHE_KEY = "mail:template:cache:";
    
    /**
     * 渲染邮件模板
     */
    public String renderTemplate(String templateName, Map<String, Object> variables) {
        try {
            // 先从缓存获取
            String cachedTemplate = getCachedTemplate(templateName);
            if (cachedTemplate != null) {
                return processTemplate(cachedTemplate, variables);
            }
            
            // 渲染模板
            Context context = new Context();
            context.setVariables(variables);
            String content = templateEngine.process(templateName, context);
            
            // 缓存模板(仅在生产环境)
            if (isProductionEnvironment()) {
                cacheTemplate(templateName, content);
            }
            
            return content;
        } catch (Exception e) {
            log.error("渲染邮件模板失败:{}", e.getMessage());
            throw new RuntimeException("模板渲染失败", e);
        }
    }
    
    /**
     * 预览邮件模板
     */
    public String previewTemplate(String templateName, Map<String, Object> variables) {
        try {
            Context context = new Context();
            context.setVariables(variables);
            return templateEngine.process(templateName, context);
        } catch (Exception e) {
            log.error("预览邮件模板失败:{}", e.getMessage());
            throw new RuntimeException("模板预览失败", e);
        }
    }
    
    /**
     * 验证模板语法
     */
    public boolean validateTemplate(String templateName) {
        try {
            Map<String, Object> testVariables = createTestVariables();
            renderTemplate(templateName, testVariables);
            return true;
        } catch (Exception e) {
            log.error("模板验证失败:{}", e.getMessage());
            return false;
        }
    }
    
    private Map<String, Object> createTestVariables() {
        Map<String, Object> variables = new HashMap<>();
        variables.put("username", "测试用户");
        variables.put("email", "test@example.com");
        variables.put("registerTime", new Date());
        variables.put("activationRequired", true);
        variables.put("activationLink", "http://example.com/activate");
        variables.put("loginLink", "http://example.com/login");
        return variables;
    }
    
    private String getCachedTemplate(String templateName) {
        try {
            return redisTemplate.opsForValue().get(TEMPLATE_CACHE_KEY + templateName);
        } catch (Exception e) {
            log.warn("获取模板缓存失败:{}", e.getMessage());
            return null;
        }
    }
    
    private void cacheTemplate(String templateName, String content) {
        try {
            redisTemplate.opsForValue().set(
                TEMPLATE_CACHE_KEY + templateName, 
                content, 
                Duration.ofHours(1)
            );
        } catch (Exception e) {
            log.warn("缓存模板失败:{}", e.getMessage());
        }
    }
    
    private String processTemplate(String cachedTemplate, Map<String, Object> variables) {
        // 这里可以实现简单的变量替换逻辑
        // 或者使用其他轻量级模板引擎
        return cachedTemplate;
    }
    
    private boolean isProductionEnvironment() {
        // 判断是否为生产环境
        return "production".equals(System.getProperty("spring.profiles.active"));
    }
}

邮件发送限流器

@Component
@Slf4j
public class MailRateLimiter {
    
    @Autowired
    private StringRedisTemplate redisTemplate;
    
    private static final String RATE_LIMIT_KEY = "mail:rate:limit:";
    
    /**
     * 检查是否可以发送邮件
     */
    public boolean canSendMail(String identifier, int maxCount, Duration duration) {
        try {
            String key = RATE_LIMIT_KEY + identifier;
            String countStr = redisTemplate.opsForValue().get(key);
            
            if (countStr == null) {
                // 第一次发送
                redisTemplate.opsForValue().set(key, "1", duration);
                return true;
            }
            
            int currentCount = Integer.parseInt(countStr);
            if (currentCount >= maxCount) {
                log.warn("邮件发送频率超限:{},当前次数:{}", identifier, currentCount);
                return false;
            }
            
            // 增加计数
            redisTemplate.opsForValue().increment(key);
            return true;
        } catch (Exception e) {
            log.error("检查邮件发送限制失败:{}", e.getMessage());
            return true; // 异常情况下允许发送
        }
    }
    
    /**
     * 全局限流检查
     */
    public boolean canSendGlobalMail() {
        return canSendMail("global", 1000, Duration.ofMinutes(1)); // 每分钟最多1000封
    }
    
    /**
     * 用户限流检查
     */
    public boolean canSendUserMail(String userEmail) {
        return canSendMail("user:" + userEmail, 10, Duration.ofMinutes(10)); // 每10分钟最多10封
    }
    
    /**
     * IP限流检查
     */
    public boolean canSendIpMail(String ipAddress) {
        return canSendMail("ip:" + ipAddress, 50, Duration.ofMinutes(5)); // 每5分钟最多50封
    }
}

🧪 测试用例

@SpringBootTest
@TestPropertySource(properties = {
    "spring.mail.host=smtp.gmail.com",
    "spring.mail.port=587",
    "spring.mail.username=test@gmail.com",
    "spring.mail.password=testpassword"
})
class MailServiceTest {
    
    @Autowired
    private MailService mailService;
    
    @MockBean
    private JavaMailSender mailSender;
    
    @Test
    void testSendSimpleMail() {
        // Given
        String to = "recipient@example.com";
        String subject = "测试邮件";
        String content = "这是一封测试邮件";
        
        // When
        mailService.sendSimpleMail(to, subject, content);
        
        // Then
        verify(mailSender, times(1)).send(any(SimpleMailMessage.class));
    }
    
    @Test
    void testSendTemplateMail() {
        // Given
        String to = "recipient@example.com";
        String subject = "欢迎邮件";
        String templateName = "welcome";
        Map<String, Object> variables = new HashMap<>();
        variables.put("username", "测试用户");
        variables.put("email", to);
        
        // When
        mailService.sendTemplateMail(to, subject, templateName, variables);
        
        // Then
        verify(mailSender, times(1)).send(any(MimeMessage.class));
    }
    
    @Test
    void testSendTemplateMailAsync() throws Exception {
        // Given
        String to = "recipient@example.com";
        String subject = "异步邮件";
        String templateName = "welcome";
        Map<String, Object> variables = new HashMap<>();
        variables.put("username", "测试用户");
        
        // When
        CompletableFuture<Boolean> future = mailService.sendTemplateMailAsync(to, subject, templateName, variables);
        Boolean result = future.get(5, TimeUnit.SECONDS);
        
        // Then
        assertTrue(result);
        verify(mailSender, times(1)).send(any(MimeMessage.class));
    }
}

🔍 故障排查

🚨 常见问题

  • 认证失败:检查邮箱账号密码,确保开启了SMTP服务
  • 连接超时:检查网络连接,确认SMTP服务器地址和端口
  • 发送频率限制:检查邮件服务商的发送限制
  • 模板渲染错误:检查模板语法和变量名

🔧 调试技巧

  • 开启JavaMail调试日志:mail.debug=true
  • 使用邮件测试工具验证SMTP配置
  • 检查防火墙和代理设置
  • 监控邮件队列和线程池状态

📋 日志配置

  • 记录邮件发送的详细日志
  • 分级记录不同类型的异常
  • 使用结构化日志便于分析
  • 定期清理过期日志文件

⚠️ 注意事项

  • 避免在邮件内容中包含敏感信息
  • 合理设置邮件重试次数和间隔
  • 定期更新邮件模板和配置
  • 监控邮件服务的健康状态

📚 总结

Java模板异步邮件发送是现代Web应用中不可或缺的功能。通过合理的架构设计和实现,我们可以构建一个高效、可靠、易维护的邮件服务系统。

🎯 核心要点回顾

  • 模板化:使用模板引擎提供灵活的邮件内容定制
  • 异步处理:避免阻塞用户操作,提高系统响应性
  • 队列管理:使用消息队列处理大量邮件发送任务
  • 监控告警:实时监控邮件发送状态和系统健康
  • 安全可靠:确保邮件发送的安全性和可靠性

希望这个教程能帮助你快速构建一个完整的邮件发送系统!