Mandrill emails in Spring Boot Java

Upasana | December 26, 2019 | 4 min read | 634 views


In this article we will learn how to send Mandrill emails (plain text, rich text, with attachments) from Java and Spring Boot applications with error handling and retry logic.
Table of contents
  1. Gradle setup

  2. Send plain text email

  3. Send multimedia rich-text emails

  4. Send emails with attachment

  5. Error handling and retry attempts

Configuration

Lutung is a Mandrill API client for Java.

We will include lutung dependency in our build.gradle file.

build.gradle
dependencies {
    implementation 'com.mandrillapp.wrapper.lutung:lutung:0.0.8'
    implementation group: 'org.springframework.retry', name: 'spring-retry', version: '1.2.4.RELEASE'
}

for maven users, please use the following:

pom.xml
<dependency>
    <groupId>com.mandrillapp.wrapper.lutung</groupId>
    <artifactId>lutung</artifactId>
    <version>0.0.8</version>
</dependency>

We need to create a bean for MandrillAPI that will be used later on for sending emails.

MandrillConfig.java
@Configuration
public class MandrillConfig {

    @Value("${mandrill.api.key}")   (1)
    String mandrillApiKey;

    @Bean
    public MandrillApi createMandrillApi() {
        return new MandrillApi(mandrillApiKey);
    }
}
1 You need to obtain Mandrill API key from your mandrill account.

Also, we will create a POJO for handling email attributes.

EmailMessage.java
public class EmailMessage {
    private String fromEmail;
    private String fromName;

    private List<String> to = new ArrayList<>();
    private List<String> cc = new ArrayList<>();

    private String subject;
    private String body;

    private String attachmentName;
    private String attachmentContent;

    //Getters and Setters omitted for brevity
}

Send plain text emails

Plain text emails can be easily sent using MandrillAPI. We will create a service that will accept the payload and send email using MandrillAPI under the hood.

@Service
public class EmailService {
    private static final Logger logger = LoggerFactory.getLogger(EmailService.class);

    @Autowired
    private MandrillApi mandrillApi;

    public void sendEmail(EmailMessage emailMessage) {
        MandrillMessage message = new MandrillMessage();
        message.setSubject(emailMessage.getSubject());
        message.setText(emailMessage.getBody());
        message.setAutoText(true);
        message.setFromEmail(emailMessage.getFromEmail());
        message.setFromName(emailMessage.getFromName());

        ArrayList<MandrillMessage.Recipient> recipients = new ArrayList<>();
        for (String email : emailMessage.getTo()) {
            MandrillMessage.Recipient recipient = new MandrillMessage.Recipient();
            recipient.setEmail(email);
            //recipient.setName("optional name");
            recipient.setType(MandrillMessage.Recipient.Type.TO);
            recipients.add(recipient);
        }

        for (String email : emailMessage.getCc()) {
            MandrillMessage.Recipient recipient = new MandrillMessage.Recipient();
            recipient.setEmail(email);
            recipient.setType(MandrillMessage.Recipient.Type.CC);
            recipients.add(recipient);
        }
        message.setTo(recipients);
        message.setPreserveRecipients(true);
        try {
            logger.info("Sending email to - {} with subject {}", emailMessage.getTo(), emailMessage.getSubject());
            MandrillMessageStatus[] messageStatusReports = mandrillApi.messages().send(message, false);
            for (MandrillMessageStatus messageStatusReport : messageStatusReports) {
                final String status = messageStatusReport.getStatus();
                logger.info("MessageStatusReports = " + status);
                if (status.equalsIgnoreCase("rejected") || status.equalsIgnoreCase("invalid")) {
                    logger.error("Could not send email to {} status {}", emailMessage.getTo(), status);
                }
            }
        } catch (MandrillApiError mandrillApiError) {
            logger.error("MandrillApiError: " + mandrillApiError.getMandrillErrorAsJson());
            logger.error("MandrillApiError sending email - " + emailMessage.getTo(), mandrillApiError);
            throw new EmailException("MandrillApiError sending email - " + emailMessage.getTo(), mandrillApiError);
        } catch (IOException e) {
            logger.error("IOException sending email - " + emailMessage.getTo(), e);
            throw new EmailException("IOException sending email - " + emailMessage.getTo(), e);
        }
    }
}

Now we are ready with our service that can send email to the customers.

Client code that will invoke this service will look like below:

@Component
public class EmailClient {

    private final Logger logger = LoggerFactory.getLogger(WbcEmailService.class);

    @Autowired
    private EmailService emailService;

    public void sendEmail() {
        EmailMessage message = EmailMessageBuilder.anEmailMessage()
                    .withSubject("subject line")
                    .withBody("this is email text body")
                    .withTo("foo.bar@mail.com")
                    .withFrom("mandrill@foo.bar")
                    .withFromName("Foo Bar")
                    .build();
        emailService.sendEmail(message);
    }

}

Send multimedia richtext emails

Code for sending multi-media richtext email is quite similar to what it was for plain text email, except the few changes that we need to make. We need to replace the following code in previously declared EmailService:

message.setText(emailMessage.getBody());
message.setAutoText(true);

with this one:

message.setHtml(emailMessage.getBody());
message.setAutoHtml(true);

That will send richtext emails.

Send emails with attachment

Lets make few additions to the email service to make it support attachments.

Add the below code to sendEmail(…​) method of EmailService class.

EmailService.java - add attachment support
if (emailMessage.getAttachmentContent() != null) {
    MandrillMessage.MessageContent messageContent = new MandrillMessage.MessageContent();
    messageContent.setContent(emailMessage.getAttachmentContent());
    messageContent.setBinary(true);
    messageContent.setName(emailMessage.getAttachmentName());
    message.setAttachments(Collections.singletonList(messageContent));
}

Including attachments in email is bit tricky when it comes to MandrillAPI, as we have to convert the attachment content to base64 format first.

To convert file content to Base64 format, we can leverage the following Java 8 based code:

convertFileToBase64 - converts byte array to base64
private String convertFileToBase64(byte[] input) {
    return Base64.getEncoder().encodeToString(input);
}

Now we can make changes in the client code to include attachment and trigger the email.

Client code to send atatchment
public void sendEmail() throws IOException {
    final EmailMessage message = EmailMessageBuilder.anEmailMessage()
            .withSubject("test")
            .withBody("this is a test body")
            .withTo("foo@email.com")
            .withFrom("mandrill@foo.bar")
            .withAttachmentName("foo-bar.pdf")
            .withAttachmentContent(convertFileToBase64(Files.readAllBytes(Paths.get("foo-bar.pdf"))))
            .build();
    emailService.sendEmail(message);
}

Error handling and retry attempts

When it comes to HTTP communication, error can be obvious due to many reasons beyond our control. For example, network connection may become flaky or the remote service may become unavailable momentarily.

To know the exact reason for failure, MandrillAPI provides proper failure reason, which can be obtained using the below exception.

catch (MandrillApiError mandrillApiError) {
    logger.error("MandrillApiError: " + mandrillApiError.getMandrillErrorAsJson(), mandrillApiError);
}

getMandrillErrorAsJson() method returns the exact failure reason from Mandrill server.

if the failure is transient (which can be recovered by a retry, and not related to account) and is worth retrying, we can utilize Spring retry module to retry the attempt automatically with exponential backoff strategy.

MandrillConfig.java - adding retry support from spring
@Configuration
@EnableRetry    (1)
public class MandrillConfig {


}
1 this will enable retry logic using spring-retry module.

Finally we need to modify our EmailSerive.sendEmail(…​) method a bit to include the retry annotation.

EmailService.java - added retry annotation
@Retryable(maxAttempts = 3, value = {Exception.class}, backoff = @Backoff(value = 60000, multiplier = 2))
public void sendEmail(EmailMessage emailMessage) {

}

that’s all for this article.


Top articles in this category:
  1. Spring Boot with GMAIL SMTP
  2. Top 50 Spring Interview Questions
  3. Multi-threading Java Interview Questions for Investment Bank
  4. Sapient Global Market Java Interview Questions and Coding Exercise
  5. Hibernate & Spring Data JPA interview questions
  6. Goldman Sachs Java Interview Questions
  7. Cracking core java interviews - question bank

Recommended books for interview preparation:

Find more on this topic:
Buy interview books

Java & Microservices interview refresher for experienced developers.