Comprehensive analysis of SpringBoot mail service integration configuration

Preface

This article takes NetEase mailbox (and 163 mailbox) as an example to show how to integrate email services for the SpringBoot project. Other mailbox configurations are similar. You can check the Spring Email guide or other official documents.

Authorization code

First we need to obtain the authorization code for subsequent configuration and log in to the email address: https://mail.163.com/

Click Settings at the top and select the POP3/SMTP/IMAP option

The POP3/SMTP service is enabled – enable this service. To enable it, you need to verify your mobile phone number and send a verification code.

After verification is completed, Authorization Code will be returned. This authorization code will only be displayed once. Remember to save it, otherwise you will need to resend the verification code to obtain a new authorization code.

Add dependencies

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

Configuration file

Spring Boot provides automatic configuration and starter modules for JavaMailSender.

spring:
  mail:  
    default-encoding: UTF-8  
    host: smtp.163.com # Email service for sending emails from website host    port: 465 
    username: xxx@163.com # Mail 
    password: ONSWXXXXXXXX # Authorization code 
    protocol: smtp  
    properties:  
      mail:  
        smtp:  
          auth: 'true'  
          socketFactory:  
            class: com.rymcu.forest.util.MailSSLSocketFactory  
#            class: javax.net.ssl.SSLSocketFactory 
            port: 465  
          ssl:  
            enable: true  
          starttls:  
            enable: true  
          stattls:  
            required: true  
          connectiontimeout: 5000  
          timeout: 3000  
          writetimeout: 5000 

Note: The password above is not your email password, but the authorization code we got in the previous chapter.

Introduction to relevant parameters

  • default-encoding: Default encoding format, here set to UTF-8.
  • host: The address of the SMTP server. Here is the SMTP server address of the 163 mailbox.
  • port: The port of the SMTP server. The SMTP port of the 163 mailbox is 465.
  • username: 163 email account.
  • password: The authorization code we got above.
  • protocol: The protocol used, here is the SMTP protocol.
  • properties: Additional property settings.
  • mail: Mail-related attributes.
  • smtp: SMTP related attributes.
  • auth: Whether authentication is required. Set to true here, indicating that authentication is required.
  • socketFactory: Socket factory related settings.
  • class: Socket factory class, indicating the use of SSL encryption.
  • port: The port used by the Socket factory, here is also 465.
  • ssl: SSL related settings.
  • enable: Whether to enable SSL. Set to true here to enable SSL encryption.
  • starttls: STARTTLS related settings.
  • enable: Whether to enable STARTTLS. Set to true here to enable STARTTLS.
  • stattls: STARTTLS related settings.
  • required: Whether STARTTLS is required, set to true here, indicating that STARTTLS is required.
  • connectiontimeout: Connection timeout in milliseconds, here set to 5000 milliseconds (5 seconds).
  • timeout: Operation timeout in milliseconds, here set to 3000 milliseconds (3 seconds).
  • writetimeout: Write timeout in milliseconds, here set to 5000 milliseconds (5 seconds).

More details can be found in Spring official documentation: 36. Sending Email (spring.io)

MailProperties source code: MailProperties.java at v2.0.3.RELEASE

If you don’t want to write some configuration information in application.yml, you can also hardcode it directly in the code.

 Properties props = new Properties();
        // expressSMTPsend email,Authentication required
        props.put("mail.smtp.auth", true);
        props.put("mail.smtp.ssl.enable", true);
        props.put("mail.smtp.host", SERVER_HOST);
        props.put("mail.smtp.port", SERVER_PORT);
        // If usingssl,then remove the use of25Port configuration,Configure as follows
        props.put("mail.smtp.socketFactory.class", "javax.net.ssl.SSLSocketFactory");
        props.put("mail.smtp.socketFactory.port", SERVER_PORT);
        // Sender's account number,Fill in the sending address configured in the console,for examplexxx@xxx.com
        props.put("mail.user", USERNAME);
        // accessSMTPPassword required for service(Select the sending address in the console to set it.)
        props.put("mail.password", PASSWORD);
        // Configuration
        mailSender.setJavaMailProperties(props); 

Also, you can notice that the port specified above is 465. This is because the default port 25 of the SMTP service is disabled by Alibaba Cloud by default. For details, please see the official Alibaba Cloud documentation.

Therefore, if we do not specify the port for local debugging, there is no problem. However, Alibaba Cloud cannot send emails through port 25 online. It is recommended to directly specify port 465 (using SSL) or port 80 (not using SSL). Although port 25 can be unblocked through certain means, it is more troublesome and the success rate is not high.

Write code

Note: The code part comes from the warehouse: rymcu/forest: forest (forest), and has been changed at the same time.

SSL related configuration

This chapter is related to the configuration that requires the use of https protocol. If you do not have this requirement, you can skip this small chapter first. It will not affect the configuration after configuring it later. However, you should set spring.mail.properties.mail.smtp.ssl. enable=false to turn off ssl

MailSSLSocketFactory

package com.rymcu.forest.util;

import javax.net.SocketFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import java.io.IOException;
import java.net.InetAddress;
import java.net.Socket;

public class MailSSLSocketFactory extends SSLSocketFactory {
    private SSLSocketFactory factory;

    public MailSSLSocketFactory() {
        try {
            SSLContext sslcontext = SSLContext.getInstance("TLS");
            sslcontext.init(null, new TrustManager[]{new MailTrustManager()}, null);
            factory = sslcontext.getSocketFactory();
        } catch (Exception ex) {
            // ignore
        }
    }

    public static SocketFactory getDefault() {
        return new MailSSLSocketFactory();
    }

    @Override
    public Socket createSocket() throws IOException {
        return factory.createSocket();
    }

    @Override
    public Socket createSocket(Socket socket, String s, int i, boolean flag) throws IOException {
        return factory.createSocket(socket, s, i, flag);
    }

    @Override
    public Socket createSocket(InetAddress inaddr, int i, InetAddress inaddr1, int j) throws IOException {
        return factory.createSocket(inaddr, i, inaddr1, j);
    }

    @Override
    public Socket createSocket(InetAddress inaddr, int i) throws IOException {
        return factory.createSocket(inaddr, i);
    }

    @Override
    public Socket createSocket(String s, int i, InetAddress inaddr, int j) throws IOException {
        return factory.createSocket(s, i, inaddr, j);
    }

    @Override
    public Socket createSocket(String s, int i) throws IOException {
        return factory.createSocket(s, i);
    }

    @Override
    public String[] getDefaultCipherSuites() {
        return factory.getDefaultCipherSuites();
    }

    @Override
    public String[] getSupportedCipherSuites() {
        return factory.getSupportedCipherSuites();
    }
} 

MailTrustManager

package com.rymcu.forest.util;

import javax.net.ssl.X509TrustManager;
import java.security.cert.X509Certificate;

public class MailTrustManager implements X509TrustManager {
    public void checkClientTrusted(X509Certificate[] cert, String authType) {
        // everything is trusted
    }

    public void checkServerTrusted(X509Certificate[] cert, String authType) {
        // everything is trusted
    }

    public X509Certificate[] getAcceptedIssuers() {
        return new X509Certificate[0];
    }
} 

send email

JavaMailService defines mail-related interfaces

public interface JavaMailService {
    /**
     * Send verification code email
     *
     * @param email Recipient email
     * @return Results of the 0:fail1:success
     * @throws MessagingException
     */
    Integer sendEmailCode(String email) throws MessagingException;

    /**
     * Send password retrieval email
     *
     * @param email Recipient email
     * @return Results of the 0:fail1:success
     * @throws MessagingException
     */
    Integer sendForgetPasswordEmail(String email) throws MessagingException;

    /**
     * Send next message notification
     *
     * @param notification
     * @return
     * @throws MessagingException
     */
    Integer sendNotification(NotificationDTO notification) throws MessagingException;
} 

JavaMailServiceImpl mail interface implementation

@Service
public class JavaMailServiceImpl implements JavaMailService {

    /**
     * Javamailer
     */
    @Resource
    private JavaMailSenderImpl mailSender;
    @Resource
    private RedisService redisService;
    @Resource
    private UserService userService;
    /**
     * thymeleaftemplate engine
     */
    @Resource
    private TemplateEngine templateEngine;

    @Value("{spring.mail.host}")
    private String SERVER_HOST;
    @Value("{spring.mail.port}")
    private String SERVER_PORT;
    @Value("{spring.mail.username}")
    private String USERNAME;
    @Value("{spring.mail.password}")
    private String PASSWORD;
    @Value("${resource.domain}")
    private String BASE_URL;

    @Override
    public Integer sendEmailCode(String email) throws MessagingException {
        return sendCode(email, 0);
    }

    @Override
    public Integer sendForgetPasswordEmail(String email) throws MessagingException {
        return sendCode(email, 1);
    }

    @Override
    public Integer sendNotification(NotificationDTO notification) throws MessagingException {
        User user = userService.findById(String.valueOf(notification.getIdUser()));
        if (NotificationConstant.Comment.equals(notification.getDataType())) {
            String url = notification.getDataUrl();
            String thymeleafTemplatePath = "mail/commentNotification";
            Map<String, Object> thymeleafTemplateVariable = new HashMap<String, Object>(4);
            thymeleafTemplateVariable.put("user", notification.getAuthor().getUserNickname());
            thymeleafTemplateVariable.put("articleTitle", notification.getDataTitle());
            thymeleafTemplateVariable.put("content", notification.getDataSummary());
            thymeleafTemplateVariable.put("url", url);

            sendTemplateEmail(USERNAME,
                    new String[]{user.getEmail()},
                    new String[]{},
                    "【RYMCU】 notification",
                    thymeleafTemplatePath,
                    thymeleafTemplateVariable);
            return 1;
        }
        return 0;
    }

    private Integer sendCode(String to, Integer type) throws MessagingException {
        SimpleMailMessage simpleMailMessage = new SimpleMailMessage();
        simpleMailMessage.setFrom(USERNAME);
        simpleMailMessage.setTo(to);
        if (type == 0) {
            Integer code = Utils.genCode();
            redisService.set(to, code, 5 * 60);
            simpleMailMessage.setSubject("New user registration email verification");
            simpleMailMessage.setText("【RYMCU】Your verification code is " + code + ",Effective time 5 minute,Please do not disclose the verification code to others。If not operated by myself,please ignore!");
            mailSender.send(simpleMailMessage);
            return 1;
        } else if (type == 1) {
            String code = Utils.entryptPassword(to);
            String url = BASE_URL + "/forget-password?code=" + code;
            redisService.set(code, to, 15 * 60);

            String thymeleafTemplatePath = "mail/forgetPasswordTemplate";
            Map<String, Object> thymeleafTemplateVariable = new HashMap<String, Object>(1);
            thymeleafTemplateVariable.put("url", url);

            sendTemplateEmail(USERNAME,
                    new String[]{to},
                    new String[]{},
                    "【RYMCU】 Retrieve password",
                    thymeleafTemplatePath,
                    thymeleafTemplateVariable);
            return 1;
        }
        return 0;
    }

    /**
     * sendthymeleafTemplate email
     *
     * @param deliver                   Sender's email name like: returntmp@163.com
     * @param receivers                 recipient,Multiple recipients possible like:11111@qq.com,2222@163.com
     * @param carbonCopys               CC,Can have multiple carbon copies like:33333@sohu.com
     * @param subject                   Email Subject like:You received a fancy email,please check。
     * @param thymeleafTemplatePath     Email template like:mail\mailTemplate.html。
     * @param thymeleafTemplateVariable Email template variable set
     */
    public void sendTemplateEmail(String deliver, String[] receivers, String[] carbonCopys, String subject, String thymeleafTemplatePath,
                                  Map<String, Object> thymeleafTemplateVariable) throws MessagingException {
        String text = null;
        if (thymeleafTemplateVariable != null && thymeleafTemplateVariable.size() > 0) {
            Context context = new Context();
            thymeleafTemplateVariable.forEach((key, value) -> context.setVariable(key, value));
            text = templateEngine.process(thymeleafTemplatePath, context);
        }
        sendMimeMail(deliver, receivers, carbonCopys, subject, text, true, null);
    }

    /**
     * Sent email(Supports accessories/htmltype of mail)
     *
     * @param deliver             Sender's email name like: returntmp@163.com
     * @param receivers           recipient,Multiple recipients possible like:11111@qq.com,2222@163.com
     * @param carbonCopys         CC,Can have multiple carbon copies like:3333@sohu.com
     * @param subject             Email Subject like:You received a fancy email,please check。
     * @param text                content of email like:The test email is just for fun.。 <html><body><img
     *                            src=\"cid:attchmentFileName\"></body></html>
     * @param attachmentFilePaths Attachment file path like:
     *                            have to be aware of isaddInlineResource name in functionattchmentFileNameNeed and in the textcid:attchmentFileNamecorrespond to
     * @throws Exception Abnormal information during email sending
     */
    private void sendMimeMail(String deliver, String[] receivers, String[] carbonCopys, String subject, String text,
                              boolean isHtml, String[] attachmentFilePaths) throws MessagingException {
        StopWatch stopWatch = new StopWatch();

        stopWatch.start();
        MimeMessage mimeMessage = mailSender.createMimeMessage();
        MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, true);
        helper.setFrom(deliver);
        helper.setTo(receivers);
        helper.setCc(carbonCopys);
        helper.setSubject(subject);
        helper.setText(text, isHtml);
        // Add email attachment
        if (attachmentFilePaths != null && attachmentFilePaths.length > 0) {
            for (String attachmentFilePath : attachmentFilePaths) {
                File file = new File(attachmentFilePath);
                if (file.exists()) {
                    String attachmentFile = attachmentFilePath
                            .substring(attachmentFilePath.lastIndexOf(File.separator));
                    long size = file.length();
                    if (size > 1024 * 1024) {
                        String msg = String.format("The size of a single email attachment is not allowed to exceed1MB,[%s]File size[%s]。", attachmentFilePath,
                                file.length());
                        throw new RuntimeException(msg);
                    } else {
                        FileSystemResource fileSystemResource = new FileSystemResource(file);
                        helper.addInline(attachmentFile, fileSystemResource);
                    }
                }
            }
        }
        mailSender.send(mimeMessage);
        stopWatch.stop();

    }

} 

JavaMailServiceTest unit test

/**
 * javaMailtest
 */
class JavaMailServiceTest extends BaseServiceTest {

    private static final String REALITY_EMAIL = "xxxx@qq.com";
    @Autowired
    private JavaMailService javaMailService;

    @Test
    void sendEmailCode() throws MessagingException {
        assertEquals(1, javaMailService.sendEmailCode(REALITY_EMAIL));
    }

    @Test
    void sendForgetPasswordEmail() throws MessagingException {
        assertEquals(1, javaMailService.sendForgetPasswordEmail(REALITY_EMAIL));
    }

    @Test
    void sendNotification() throws MessagingException {
        assertEquals(0, javaMailService.sendNotification(new NotificationDTO()));

    }
} 

Finally, we test the above send verification code function: sendEmailCode (replace xxxx@qq.com above with your own recipient email address)

Finally sent successfully