10 Minutes Send Mail Service

Posted by NoDoze on Mon, 30 Mar 2020 04:57:42 +0200

Sending e-mail should be one of the essential extensions to the website, such as registering for verification, forgetting your password, or sending marketing information to users.

1. Mail Agreement

In the process of sending and receiving mail, related agreements need to be complied with, including:

  1. Protocol for sending e-mail: SMTP;
  2. Protocol for receiving e-mail: POP3 and IMAP.

1.1 What is SMTP?

SMTP, known as Simple Mail Transfer Protocol (Simple Mail Transfer Protocol), is a set of specifications for transporting messages from source addresses to destination addresses through which to control how they are transported.SMTP authentication requires an account and password to log on to the server and is designed to avoid spam.

1.2 What is IMAP?

IMAP is fully known as the Internet Message Access Protocol (Internet Mail Access Protocol), which allows you to get information from mail servers, download mail, and so on.IMAP, like POP, is a mail acquisition protocol.

1.3 What is POP3?

POP3 is fully known as Post Office Protocol 3 (Post Office Protocol), which enables clients to remotely manage mail on the server side.POP3 is commonly used for offline mail processing, which allows clients to download server mail, which is then deleted from the server.At present, many POP3 mail servers only provide download mail function, the server itself does not delete mail, this is an improved version of the POP3 protocol.

What is the difference between the 1.4 IMAP and POP3 protocols?

The biggest difference between the two is that IMAP allows two-way communication, where the client's actions are fed back to the server, such as taking mail from the client, marking it read, and so on, and the server synchronizes them.While the POP protocol also allows clients to download server mail, client operations are not synchronized to the server, such as collecting or marking read mail on the client, and the server does not synchronize these operations.

2. Initialization Configuration

2.1 Open Mail Service

This article only takes QQ mailbox and 163 mailbox as examples.

  1. QQ Mailbox Opens Mail Service Document
  2. 163 Mailbox Open Mail Service Document

2.2 pom.xml

Normally we use the JavaMail-related api to write code for sending mail, but now Spring Boot offers a simpler set of encapsulations to use.

  1. spring-boot-starter-mail:Spring Boot mail service;
  2. spring-boot-starter-thymeleaf: Use Thymeleaf to make mail templates.
<!-- test package-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
</dependency>
<!--mail -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-mail</artifactId>
</dependency>
<!--Use Thymeleaf Making Mail Templates -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

<!-- lombok -->
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <scope>1.8.4</scope>
</dependency>

2.3 application.yml

The configuration of spring-boot-starter-mail is provided by the MailProperties configuration class.

There are slightly different configurations for different mailboxes. Below are the configurations for QQ and 163 mailboxes.

server:
  port: 8081
#spring:
#  mail:
#    # QQ mailbox https://service.mail.qq.com/cgi-bin/help?Subtype=1 &&no=1001256 &&id=28
#    host: smtp.qq.com
#    # Mailbox account
#    username: van93@qq.com
#    # Mailbox Authorization Number (not Password)
#    password: password
#    default-encoding: UTF-8
#    properties:
#      mail:
#        smtp:
#          auth: true
#          starttls:
#            enable: true
#            required: true
spring:
  mail:
    # 163 Mailbox http://help.mail.163.com/faqDetail.do?Code=d7a5dc8471cd0c0e8b4f8e49998b374173cfe91305fa1ce630d7f67ac2cda80145a1742516
    host: smtp.163.com
    # Mailbox account
    username: 17098705205@163.com
    # Mailbox Authorization Number (not Password)
    password: password
    default-encoding: UTF-8
    properties:
      mail:
        smtp:
          auth: true
          starttls:
            enable: true
            required: true

2.4 Mail Information Class

To save the subject, content and other information of the message when it is sent

@Data
public class Mail {
    /**
     * Mail id
     */
    private String id;
    /**
     * Mail sender
     */
    private String sender;
    /**
     * Mail recipient (multiple mailboxes are separated by a comma "."
     */
    private String receiver;
    /**
     * Mail Subject
     */
    private String subject;
    /**
     * Mail Content
     */
    private String text;
    /**
     * Attachment/File Address
     */
    private String filePath;
    /**
     * Attachment/File Name
     */
    private String fileName;
    /**
     * Is there an attachment (not by default)
     */
    private Boolean isTemplate = false;
    /**
     * Template Name
     */
    private String emailTemplateName;
    /**
     * Template Content
     */
    private Context emailTemplateContext;

}

3. Realization of Sending Mail

3.1 Check incoming mail configuration

Verify the required entries for the recipient, subject, and content of the message

private void checkMail(Mail mail) {
    if (StringUtils.isEmpty(mail.getReceiver())) {
        throw new RuntimeException("Mail recipient cannot be empty");
    }
    if (StringUtils.isEmpty(mail.getSubject())) {
        throw new RuntimeException("Mail subject cannot be empty");
    }
    if (StringUtils.isEmpty(mail.getText()) && null == mail.getEmailTemplateContext()) {
        throw new RuntimeException("Message content cannot be empty");
    }
}

3.2 Save Mail to Database

Save the mail to the database after sending, to facilitate statistics and tracking of mail problems.

private Mail saveMail(Mail mail) {
    // todo Send Success/Failure Synchronize Mail Information to Database
    return mail;
}

3.3 Send Mail

  • Send plain text messages
public void sendSimpleMail(Mail mail){
    checkMail(mail);
    SimpleMailMessage mailMessage = new SimpleMailMessage();
    mailMessage.setFrom(sender);
    mailMessage.setTo(mail.getReceiver());
    mailMessage.setSubject(mail.getSubject());
    mailMessage.setText(mail.getText());
    mailSender.send(mailMessage);
    saveMail(mail);
}
  • Send mail and carry attachments
public void sendAttachmentsMail(Mail mail) throws MessagingException {
    checkMail(mail);
    MimeMessage mimeMessage = mailSender.createMimeMessage();
    MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, true);
    helper.setFrom(sender);
    helper.setTo(mail.getReceiver());
    helper.setSubject(mail.getSubject());
    helper.setText(mail.getText());
    File file = new File(mail.getFilePath());
    helper.addAttachment(file.getName(), file);
    mailSender.send(mimeMessage);
    saveMail(mail);
}
  • Send template mail
public void sendTemplateMail(Mail mail) throws MessagingException {
    checkMail(mail);
    // TempeEngine replaces the dynamic parameters and produces the final html
    String emailContent = templateEngine.process(mail.getEmailTemplateName(), mail.getEmailTemplateContext());

    MimeMessage mimeMessage = mailSender.createMimeMessage();

    MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, true);
    helper.setFrom(sender);
    helper.setTo(mail.getReceiver());
    helper.setSubject(mail.getSubject());
    helper.setText(emailContent, true);
    mailSender.send(mimeMessage);
    saveMail(mail);
}

IV. Testing and Optimization

4.1 Unit Test

  1. When testing attachment mail, attachments are placed in the static folder;
  2. When testing template mail, the template is placed in the file folder.
@RunWith(SpringRunner.class)
@SpringBootTest
public class MailServiceTest {

    @Resource
    MailService mailService;
    
    /**
     * Send plain text messages
     */
    @Test
    public void sendSimpleMail() {
        Mail mail = new Mail();
//        mail.setReceiver("17098705205@163.com");
        mail.setReceiver("van93@qq.com");
        mail.setSubject("Test Simple Mail");
        mail.setText("Test Simple Content");
        mailService.sendSimpleMail(mail);
    }

    /**
     * Send mail and carry attachments
     */
    @Test
    public void sendAttachmentsMail() throws MessagingException {
        Mail mail = new Mail();
//        mail.setReceiver("17098705205@163.com");
        mail.setReceiver("van93@qq.com");
        mail.setSubject("Test Attachment Mail");
        mail.setText("Attachment Mail Content");
        mail.setFilePath("file/dusty_blog.jpg");
        mailService.sendAttachmentsMail(mail);
    }

    /**
     * Test Template Mail
     */
    @Test
    public void sendTemplateMail() throws MessagingException {
        Mail mail = new Mail();
//        mail.setReceiver("17098705205@163.com");
        mail.setReceiver("van93@qq.com");
        mail.setSubject("Test Template Mail");
        //Create template body
        Context context = new Context();
        // Set parameters for template to be replaced
        context.setVariable("verifyCode", "6666");
        mail.setEmailTemplateContext(context);
        // Template name (Template location in templates directory)
        mail.setEmailTemplateName("emailTemplate");
        mailService.sendTemplateMail(mail);
    }
    
}

4.2 Optimization

Because there is also a need for Cc/Bcc for sending mail in general, here, an entity and a tool class are encapsulated to make it easy to call the mail service directly.

  • Mail Information Class
@Data
public class MailDomain {
    /**
     * Mail id
     */
    private String id;
    /**
     * Mail sender
     */
    private String sender;
    /**
     * Mail recipient (multiple mailboxes are separated by a comma "."
     */
    private String receiver;
    /**
     * Mail Subject
     */
    private String subject;
    /**
     * Mail Content
     */
    private String text;

    /**
     * Cc (multiple mailboxes are separated by commas','
     */
    private String cc;
    /**
     * Bcc (multiple mailboxes are separated by commas','
     */
    private String bcc;
    /**
     * Attachment/File Address
     */
    private String filePath;
    /**
     * Attachment/File Name
     */
    private String fileName;
    /**
     * Is there an attachment (not by default)
     */
    private Boolean isTemplate = false;
    /**
     * Template Name
     */
    private String emailTemplateName;
    /**
     * Template Content
     */
    private Context emailTemplateContext;
    /**
     * Send time (future send time can be specified)
     */
    private Date sentDate;
}
  • Mail Tool Class
@Component
public class EmailUtil {

    @Resource
    private JavaMailSender mailSender;

    @Resource
    TemplateEngine templateEngine;

    @Value("${spring.mail.username}")
    private String sender;

    /**
     * Building Complex Mail Information Classes
     * @param mail
     * @throws MessagingException
     */
    public void sendMail(MailDomain mail) throws MessagingException {

        //true indicates support for complex types
        MimeMessageHelper messageHelper = new MimeMessageHelper(mailSender.createMimeMessage(), true);
        //Mail sender reads from configuration item
        mail.setSender(sender);
        //Mail sender
        messageHelper.setFrom(mail.getSender());
        //Mail Recipient
        messageHelper.setTo(mail.getReceiver().split(","));
        //Mail Subject
        messageHelper.setSubject(mail.getSubject());
        //Mail Content
        if (mail.getIsTemplate()) {
            // TempeEngine replaces the dynamic parameters and produces the final html
            String emailContent = templateEngine.process(mail.getEmailTemplateName(), mail.getEmailTemplateContext());
            messageHelper.setText(emailContent, true);
        }else {
            messageHelper.setText(mail.getText());
        }
        //Cc
        if (!StringUtils.isEmpty(mail.getCc())) {
            messageHelper.setCc(mail.getCc().split(","));
        }
        //bcc
        if (!StringUtils.isEmpty(mail.getBcc())) {
            messageHelper.setCc(mail.getBcc().split(","));
        }
        //Add Mail Attachment
        if (mail.getFilePath() != null) {
            File file = new File(mail.getFilePath());
            messageHelper.addAttachment(file.getName(), file);
        }
        //Send Time
        if (StringUtils.isEmpty(mail.getSentDate())) {
            messageHelper.setSentDate(mail.getSentDate());
        }
        //Send mail officially
        mailSender.send(messageHelper.getMimeMessage());
    }

    /**
     * Detect Mail Information Class
     * @param mail
     */
    private void checkMail(MailDomain mail) {
        if (StringUtils.isEmpty(mail.getReceiver())) {
            throw new RuntimeException("Mail recipient cannot be empty");
        }
        if (StringUtils.isEmpty(mail.getSubject())) {
            throw new RuntimeException("Mail subject cannot be empty");
        }
        if (StringUtils.isEmpty(mail.getText()) && null == mail.getEmailTemplateContext()) {
            throw new RuntimeException("Message content cannot be empty");
        }
    }

    /**
     * Save Mail to Database
     * @param mail
     * @return
     */
    private MailDomain saveMail(MailDomain mail) {
        // todo Send Success/Failure Synchronize Mail Information to Database
        return mail;
    }
}

Detailed testing can be found in Github sample code And it won't stick out here.

V. Summary and Extension

5.1 Asynchronous Send

Many times mail sending is not the result of our main business, such as notification class, reminder class business can allow delays or failures.At this time, mail can be sent asynchronously to speed up the execution of the main transaction. In practical projects, MQ can be used to send mail related parameters, and start sending mail after listening to the message queue.

5.2 Send Failure

For a variety of reasons, there will always be cases of mail delivery failure, such as: mail sent too frequently, network abnormalities, and so on.When this happens, we usually consider retrying to send the message, which can be accomplished in the following steps:

  1. Receive a request to send mail, first record the request and store it in the library;
  2. Call the mail sending interface to send mail, and record the results of sending into the library;
  3. During the startup timer system scan period, no mail was sent successfully and the number of retries was less than 3 times, and then sent again.

5.3 Other Issues

Mail port problem and attachment size problem.

5.4 Sample Code Address

5.5 Technical Exchange

  1. Dust Blog
  2. Dust Blog-Gold Excavation
  3. Dust Blog-Blog Park
  4. Github

Topics: Java Spring Database Thymeleaf github