Amazon Simple Email Service (SES) is a cloud-based email sending service that allows you to add email functionality to any application. Using Amazon SES, you send transactional emails, marketing messages, or any other kind of notification emails to your customers. Built on the reliable and scalable infrastructure of Amazon Web Services (AWS), it is a cost-effective service for companies of all sizes that use email to communicate with their customers.

I have been using Amazon SES for sending different kinds of emails for my projects including order confirmations, welcome emails, occasional announcements, and even for weekly newsletters.

In this tutorial, I will teach you how to setup Amazon SES in a Spring Boot project and how to scale it to support hundreds of emails per minute.

Why Amazon SES?

There are number of factors why I chose Amazon SES over other email service providers for integrating emails in my web apps. First of all, it is a cost-effective solution for indie makers who quickly want to launch multiple MVPs (minimal viable products) just to validate their ideas. It only costs $0.10 per thousand email messages. Since it is a pay as you go service, so you only pay for what you use. There are no upfront fees, no minimum charges, and no fixed monthly expenses. Moreover, if your application is hosted in Amazon EC2, the first 62,000 you send every month are free of cost. Since all of my applications are running on Amazon EC2 instance, this free limit was more than enough for sending transactional and marketing emails at the beginning.

Secondly, it is a reliable and scalable service with higher deliverability, thanks to Amazon Web Services multiple datacenters and redundant systems. When I first integrated Amazon SES in StartupBase, I created a special package in the application on top of AWS Java SDK. For new Spring Boot projects, I just need to copy this package into the source code and That’s it! I do not need to write everything from scratch.

In the following sections, I will discuss in details how you can add this package to your web application with code examples.

Setup Amazon SES

To use Amazon SES service in your web project, you must have an AWS account with full access to Simple Email Service (SES). If you are not already signed up, click here to create a new AWS account. The new users are entitled to get 12 months of free tier access to gain free, hand-on experience with the AWS platform, products, and services.

In order to setup Amazon SES for your custom domain, you must have permissions to edit DNS records of that domain name. If you do not have access to DNS records, contact your domain name registrar to get that. In this tutorial, I am going to use a dummy domain name mysestest.com.

Now go to your Amazon SES console, found under Services tab in top bar menu. The SES console looks like following:

SES Console

In the left navigation pane of the Amazon SES console, under Identity Management, click on Domain. To verify a new domain, click on Verify a New Domain button on top left.

SES Domain Verification

Enter the name of your domain in the box (for this tutorial, I entered mysestest.com) and select Generate DKIM Settings. Afterwards, click on Verify This Domain to generate verification and DKIM records as shown below.

SES DNS Management

Now go to your DNS service provider (mostly domain name registrar) and add all required DNS records (TXT, CNAME and MX) to complete the domain verification step. MX records are only required if you want to receive emails via SES. For just sending emails, you can ignore these records. Once these records have been setup, it takes some time to complete the verification.

SES Verified Domain

Once the verification is completed, you will receive a confirmation email from Amazon Web Services. Then you are ready to proceed to next step: integration of Amazon SES in your web application using AWS Java SDK.

Amazon SES integration with Spring Boot

Now that we have verified Amazon SES for our domain mysestest.com, we move on to integrating this with our Spring Boot web application. If you want to create your own Spring Boot-based project, go to Spring Initializr and create a new Gradle project with Java and Spring Boot. With Amazon SES, you can send an email in three ways: using the console, using the SMTP interface, or using the API. This tutorial uses Amazon SES API to send emails programmatically. Before you use API to send emails, you need to create AWS access keys. For more information, please check getting AWS access keys.

We will build a simple Spring Boot application and add Amazon SES to it. For this guide, I assume that you are familiar with the tools required to run a Spring Boot application. At minimum, you need JDK 1.8 or later to compile the source code, a text editor or IDE to edit the files and Gradle 4+ for building web application. Although you can use any build system you like to build Spring Boot apps, for this guide, I am going to use Gradle. If you are not familiar with it, refer to Building Java Projects with Gradle.

Create a directory structure

First create a project directory ses-spring-boot in the file system and then create the following sub-directory structure inside. You can do both in one command mkdir -p ses-spring-boot/src/main/java/app/ on *nix systems.

└── src
    └── main
        └── java
            └── app

Create a Gradle build file

Create gradle.build file in project root directory and copy the following Groovy code into it:

build.gradle

buildscript {
	ext {
		springBootVersion = '2.0.4.RELEASE'
	}
	repositories {
		mavenCentral()
	}
	dependencies {
		classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
	}
}

apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'org.springframework.boot'
apply plugin: 'io.spring.dependency-management'

group = 'com.attacomsian'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = 1.8

repositories {
	mavenCentral()
}

dependencyManagement {
	imports {
		mavenBom 'com.amazonaws:aws-java-sdk-bom:1.11.394'
	}
}

dependencies {
	compile('org.springframework.boot:spring-boot-starter-web')
	testCompile('org.springframework.boot:spring-boot-starter-test')

	//aws ses sdk
	compile('com.amazonaws:aws-java-sdk-ses')
	//javax required for aws core
	compile('javax.mail:javax.mail-api:1.6.1')
}

Create Amazon SES classes

Now we are ready to add Amazon SES classes to our web application. Create a new folder ses inside app folder and add the following classes:

  • SESForm.java – List all your possible From email addresses in this enum class.
  • AmazonAttachment.java – This classes is used to store attachment files if any.
  • AmazonEmail.java – It stores the email details like To/From email addresses, subject, email body etc.
  • SESProcessor.java – This is the entry class which runs in the background as a thread. It initializes SESWorker.java threads for sending emails and provides methods to add new SESEmail.java objects into the waiting queue. It also performs load balancing among worker threads.
  • SESWorker.java – This is where we send emails programmatically using Amazon SES API. Each worker is a separate thread with own queue of email messages. You can decide how many workers you want to initialize in SESProcessor.java by adjusting MAX_WORKERS variable depending on your requirements.

SESFrom.java

package app.ses;

public enum SESFrom {

    ATTA("atta@mysestest.com", "Atta from My SES Test"),
    NO_REPLY("noreply@mysestest.com", "My SES Test"),
    SUPPORT("support@mysestest.com", "My SES Support Support");

    private final String email;
    private final String name;

    private SESFrom(String email, String name) {
        this.email =email;
        this.name = name;
    }

    public String getEmail() {
        return email;
    }

    public String getName() {
        return name;
    }
}

AmazonAttachment.java

package app.ses;

public class AmazonAttachment {
    
    private String name;
    private byte[] content;
    private String contentType;

    public AmazonAttachment() {
    }

    public AmazonAttachment(String name, byte[] content, String contentType) {
        this.name = name;
        this.content = content;
        this.contentType = contentType;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public byte[] getContent() {
        return content;
    }

    public void setContent(byte[] content) {
        this.content = content;
    }

    public String getContentType() {
        return contentType;
    }

    public void setContentType(String contentType) {
        this.contentType = contentType;
    }
}

AmazonEmail.java

package app.ses;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class AmazonEmail {

    private List<String> to;
    private List<String> cc;
    private List<String> bcc;
    private SESFrom from;
    private String subject;
    private String body;
    private boolean html;
    private List<AmazonAttachment> files;

    public AmazonEmail() {
        this.to = new ArrayList<>();
        this.cc = new ArrayList<>();
        this.bcc = new ArrayList<>();
        this.from = SESFrom.NO_REPLY;
        this.files = new ArrayList<>();
        this.html = true;
    }

    public AmazonEmail(String to, String subject, String body) {
        this.to = new ArrayList<>();
        this.to.add(to);
        this.subject = subject;
        this.body = body;
        this.html = true;
        this.files = new ArrayList<>();
        this.from = SESFrom.NO_REPLY;
        this.cc = new ArrayList<>();
        this.bcc = new ArrayList<>();
    }

    public AmazonEmail(String to, SESFrom from, String subject, String body) {
        this.to = new ArrayList<>();
        this.to.add(to);
        this.from = from;
        this.subject = subject;
        this.body = body;
        this.html = true;
        this.files = new ArrayList<>();
        this.cc = new ArrayList<>();
        this.bcc = new ArrayList<>();
    }

    public AmazonEmail(String to, SESFrom from, String subject, String body, List<String> cc) {
        this.to = new ArrayList<>();
        this.to.add(to);
        this.from = from;
        this.subject = subject;
        this.body = body;
        this.cc = cc;
        this.html = true;
        this.files = new ArrayList<>();
        this.bcc = new ArrayList<>();
    }

    public AmazonEmail(String to, SESFrom from, String subject, String body, List<String> cc, List<String> bcc) {
        this.to = new ArrayList<>();
        this.to.add(to);
        this.from = from;
        this.subject = subject;
        this.body = body;
        this.cc = cc;
        this.bcc = bcc;
        this.html = true;
        this.files = new ArrayList<>();
    }

    public AmazonEmail(List<String> to, String subject, String body) {
        this.to = to;
        this.subject = subject;
        this.body = body;
        this.html = true;
        this.files = new ArrayList<>();
        this.from = SESFrom.NO_REPLY;
        this.cc = new ArrayList<>();
        this.bcc = new ArrayList<>();
    }

    public AmazonEmail(List<String> to, SESFrom from, String subject, String body) {
        this.to = to;
        this.from = from;
        this.subject = subject;
        this.body = body;
        this.html = true;
        this.files = new ArrayList<>();
        this.cc = new ArrayList<>();
        this.bcc = new ArrayList<>();
    }

    public AmazonEmail(List<String> to, String subject, String body, boolean html) {
        this.to = to;
        this.subject = subject;
        this.body = body;
        this.html = html;
        this.files = new ArrayList<>();
        this.from = SESFrom.NO_REPLY;
        this.cc = new ArrayList<>();
        this.bcc = new ArrayList<>();
    }

    public AmazonEmail(List<String> to, SESFrom from, String subject, String body, boolean html) {
        this.to = to;
        this.from = from;
        this.subject = subject;
        this.body = body;
        this.html = html;
        this.cc = new ArrayList<>();
        this.bcc = new ArrayList<>();
    }

    public AmazonEmail(List<String> to, SESFrom from, String subject, String body, boolean html, List<AmazonAttachment> files) {
        this.to = to;
        this.from = from;
        this.subject = subject;
        this.body = body;
        this.html = html;
        this.files = files;
        this.cc = new ArrayList<>();
        this.bcc = new ArrayList<>();
    }

    public List<String> getTo() {
        return to;
    }

    public void setTo(List<String> to) {
        this.to = to;
    }

    public void setTo(String... to) {
        this.to = new ArrayList<>();
        this.to.addAll(Arrays.asList(to));
    }

    public List<String> getCc() {
        return cc;
    }

    public void setCc(List<String> cc) {
        this.cc = cc;
    }

    public void setCc(String... cc) {
        this.cc = new ArrayList<>();
        this.cc.addAll(Arrays.asList(cc));
    }

    public List<String> getBcc() {
        return bcc;
    }

    public void setBcc(List<String> bcc) {
        this.bcc = bcc;
    }

    public void setBcc(String... bcc) {
        this.bcc = new ArrayList<>();
        this.bcc.addAll(Arrays.asList(bcc));
    }

    public String getFrom() {
        return String.format("\"%s\" <%s>", from.getName(), from.getEmail());
    }

    public void setFrom(SESFrom from) {
        this.from = from;
    }

    public String getSubject() {
        return subject;
    }

    public void setSubject(String subject) {
        this.subject = subject;
    }

    public String getBody() {
        return body;
    }

    public void setBody(String body) {
        this.body = body;
    }

    public boolean isHtml() {
        return html;
    }

    public void setHtml(boolean html) {
        this.html = html;
    }

    public List<AmazonAttachment> getFiles() {
        return files;
    }

    public void setFiles(List<AmazonAttachment> files) {
        this.files = files;
    }

    public void setFiles(AmazonAttachment... files) {
        this.files = new ArrayList<>();
        this.files.addAll(Arrays.asList(files));
    }
}

SESProcessor.java

package app.ses;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.LinkedList;
import java.util.Queue;
import java.util.logging.Level;
import java.util.logging.Logger;

public class SESProcessor extends Thread {

    private static final Logger LOG = Logger.getLogger(SESProcessor.class.getName());

    private boolean iCanContinue = true;

    private static SESProcessor sInstance = null;

    private SESWorker[] workers;

    private final Queue<AmazonEmail> queue = new LinkedList<>();

    private final int MAX_SLEEP_TIME = 3 * 60 * 60 * 1000; //3 hours

    private final int MAX_WORKERS = 1;

    private int current = 0;

    public SESProcessor() {
        super("SESProcessor");
        setDaemon(true);
    }

    @Override
    public void run() {
        LOG.log(Level.INFO, "Amazon SES processor is up and running.");
        //initialize workers
        initializeWorkers();
        //start processing
        while (iCanContinue) {
            synchronized (queue) {
                // Check for a new item from the queue
                if (queue.isEmpty()) {
                    // Sleep for it, if there is nothing to do
                    LOG.log(Level.INFO, "Waiting for Amazon SES email to send...{0}", getTime());
                    try {
                        queue.wait(MAX_SLEEP_TIME);
                    } catch (InterruptedException e) {
                        LOG.log(Level.INFO, "Interrupted...{0}", getTime());
                    }
                }
                //distribute tasks among workers
                while (!queue.isEmpty()) {
                    workers[current++].add(queue.poll());
                    current = current % MAX_WORKERS;
                }
            }
        }
    }

    public static synchronized SESProcessor getInstance() {
        if (sInstance == null) {
            sInstance = new SESProcessor();
            sInstance.start();
        }
        return sInstance;
    }

    private void initializeWorkers() {
        LOG.info("Amazon SES workers are initializing ....");
        workers = new SESWorker[MAX_WORKERS];
        for (int i = 0; i < MAX_WORKERS; i++) {
            workers[i] = new SESWorker(i + 1);
            workers[i].start();
        }
    }

    private void stopWorkers() {
        LOG.info("Amazon SES workers are stopping...");
        for (int i = 0; i < MAX_WORKERS; i++) {
            workers[i].stopWorker();
        }
    }

    public void add(AmazonEmail item) {
        synchronized (queue) {
            queue.add(item);
            queue.notify();
            LOG.info("New Amazon SES email added into queue...");
        }
    }

    public static void stopProcessor() {
        if (sInstance == null) {
            return;
        }
        LOG.info("Stopping Amazon SES file processor...");
        try {
            //stop workers first
            sInstance.stopWorkers();
            sInstance.iCanContinue = false;
            sInstance.interrupt();
            sInstance.join();
        } catch (InterruptedException | NullPointerException e) {
            LOG.log(Level.SEVERE, "Exception while stop Amazon SES processor...{0}",
                            e.getMessage());
        }
    }

    /*
     * Get current server date & time
    */
    public String getTime() {
        SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SS");
        return format.format(new Date());
    }
}

SESWorker.java

package app.ses;

import com.amazonaws.auth.AWSCredentialsProvider;
import com.amazonaws.auth.ClasspathPropertiesFileCredentialsProvider;
import com.amazonaws.regions.Regions;
import com.amazonaws.services.simpleemail.AmazonSimpleEmailService;
import com.amazonaws.services.simpleemail.AmazonSimpleEmailServiceClientBuilder;
import com.amazonaws.services.simpleemail.model.*;

import javax.activation.DataHandler;
import javax.activation.DataSource;
import javax.mail.Address;
import javax.mail.BodyPart;
import javax.mail.Session;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeBodyPart;
import javax.mail.internet.MimeMessage;
import javax.mail.internet.MimeMultipart;
import javax.mail.util.ByteArrayDataSource;
import java.io.ByteArrayOutputStream;
import java.nio.ByteBuffer;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.logging.Level;
import java.util.logging.Logger;

public class SESWorker extends Thread {

    private static final Logger LOG = Logger.getLogger(SESWorker.class.getName());

    private boolean iCanContinue = true;

    private final Queue<AmazonEmail> queue = new LinkedList<>();

    private AmazonEmail item;

    private final int MAX_SLEEP_TIME = 3 * 60 * 60 * 1000; //3 hours

    private AmazonSimpleEmailService sesClient;

    private final int id;

    public SESWorker(int id) {
        super("SESWorker-" + id);
        setDaemon(true);
        this.id = id;
    }

    @Override
    public void run() {
        try {
            AWSCredentialsProvider awsCreds = new ClasspathPropertiesFileCredentialsProvider();

            sesClient = AmazonSimpleEmailServiceClientBuilder.standard()
                    .withCredentials(awsCreds)
                    .withRegion(Regions.US_WEST_2) //TODO: make sure you've selected correct region 
                    .build();
        } catch (Exception ex) {
            Logger.getLogger(SESWorker.class.getName()).log(Level.SEVERE, null, ex);
        }

        LOG.log(Level.INFO, "Amazon SES worker-{0} is up and running.", id);

        while (iCanContinue) {
            synchronized (queue) {
                // Check for a new item from the queue
                if (queue.isEmpty()) {
                    // Sleep for it, if there is nothing to do
                    LOG.log(Level.INFO, "Waiting for Amazon SES to send...{0}", getTime());
                    try {
                        queue.wait(MAX_SLEEP_TIME);
                    } catch (InterruptedException e) {
                        LOG.log(Level.INFO, "Interrupted...{0}", getTime());
                    }
                }
                // Take new item from the top of the queue
                item = queue.poll();
                // Null if queue is empty
                if (item == null) {
                    continue;
                }
                try {
                    /*
                     send simple formatted message
                     */
                    if (item.getFiles().isEmpty()) {
                        // Construct an object to contain the recipient address.
                        Destination destination = new Destination().withToAddresses(item.getTo());
                        //set cc & bcc addresses
                        if (item.getCc().size() > 0) {
                            destination.withCcAddresses(item.getCc());
                        }
                        if (item.getBcc().size() > 0) {
                            destination.withBccAddresses(item.getBcc());
                        }
                        // Create the subject and body of the message.
                        Content subject = new Content().withData(item.getSubject());
                        Content textBody = new Content().withData(item.getBody());
                        Body body = item.isHtml() ? new Body().withHtml(textBody) : new Body().withText(textBody);

                        // Create a message with the specified subject and body.
                        Message message = new Message().withSubject(subject).withBody(body);
                        // Assemble the email.
                        SendEmailRequest request = new SendEmailRequest().withSource(item.getFrom())
                                .withReplyToAddresses(item.getFrom())
                                .withDestination(destination)
                                .withMessage(message);
                        // Send the email.
                        sesClient.sendEmail(request);
                    } /*
                     send raw message with attachment
                     */ else {
                        Session session = Session.getDefaultInstance(new Properties());
                        MimeMessage message = new MimeMessage(session);
                        //set subject
                        message.setSubject(item.getSubject(), "UTF-8");
                        //set message receivers
                        message.setFrom(new InternetAddress(item.getFrom()));
                        message.setReplyTo(new Address[]{new InternetAddress(item.getFrom())});
                        //set to address
                        Address[] addresses = new Address[item.getTo().size()];
                        for (int i = 0; i < item.getTo().size(); i++) {
                            addresses[i] = new InternetAddress(item.getTo().get(i));
                        }
                        message.setRecipients(javax.mail.Message.RecipientType.TO, addresses);
                        //set cc addresses if any
                        if (!item.getCc().isEmpty()) {
                            addresses = new Address[item.getCc().size()];
                            for (int i = 0; i < item.getCc().size(); i++) {
                                addresses[i] = new InternetAddress(item.getCc().get(i));
                            }
                            message.setRecipients(javax.mail.Message.RecipientType.CC, addresses);
                        }
                        //set bcc addresses
                        if (!item.getBcc().isEmpty()) {
                            addresses = new Address[item.getBcc().size()];
                            for (int i = 0; i < item.getBcc().size(); i++) {
                                addresses[i] = new InternetAddress(item.getBcc().get(i));
                            }
                            message.setRecipients(javax.mail.Message.RecipientType.BCC, addresses);
                        }

                        // Add a MIME part to the message for body
                        MimeMultipart mp = new MimeMultipart();
                        BodyPart part = new MimeBodyPart();
                        if (item.isHtml()) {
                            part.setContent(item.getBody(), "text/html");
                        } else {
                            part.setText(item.getBody());
                        }
                        mp.addBodyPart(part);
                        //add attachments part of message
                        for (AmazonAttachment file : item.getFiles()) {
                            MimeBodyPart attachment = new MimeBodyPart();
                            DataSource ds = new ByteArrayDataSource(file.getContent(), file.getContentType());
                            attachment.setDataHandler(new DataHandler(ds));
                            attachment.setHeader("Content-ID", "<" + UUID.randomUUID().toString() + ">");
                            attachment.setFileName(file.getName());
                            mp.addBodyPart(attachment);
                        }

                        //set message contents
                        message.setContent(mp);

                        // Send the email.
                        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
                        message.writeTo(outputStream);
                        RawMessage rawMessage = new RawMessage(ByteBuffer.wrap(outputStream.toByteArray()));

                        SendRawEmailRequest rawEmailRequest = new SendRawEmailRequest(rawMessage);
                        sesClient.sendRawEmail(rawEmailRequest);
                    }
                } catch (Exception ex) {
                    LOG.log(Level.SEVERE, "Exception while sending SES email ...{0}",
                            ex.getMessage());
                }
            }
        }
    }

    public void add(AmazonEmail item) {
        synchronized (queue) {
            queue.add(item);
            queue.notify();
            LOG.log(Level.INFO, "New Amazon SES email added into queue for  worker-{0}...", id);
        }
    }

    public void stopWorker() {
        LOG.log(Level.INFO, "Stopping Amazon SES worker-{0}...", id);
        try {
            iCanContinue = false;
            this.interrupt();
            this.join();
        } catch (InterruptedException | NullPointerException e) {
            LOG.log(Level.SEVERE, "Exception while stopping Amazon SES worker...{0}",
                    e.getMessage());
        }
    }

    /*
     * Get current server date & time
     */
    public String getTime() {
        SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SS");
        return format.format(new Date());
    }
}

SESWorker.java class loads AWS access keys from classpath at line 52 to initialize SES client. To authorize Spring Boot application to send emails using Amazon SES, create AwsCredentials.properties file in src/main/resources/ folder which should include user secret key and access key as shown below:

AwsCredentials.properties

#Amazon AWS credentials
#These are dummy values. Replace them with
#acutal access key and secret key.
accessKey=32AABS3SDSF3DABHF3DF3
secretKey=sd3sdf55sdfd445+ghjh44s+sdfdsd

Create web controller and application classes

Now you can create a simple web controller (AppController.java) for handling HTTP requests in app folder:

AppController.java

package app;

import app.ses.AmazonEmail;
import app.ses.SESFrom;
import app.ses.SESProcessor;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class AppController {

    @RequestMapping("/")
    public String index() {

        //send Email using default NO_REPLY from email
        SESProcessor.getInstance().add(new AmazonEmail(
        "hi@atta.me",
        "Hey Atta",
        "We have an offer for you :)"));

        //send Email using ATTA from email
        SESProcessor.getInstance().add(new AmazonEmail(
        "hi@atta.me",
        SESFrom.ATTA,
        "Hey Atta",
        "We have an offer for you :)"));

        return "Emails Sent!";
    }
}

After this, it is time to create an application class in app folder that includes the main() method to launch the Spring Boot application. This classes is named Application.java:

Application.java

package app;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class Application {

	public static void main(String[] args) {
		SpringApplication.run(Application.class, args);
	}
}

Run the application

To run the application, execute the following command:

$ ./gradlew build && java -jar build/libs/ses-spring-boot-0.0.1-SNAPSHOT.jar

At this point, our Amazon SES integration with Spring Boot is completed and emails can be sent using AWS Java SDK API. The index() method of AppController.java class contains two examples of how you can use SESProcessor.java to send emails from anywhere in the application. The complete source code of this tutorial is available on GitHub. You can download it from here.

Source code: You can download the complete source code from GitHub available under MIT license.

Summary

In this tutorial, we learned how to use Amazon SES to send emails in Spring Boot application. Amazon SES is the cheapest service for sending large number of transactional, marketing, and notification emails to the customers. It is a reliable service built on top of Amazon Web Services infrastructure. It scales well when the application grows bigger and requires to send a large number of emails.

I hope this blog post was helpful to you to understand what is Amazon SES and how it can be used in Java applications for email communication.

Want to integrate AWS SES in Node.js application? Follow this tutorial for step-by-step instructions.

✌️ Like this article? Follow @attacomsian on Twitter. You can also follow me on LinkedIn and DEV. Buy me a coffee (cost $3)

Need help to start a new Spring Boot or MEAN stack project? I am available for contract work. Hire me to accomplish your business goals with engineering and design. Let’s talk about your project: hi@attacomsian.com.