Receive emails with SubEtha SMTP and Spring Boot

Java Jul 04, 2020

Some years ago I needed to write a Java application that receives emails and process some business stuff on those. It wasn't an easy task as I didn't find a lot of information. Because of that I decided, one day, I'll write about it and maybe that could helps 🙂.

Code example

This post has a working code example available here.

Context

Why ?

You would ask why do you need that ? I would say :

Business requirements can surprise you every day

Below a small list of some use cases when this incoming emails processing can be helpful.

  • Testing : Can be good option for integration testing the email capabilities of your services/applications (The old FakeSMTP is a good GUI example)
  • Analyse received emails and extract some specific information
  • ...

What ?

SubEtha SMTP is low-level API for writing almost any kind of SMTP mail-receiving application.

To avoid you a long history presentation, the most updated version of SubEtha SMTP is a fork by David Moten (many thanks to him for his excellent work and keeping this library updated). We'll use this fork in this post.

I think no need to describe what's Spring Boot in nowadays.

Let's code

Before starting the code, I suggest to see how we can send an email via terminal. We'll need that to send an email to our future emails receiver application.

Send email with cURL

Below an example of sending email to specific mail server with parameters like from, recipient, auth and the content of the email.

curl --url 'smtp://localhost:25000' \                                     
  --mail-from 'customer-address@mail.com' \
  --mail-rcpt 'info@marketing.company.com' \
  --user 'superus3r:passw0rd' \
  -T <(echo -e 'From: customer-address@mail.com\nTo: info@marketing.company.com\nSubject: cURL Test\n\nHello, this is the content')
Send email with cURL

SubEtha dependency

Here we add the maven dependency of SubEtha SMTP.

<dependency>
    <groupId>com.github.davidmoten</groupId>
    <artifactId>subethasmtp</artifactId>
    <version>5.2.4</version>
</dependency>
SubEtha SMTP maven dependency

Start the embedded SMTP server

It's very easy to start a simple SMTP server with the library. Just create/build an instance of SMTPServer with the right parameters like port and start this instance.

// Build the embedded SMTP server
SMTPServer smtpServer = SMTPServer.port(25000).build();
// Start server asynchronously 
smtpServer.start();
Build and start SMTP server

Now our SMTP server is started but not fully ready to receive emails. We need to implement a MessageListener to extract important data like from and recipient addresses but also the subject and the body of the received email.

For that we can implement the SimpleMessageListener interface :

public class MarketingMailListener implements SimpleMessageListener {

    private static final String MARKETING_DOMAIN = "@marketing.company.com";

    /**
     * Do some business logic here
     */
    @Override
    public boolean accept(String from, String recipient) {
        return recipient != null && recipient.endsWith(MARKETING_DOMAIN);
    }

    @Override
    public void deliver(String from, String recipient, InputStream data) 
    throws TooMuchDataException, IOException {
        System.out.println("deliver message of MARKETING department");
        System.out.println("From : "+from);
        System.out.println("Recipient : "+recipient);
    }
Implement SimpleMessageListener interface

As you can see here, I added a simple business rule inside accept method to handle only emails sent to marketing department.

The accept method is responsible for defining if the received message will be proceeded by this listener or not.

The deliver method is responsible for extracting :

  • From : The sender address
  • Recipient : The receiver address
  • Data : It's an InputStream type from which we can extract content and relevant information related to the received email like : subject, body, CC, attachments and else.

Let's see how we can extract the content of the email :

Convert to MimeMessage

We can retrieve content from multipart content-typed message using MimeMessage instance.

public MimeMessage convertToMimeMessage(InputStream data) 
throws MessagingException {
        Session session = Session.getDefaultInstance(new Properties());
        try {
            return new MimeMessage(session, data);
        }catch (MessagingException e){
            throw new MessagingException();
        }
    }
Convert InputStream to MimeMessage

Extract email content

To extract the email content, we'll create an ReceivedEmail model to facilitate the processing.

public class ReceivedEmail {

    private String subject;
    private String senderAddress;
    private String senderName;
    private String recipientAddress;
    private String recipientName;
    private String cc;
    private String bcc;
    private String contentType;
    private List<DataSource> attachments = new ArrayList();

    // Getters and setters
    // ...
}
ReceivedEmail model

For the sake of simplicity I used String for recipientAddress, CC and BCC but it's can be a list of addresses (example in the code).

We create dedicated service for email content extraction and the extract method could looks like :

public ReceivedEmail extractReceivedEmail(InputStream data) throws Exception {
        ReceivedEmail receivedEmail = new ReceivedEmail();
        MimeMessage message;
        try {
            message = this.convertToMimeMessage(data);
            receivedEmail.setSubject(message.getSubject());
            receivedEmail.setSenderAddress(InternetAddress.toString(message.getFrom()));
            InternetAddress[] recipientAddresses = InternetAddress.parse(InternetAddress.toString(message.getAllRecipients()));
            receivedEmail.setRecipientAddress(InternetAddress.toString(recipientAddresses));
            receivedEmail.setRecipientName(recipientAddresses[0].getPersonal());
            receivedEmail.setContentType(message.getContentType());
            // Use here Apache library for parsing
            MimeMessageParser messageParser = new MimeMessageParser(message);
            messageParser.parse(); // very important to parse before getting data
            receivedEmail.setCc(messageParser.getCc().toString());
            receivedEmail.setBcc(messageParser.getBcc().toString());
            receivedEmail.setAttachments(messageParser.getAttachmentList());
            System.out.println(receivedEmail);
            return receivedEmail;
        } catch (Exception e) {
            throw new Exception();
        }
    }
Extract content of the received email

Now we have a full complete email receiver with minimal lines of code.

Secure SMTP Server

Let's go further step to see how we can secure our mail server.

First thing to do is to indicate that the authentication is required and implement basic auth with username and password.

this.smtpServer = SMTPServer
                .port(25000)
                .simpleMessageListener(marketingMsgListener)
                .requireAuth(true) // auth required
                .authenticationHandlerFactory(easyAuth) // implement type of auth
                .build();
Add authentication to the mail receiver server

Different implementations of authenticationHandlerFactory can be done, I chose here, again for the sake of simplicity, the EasyAuthenticationHandlerFactory class which needs implementation of the UsernamePasswordValidator interface.

@Configuration
public class SimpleAuthValidatorImpl implements UsernamePasswordValidator {

    private final String CREDENTIALS_LOGIN = "superus3r";
    private final String CREDENTIALS_PASSWORD = "passw0rd";

    @Override
    public void login(String username, String password, MessageContext context) throws LoginFailedException {
        if(CREDENTIALS_LOGIN.equals(username) && CREDENTIALS_PASSWORD.equals(password)){
            System.out.println("Authenticated successfully");
        }else{
            System.err.println("Invalid authentication !");
            throw new LoginFailedException();
        }
    }
}
Add authentication to the mail receiver server

And this how our SMTPServerConfig looks like :

@Configuration
public class SMTPServerConfig {

    private final SMTPServer smtpServer;
    private final SimpleMessageListener marketingMsgListener;
    private final UsernamePasswordValidator authValidator;
    private final EasyAuthenticationHandlerFactory easyAuth;

    public SMTPServerConfig(SimpleMessageListener marketingMsgListener) {
        authValidator = new SimpleAuthValidatorImpl();
        easyAuth = new EasyAuthenticationHandlerFactory(authValidator);
        this.marketingMsgListener = marketingMsgListener;

        this.smtpServer = SMTPServer
                .port(25000)
                .simpleMessageListener(this.marketingMsgListener)
                .requireAuth(true)
                .authenticationHandlerFactory(easyAuth)
                .build();

        this.smtpServer.start();
    }
}
Custom configuration of SMTP server

Go deeper

More parameters can be customised to fit your needs and improve your SubEtha server, I list those below and you can try to explore them to improve your mail receiver implementation :

  • Max connections and connection timeout
  • TLS support
  • Messages size
  • Shutdown of the SMTP server
  • Testing

Conclusion

I presented how we can, with small lines of code, write an application that can receive emails, handle those and apply some magic to them with SubEtha SMTP and Spring Boot.

If you have any questions or suggestions, please, leave your comments down below.

Thanks for reading and happy coding.

Resources

Source code : https://github.com/redamessoudi/subethasmtp-springboot.

Header photo by Liam Truong on Unsplash

Tags

Reda Messoudi

I believe in knowledge sharing and this is why internet revolute our lives and mindsets thus I try to do my best to participate even with a little bit to this change.

Great! You've successfully subscribed.
Great! Next, complete checkout for full access.
Welcome back! You've successfully signed in.
Success! Your account is fully activated, you now have access to all content.