Spring Boot使用邮箱模板发送邮件完整指南

在现代Web应用开发中,邮件发送功能是必不可少的一部分。无论是用户注册验证、密码重置,还是营销推广,邮件都扮演着重要角色。本文将详细介绍如何在Spring Boot项目中使用邮箱模板发送精美的邮件。

前言

Spring Boot为邮件发送提供了强大的支持,通过集成Spring Mail和模板引擎(如Thymeleaf),我们可以轻松实现发送HTML格式的模板邮件。这种方式不仅让邮件内容更加美观,还能提高用户体验。

环境准备

1. 添加依赖

首先在pom.xml中添加必要的依赖:

<dependencies>
    <!-- Spring Boot Starter Mail -->
    <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>
    
    <!-- Spring Boot Web Starter -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
</dependencies>

2. 邮箱配置

application.ymlapplication.properties中配置邮箱信息:

spring:
  mail:
    host: smtp.qq.com  # 邮箱服务器地址
    port: 587          # 端口号
    username: your-email@qq.com  # 发送方邮箱
    password: your-auth-code     # 授权码(不是邮箱密码)
    protocol: smtp
    properties:
      mail:
        smtp:
          auth: true
          starttls:
            enable: true
            required: true
  
  # Thymeleaf配置
  thymeleaf:
    prefix: classpath:/templates/
    suffix: .html
    mode: HTML
    encoding: UTF-8
    cache: false

注意:

  • QQ邮箱需要开启SMTP服务并获取授权码
  • 不同邮箱服务商的配置可能有所不同

核心代码实现

1. 邮件服务接口

public interface EmailService {
    /**
     * 发送简单文本邮件
     */
    void sendSimpleEmail(String to, String subject, String text);
    
    /**
     * 发送HTML模板邮件
     */
    void sendTemplateEmail(String to, String subject, String templateName, Map<String, Object> variables);
    
    /**
     * 发送带附件的邮件
     */
    void sendEmailWithAttachment(String to, String subject, String templateName, 
                                Map<String, Object> variables, String attachmentPath);
}

2. 邮件服务实现类

@Service
@Slf4j
public class EmailServiceImpl implements EmailService {
    
    @Autowired
    private JavaMailSender mailSender;
    
    @Autowired
    private TemplateEngine templateEngine;
    
    @Value("${spring.mail.username}")
    private String from;
    
    @Override
    public void sendSimpleEmail(String to, String subject, String text) {
        try {
            SimpleMailMessage message = new SimpleMailMessage();
            message.setFrom(from);
            message.setTo(to);
            message.setSubject(subject);
            message.setText(text);
            
            mailSender.send(message);
            log.info("简单邮件发送成功,收件人:{}", to);
        } catch (Exception e) {
            log.error("简单邮件发送失败,收件人:{},错误信息:{}", to, e.getMessage());
            throw new RuntimeException("邮件发送失败", e);
        }
    }
    
    @Override
    public void sendTemplateEmail(String to, String subject, String templateName, Map<String, Object> variables) {
        try {
            // 使用Thymeleaf渲染模板
            Context context = new Context();
            context.setVariables(variables);
            String htmlContent = templateEngine.process(templateName, context);
            
            // 创建邮件消息
            MimeMessage message = mailSender.createMimeMessage();
            MimeMessageHelper helper = new MimeMessageHelper(message, true, "UTF-8");
            
            helper.setFrom(from);
            helper.setTo(to);
            helper.setSubject(subject);
            helper.setText(htmlContent, true); // true表示HTML格式
            
            mailSender.send(message);
            log.info("模板邮件发送成功,收件人:{},模板:{}", to, templateName);
        } catch (Exception e) {
            log.error("模板邮件发送失败,收件人:{},错误信息:{}", to, e.getMessage());
            throw new RuntimeException("邮件发送失败", e);
        }
    }
    
    @Override
    public void sendEmailWithAttachment(String to, String subject, String templateName, 
                                       Map<String, Object> variables, String attachmentPath) {
        try {
            Context context = new Context();
            context.setVariables(variables);
            String htmlContent = templateEngine.process(templateName, context);
            
            MimeMessage message = mailSender.createMimeMessage();
            MimeMessageHelper helper = new MimeMessageHelper(message, true, "UTF-8");
            
            helper.setFrom(from);
            helper.setTo(to);
            helper.setSubject(subject);
            helper.setText(htmlContent, true);
            
            // 添加附件
            File file = new File(attachmentPath);
            if (file.exists()) {
                helper.addAttachment(file.getName(), file);
            }
            
            mailSender.send(message);
            log.info("带附件邮件发送成功,收件人:{}", to);
        } catch (Exception e) {
            log.error("带附件邮件发送失败,收件人:{},错误信息:{}", to, e.getMessage());
            throw new RuntimeException("邮件发送失败", e);
        }
    }
}

邮件模板设计

1. 用户注册验证邮件模板

src/main/resources/templates目录下创建register-verify.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>账户验证</title>
    <style>
        body {
            font-family: 'Arial', sans-serif;
            line-height: 1.6;
            margin: 0;
            padding: 0;
            background-color: #f4f4f4;
        }
        .container {
            max-width: 600px;
            margin: 20px auto;
            background: white;
            border-radius: 10px;
            box-shadow: 0 0 20px rgba(0,0,0,0.1);
            overflow: hidden;
        }
        .header {
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            color: white;
            padding: 30px;
            text-align: center;
        }
        .content {
            padding: 40px 30px;
        }
        .btn {
            display: inline-block;
            background: #667eea;
            color: white;
            padding: 15px 30px;
            text-decoration: none;
            border-radius: 5px;
            margin: 20px 0;
            font-weight: bold;
        }
        .footer {
            background: #f8f9fa;
            padding: 20px;
            text-align: center;
            font-size: 12px;
            color: #666;
        }
    </style>
</head>
<body>
    <div class="container">
        <div class="header">
            <h1>欢迎注册我们的服务!</h1>
        </div>
        <div class="content">
            <p>亲爱的 <strong th:text="${username}">用户</strong>,</p>
            <p>感谢您注册我们的服务!为了确保您的账户安全,请点击下面的按钮完成邮箱验证:</p>
            
            <div style="text-align: center;">
                <a th:href="${verifyUrl}" class="btn">验证邮箱</a>
            </div>
            
            <p>或者复制以下链接到浏览器地址栏:</p>
            <p style="word-break: break-all; background: #f8f9fa; padding: 10px; border-radius: 5px;">
                <span th:text="${verifyUrl}">验证链接</span>
            </p>
            
            <p><strong>注意:</strong>此验证链接将在 <span th:text="${expireTime}">24小时</span> 后失效。</p>
            
            <p>如果您没有注册过我们的服务,请忽略此邮件。</p>
        </div>
        <div class="footer">
            <p>此邮件由系统自动发送,请勿回复。</p>
            <p>© 2024 Your Company. All rights reserved.</p>
        </div>
    </div>
</body>
</html>

2. 密码重置邮件模板

创建password-reset.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>密码重置</title>
    <style>
        body { font-family: Arial, sans-serif; background-color: #f4f4f4; }
        .container { max-width: 600px; margin: 20px auto; background: white; border-radius: 10px; }
        .header { background: #e74c3c; color: white; padding: 30px; text-align: center; }
        .content { padding: 40px 30px; }
        .btn { display: inline-block; background: #e74c3c; color: white; padding: 15px 30px; 
               text-decoration: none; border-radius: 5px; margin: 20px 0; }
        .warning { background: #fff3cd; border: 1px solid #ffeaa7; padding: 15px; border-radius: 5px; margin: 20px 0; }
    </style>
</head>
<body>
    <div class="container">
        <div class="header">
            <h1>🔐 密码重置请求</h1>
        </div>
        <div class="content">
            <p>您好,<strong th:text="${username}">用户</strong>!</p>
            <p>我们收到了您的密码重置请求。如果这是您的操作,请点击下面的按钮重置密码:</p>
            
            <div style="text-align: center;">
                <a th:href="${resetUrl}" class="btn">重置密码</a>
            </div>
            
            <div class="warning">
                <strong>安全提醒:</strong>
                <ul>
                    <li>此链接仅在 <span th:text="${expireTime}">30分钟</span> 内有效</li>
                    <li>如果不是您的操作,请忽略此邮件</li>
                    <li>为保护账户安全,建议定期更换密码</li>
                </ul>
            </div>
            
            <p>如果按钮无法点击,请复制以下链接:</p>
            <p style="word-break: break-all; background: #f8f9fa; padding: 10px;">
                <span th:text="${resetUrl}">重置链接</span>
            </p>
        </div>
    </div>
</body>
</html>

控制器示例

@RestController
@RequestMapping("/api/email")
@Slf4j
public class EmailController {
    
    @Autowired
    private EmailService emailService;
    
    @PostMapping("/send-verify")
    public ResponseEntity<String> sendVerifyEmail(@RequestBody EmailRequest request) {
        try {
            Map<String, Object> variables = new HashMap<>();
            variables.put("username", request.getUsername());
            variables.put("verifyUrl", "https://yourdomain.com/verify?token=" + request.getToken());
            variables.put("expireTime", "24小时");
            
            emailService.sendTemplateEmail(
                request.getEmail(),
                "账户验证 - 请验证您的邮箱",
                "register-verify",
                variables
            );
            
            return ResponseEntity.ok("验证邮件发送成功");
        } catch (Exception e) {
            log.error("发送验证邮件失败", e);
            return ResponseEntity.status(500).body("邮件发送失败");
        }
    }
    
    @PostMapping("/send-reset")
    public ResponseEntity<String> sendResetEmail(@RequestBody EmailRequest request) {
        try {
            Map<String, Object> variables = new HashMap<>();
            variables.put("username", request.getUsername());
            variables.put("resetUrl", "https://yourdomain.com/reset?token=" + request.getToken());
            variables.put("expireTime", "30分钟");
            
            emailService.sendTemplateEmail(
                request.getEmail(),
                "密码重置 - 重置您的账户密码",
                "password-reset",
                variables
            );
            
            return ResponseEntity.ok("重置邮件发送成功");
        } catch (Exception e) {
            log.error("发送重置邮件失败", e);
            return ResponseEntity.status(500).body("邮件发送失败");
        }
    }
}

最佳实践

1. 异步发送邮件

为了提高用户体验,建议使用异步方式发送邮件:

@Service
public class AsyncEmailService {
    
    @Autowired
    private EmailService emailService;
    
    @Async
    public CompletableFuture<Void> sendEmailAsync(String to, String subject, 
                                                 String templateName, Map<String, Object> variables) {
        try {
            emailService.sendTemplateEmail(to, subject, templateName, variables);
            return CompletableFuture.completedFuture(null);
        } catch (Exception e) {
            return CompletableFuture.failedFuture(e);
        }
    }
}

2. 邮件发送限流

@Component
public class EmailRateLimiter {
    private final Map<String, Long> lastSendTime = new ConcurrentHashMap<>();
    private final long RATE_LIMIT_INTERVAL = 60000; // 1分钟
    
    public boolean canSend(String email) {
        long currentTime = System.currentTimeMillis();
        Long lastTime = lastSendTime.get(email);
        
        if (lastTime == null || currentTime - lastTime > RATE_LIMIT_INTERVAL) {
            lastSendTime.put(email, currentTime);
            return true;
        }
        return false;
    }
}

3. 邮件发送日志记录

@Entity
@Table(name = "email_log")
public class EmailLog {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    private String recipient;
    private String subject;
    private String template;
    private String status; // SUCCESS, FAILED
    private String errorMessage;
    private LocalDateTime sendTime;
    
    // getter/setter...
}

常见问题及解决方案

1. 邮件发送失败

问题: AuthenticationFailedException: 535 Login Fail

解决方案:

  • 确认邮箱用户名和授权码正确
  • 检查邮箱服务商是否开启SMTP服务
  • 验证服务器网络是否可访问邮箱服务器

2. 中文乱码问题

解决方案:

helper.setText(htmlContent, true);
// 设置编码
message.setHeader("Content-Type", "text/html; charset=UTF-8");

3. 模板渲染失败

确保模板路径正确,变量名称与模板中的占位符一致。

总结

在Spring Boot项目中实现邮件模板功能。主要包括:

  1. 环境配置:添加必要依赖和邮箱配置
  2. 服务实现:创建邮件发送服务,支持简单邮件、模板邮件和带附件邮件
  3. 模板设计:使用Thymeleaf创建美观的HTML邮件模板
  4. 最佳实践:异步发送、限流控制、日志记录等

邮件功能是Web应用的重要组成部分,合理的邮件模板设计不仅能提升用户体验,还能有效传达信息。希望本文能帮助你快速掌握Spring Boot邮件功能的开发。

在实际项目中,还需要考虑邮件发送的稳定性、安全性和性能优化等方面。建议根据具体业务需求进行适当调整和扩展。

……

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注