Spring Boot Email Integration: Build Verification Code System with Redis & Thymeleaf

This tutorial walks through email protocols, third-party SMTP setup for Feishu and QQ, Spring Boot mail starter configuration, core JavaMailSender/MimeMessage/MimeMessageHelper relationships, and a complete email verification code implementation using Redis for storage and Thymeleaf for HTML templates.

Java Captain
Java Captain
Java Captain
Spring Boot Email Integration: Build Verification Code System with Redis & Thymeleaf

Email Transmission Process

When sending an email from QQ Mail to NetEase Mail, the flow involves five steps:

QQ Mail client uses SMTP to send the email to QQ's mail server.

QQ Mail server receives the email and parses the target domain name.

Since the domain belongs to another provider, QQ Mail server forwards the email via SMTP to NetEase Mail server.

NetEase Mail server recognizes its own domain and stores the envelope.

NetEase Mail client comes online, checks the server, and pulls the email using IMAP or POP3.

Enabling Third-Party Email Services

Feishu Mail

Navigate to the email settings, find "Third-party email client login", select a device, and generate an authorization code, username, and sending server (e.g., smtp.feishu.cn).

QQ Mail

Go to Account Center, enable POP3/IMAP/SMTP services, choose a verification method, and generate an authorization code. Other providers like NetEase follow a similar process.

Spring Boot Email Integration

Dependency

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

Configuration (application.yml)

spring:
  mail:
    host: smtp.feishu.cn
    username: huag****@ifree8.com
    password: 29F7s********
    default-encoding: UTF-8

Test Sending

@Test
public void test() throws Exception {
  JavaMailSenderImpl javaMailSender = new JavaMailSenderImpl();
  javaMailSender.setDefaultEncoding("utf-8");
  javaMailSender.setHost("smtp.qq.com");
  javaMailSender.setPort(465);
  javaMailSender.setUsername("[email protected]");
  javaMailSender.setPassword("<your password/authorization code>");
  javaMailSender.setProtocol("smtps");

  MimeMessage message = javaMailSender.createMimeMessage();
  MimeMessageHelper helper = new MimeMessageHelper(message, false);
  helper.setFrom("[email protected]", "springdoc");
  helper.setTo("[email protected]");
  helper.setSubject("Hello");
  helper.setText("Hello <strong> World</strong>!", true);
  javaMailSender.send(message);
}

Core Class Relationships

JavaMailSender

Implements MailSender interface which defines send(). JavaMailSenderImpl is the concrete implementation.

Acts as the "courier" responsible for actually sending the email.

MimeMessage

Extends Message and implements MimePart.

Represents the "package" — defines email content, recipients, headers.

Key methods: setFrom(Address), setRecipients(), setText().

Low-level API; parameters are already wrapped objects (e.g., Address).

MimeMessageHelper

Helper/wrapper class to simplify setting MimeMessage properties.

Accepts raw types like String for setFrom() instead of Address.

Internally delegates to mimeMessage.setFrom() after validation and parsing.

Especially useful for complex emails (HTML + attachments), reducing boilerplate.

Email Verification Code Implementation

Dependencies

<dependencies>
  <dependency> org.springframework.boot:spring-boot-starter-web </dependency>
  <dependency> org.springframework.boot:spring-boot-starter-test </dependency>
  <dependency> org.springframework.boot:spring-boot-starter-mail </dependency>
  <dependency> org.springframework.boot:spring-boot-starter-data-redis </dependency>
  <dependency> org.apache.commons:commons-pool2 </dependency>
  <dependency> org.redisson:redisson-spring-boot-starter </dependency>
  <dependency> cn.hutool:hutool-all </dependency>
  <dependency> org.springframework.boot:spring-boot-starter-thymeleaf </dependency>
  <dependency> ognl:ognl:3.3.4 </dependency>
  <dependency> org.projectlombok:lombok </dependency>
</dependencies>

Configuration Properties

spring:
  mail:
    host: smtp.feishu.cn
    username: hua******@ifree8.com
    password: 29******
    default-encoding: UTF-8
    mailFrom: hua******@ifree8.com
    mailPersonal: 通用Ai智能体
    mailSubject: 邮箱验证码
    regExpr: ^(?!.*\.\.)[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$
    variable: code
    htmlTemplate: MailVerifyTemplate.html

HTML Template (MailVerifyTemplate.html)

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>邮箱验证码</title>
</head>
<body style="font-family: Arial, Helvetica, sans-serif; background-color: #f6f8fa; padding: 20px;">
  <div style="max-width: 600px; margin: auto; background: #ffffff; border-radius: 8px; padding: 30px; box-shadow: 0 2px 6px rgba(0,0,0,0.1);">
    <h2 style="text-align: center; color: #333333;">邮箱验证码</h2>
    <p style="font-size: 16px; color: #333;">您好,</p>
    <p style="font-size: 14px; color: #555;">
      您正在进行邮箱验证,本次验证码如下(5分钟内有效):
    </p>
    <div style="text-align: center; margin: 30px 0;">
      <span style="font-size: 28px; font-weight: bold; color: #2d89ef; letter-spacing: 3px;" th:text="${code}">88888</span>
    </div>
    <p style="font-size: 14px; color: #555;">
      请在验证页面输入上方验证码完成验证。为保障账号安全,请勿将验证码告知他人。
    </p>
    <hr style="margin: 30px 0; border: none; border-top: 1px solid #eee;">
    <p style="font-size: 12px; color: #999; text-align: center;">
      如果这不是您本人的操作,请忽略此邮件。
    </p>
  </div>
</body>
</html>

Custom JavaMailSender Bean

Because the sender is fixed for verification codes, a JavaMailSender bean is initialized once and injected where needed.

Service Interface

public interface AuthService {
  ResponseEntity sendCode2Mail(String targetMail);
  ResponseEntity verifyMailCode(String mail, String userCode);
}

Service Implementation

@Service
@Slf4j
public class AuthServiceImpl implements AuthService {
  @Autowired private MailConfig mailConfig;
  @Autowired @Qualifier("defaultJavaMailSender") private JavaMailSender javaMailSender;
  @Autowired private RedisBase redisBase;
  @Autowired private RedisConstant redisConstant;

  @Override
  public ResponseEntity<String> sendCode2Mail(String targetMail) {
    // 1. Validate email
    if (StrUtil.isBlank(targetMail)) {
      return ResponseEntity.failBusinessException(FAIL, "邮箱不能为空");
    }
    if (!ReUtil.isMatch(mailConfig.getRegExpr(), targetMail)) {
      return ResponseEntity.failBusinessException(FAIL, "邮箱格式不正确");
    }

    // 2. Process template
    TemplateEngine engine = TemplateUtil.createEngine(
      new TemplateConfig("templates", TemplateConfig.ResourceMode.CLASSPATH));
    Template template = engine.getTemplate(mailConfig.getHtmlTemplate());
    int codeNum = NumberUtil.generateRandomNumber(200000, 999999, 1)[0];
    String code = String.valueOf(codeNum);
    String htmlContent = template.render(Map.of(mailConfig.getVariable(), code));

    // 3. Build email
    MimeMessage message = javaMailSender.createMimeMessage();
    MimeMessageHelper helper = null;
    try {
      helper = new MimeMessageHelper(message, false);
      helper.setFrom(mailConfig.getMailFrom(), mailConfig.getMailPersonal());
      helper.setTo(targetMail);
      helper.setSubject(mailConfig.getMailSubject());
      helper.setText(htmlContent, true);
    } catch (Exception e) {
      log.error("happen error:", e);
      return ResponseEntity.failBusinessException(FAIL, "发生异常,请稍后重试!");
    }

    // 4. Store code in Redis with SETNX (5 min TTL)
    boolean setnxFlag = redisBase.setnx(
      redisConstant.getMailVerifyCodeKeyPrefix() + targetMail,
      code,
      redisConstant.getMailVerifyCodeExpireSeconds());
    if (!setnxFlag) {
      return ResponseEntity.failBusinessException(FAIL, "redis异常");
    }

    // 5. Send
    javaMailSender.send(message);

    // 6. Return success
    return ResponseEntity.ok(null, "邮箱发送成功,请注意查收!");
  }

  @Override
  public ResponseEntity verifyMailCode(String mail, String registMailCode) {
    String key = redisConstant.getMailVerifyCodeKeyPrefix() + mail;
    String systemCode = (String) redisBase.get(key);
    if (systemCode == null) {
      return ResponseEntity.failBusinessException(FAIL,
        "尚未存在邮箱验证码或验证码已过期,请重新发送邮箱验证码!");
    }
    if (!StrUtil.equals(registMailCode, systemCode)) {
      return ResponseEntity.failBusinessException(FAIL, "验证码错误!");
    }
    return ResponseEntity.ok();
  }
}

Summary

Email protocol basics: SMTP for sending, IMAP/POP3 for receiving.

End-to-end flow between client and server across providers.

How to enable third-party client access for Feishu and QQ Mail.

Spring Boot integration with spring-boot-starter-mail, configuration, and core classes ( JavaMailSender, MimeMessage, MimeMessageHelper).

Complete verification code system using Redis for atomic SETNX storage with TTL, Thymeleaf for HTML templating, and Hutool utilities for validation and random generation.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

RedisSpring BootThymeleafSMTPJavaMailSenderEmail VerificationIMAPPOP3MimeMessageMimeMessageHelper
Java Captain
Written by

Java Captain

Focused on Java technologies: SSM, the Spring ecosystem, microservices, MySQL, MyCat, clustering, distributed systems, middleware, Linux, networking, multithreading; occasionally covers DevOps tools like Jenkins, Nexus, Docker, ELK; shares practical tech insights and is dedicated to full‑stack Java development.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.