Introduction
AWS SES (Simple Email Service) is a simple-to-setup email sending and receiving service. It is usually difficult, finicky and tedious to manage an on-premise email system, so outsourcing the process is a popular choice.
We can use Amazon SES to send transactional emails, marketing emails, or any other kind of notification emails to our clients. It is an affordable solution for businesses of all sizes who use emails to connect with their customers since it is built on the dependable and scalable infrastructure of Amazon Web Services (AWS).
For sending bulk emails, any application may interact with Amazon SES. We only pay for the emails we actually send, whether they are transactional emails or marketing emails. Additionally, a range of configurations, including dedicated, shared, or owned IP addresses, are supported by Amazon SES. Businesses can make every email count with the use of deliverability dashboards and reports on sender information.
In this guide we are going to configure an AWS SES instance in a region and then integrate with Spring Cloud for AWS which is a sub-project of Spring Cloud. Finally, we'll try out different scenarios in which you may want to send emails from our application.
Note: You can find all the source code used in the guide on GitHub.
Lifecycle of Sending an Email using AWS SES
Let’s look at how the lifecycle of an email sent by an application reaches to destination mailbox:
-
An application, in our case, the Spring Cloud code requests AWS SES to send an email to one or more recipients.
-
SES initially verifies the request, and if it is approved, produces an email message with the request's specifications. This email message has a header, body, and envelope and complies with RFC 5322's Internet Message Format definition.
-
SES then transmits the message to the recipient's receiver over the Internet. As soon as the message is handed off to SES, it is often transmitted right away, with the initial delivery attempt typically taking place in a matter of milliseconds.
-
There are several outcomes at this time. For instance:
- Successful Delivery: The Internet service provider (ISP) accepts the email and sends it to the intended recipient.
- Hard Bounce: Because the recipient's address is invalid, the ISP rejects the email. The ISP sends the hard bounce notification back to Amazon SES, which notifies the sender through email or by publishing it to an Amazon Simple Notification Service (Amazon SNS) topic set up to receive this notification.
- Soft Bounce: Due to conditions like the receiver's inbox being full, the domain not existing, or any passing circumstance like the ISP being too busy to process the request, the ISP might be unable to deliver the email to the recipient. The ISP then retries the email up to a certain number of times and sends SES a soft bounce message. If SES is unable to deliver the email within the specified time frame, it either publishes the event to an SNS topic or sends a hard bounce message via email.
- Complaint: The email is classified as spam by the receiver in their email program. A complaint notification is transmitted to Amazon SES, which then relays it to the sender if Amazon SES and the ISP have a feedback loop established up.
- Auto Response: The recipient ISP notifies Amazon SES of an automated response from the receiver, such as an out-of-office notice, and Amazon SES passes the notification to the sender.
When the delivery is unsuccessful, Amazon SES returns an error to the sender and deletes the email.
Setting Up Amazon SES
Unlike any other AWS Services, there’s practically no need to create an SES instance as all new AWS accounts are placed in the AWS SES sandbox by default. Each AWS account has sandbox access for AWS SES in the available regions by default.
When using sandbox mode, we can only send emails to verified identities. A domain or email address that we use to send an email is a verified identity. We must construct and validate each identity we intend to use as a From
, To
, Source
, Sender
, or Return-Path
address before we can send an email using SES in sandbox mode. By using Amazon SES to verify the identity, we can prove our ownership and stop illegal use.
To avoid fraud and preserve the reputation of an IP address, AWS SES includes email sending limits. These limitations specify the maximum number of emails per second and the daily email limit for each user. By getting in touch with the AWS Support Center, we may establish such quotas by region.
Let’s verify identities. Login to AWS Console and search for “Amazon Simple Email Service”:
Then click on “Create Identity” to add an email or a domain for verification. In our case, we are going to add an email for verification.
Once the identity is created, we can verify the details.
The identity that we created goes into “Verification Pending” stage. At this stage, the user needs to check the verification mail from AWS and follow the instructions to get the email verified.
Next, we need to fetch “access-key” and “secret-key” for the authentication and authorization of our application with SES. In order to generate that, we need to create a User Group and add a User to that group. When we create that User, AWS generates an access-key and secret-key. So let’s redirect to “IAM” in AWS Console and create a User Group.
Then we need to add “AdministratorAccess” permission to that group for SES.
Finally, we will add a User to the above group.
Next, we need to select the group for permissions.
Finally, copy the access-key and secret-key displayed on the screen for further usage.
Sending Emails using Spring Cloud Project
Project Setup
Let’s spin up a Spring Cloud project and run through the use-cases to integrate with SES. The easiest way to start with a skeleton project is via Spring Initializr:
We have added Spring Web for REST MVC, Apache FreeMarker to generate HTML-based email templates, Java Mail Sender to send an email and Lombok (optional boilerplate-reducing library) dependencies. Additionally, we need to add relevant dependencies for Spring Cloud AWS and SES. For Spring Cloud AWS, we will add a separate Spring Cloud AWS BOM in our pom.xml
file using this <dependencyManagement>
block:
<dependencyManagement>
<dependencies>
<dependency>
<groupId>io.awspring.cloud</groupId>
<artifactId>spring-cloud-aws-dependencies</artifactId>
<version>2.3.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
Finally, to add the support for SES, we need to include the module dependency which is available as a starter module spring-cloud-starter-aws-ses
:
<dependency>
<groupId>io.awspring.cloud</groupId>
<artifactId>spring-cloud-starter-aws-ses</artifactId>
</dependency>
spring-cloud-starter-aws-ses
includes the transitive dependencies for spring-cloud-starter-aws
, and spring-cloud-aws-ses
. The spring-cloud-aws-ses
module for SES contains two classes: SimpleEmailServiceMailSender
and SimpleEmailServiceJavaMailSender
.
- The
SimpleEmailServiceMailSender
class utilizes Amazon Simple Email Service to send emails. The Java Mail API is not a requirement for this implementation. It may be used to send straightforward mail messages devoid of attachments. - The
SimpleEmailServiceJavaMailSender
class enables the sending of emails that contain attachments and other mime elements.
So this covers all our basic requirements!
Configuring Beans
As discussed above, we need to define two types of beans: SimpleEmailServiceMailSender
and SimpleEmailServiceJavaMailSender
. We can simply pass the access-key and secret-key as credentials and configure a MailSender
bean which we will use to send emails:
@Configuration
public class SesConfig {
@Value("${cloud.aws.credentials.access-key}")
private String accessKey;
@Value("${cloud.aws.credentials.secret-key}")
private String secretKey;
@Value("${cloud.aws.region.static}")
private String region;
@Bean
public AmazonSimpleEmailService amazonSimpleEmailService() {
BasicAWSCredentials credentials = new BasicAWSCredentials(accessKey, secretKey);
return AmazonSimpleEmailServiceClientBuilder.standard()
.withCredentials(new AWSStaticCredentialsProvider(credentials))
.withRegion(region)
.build();
}
@Bean
public MailSender mailSender(AmazonSimpleEmailService amazonSimpleEmailService) {
return new SimpleEmailServiceMailSender(amazonSimpleEmailService);
}
@Bean
public JavaMailSender javaMailSender(AmazonSimpleEmailService amazonSimpleEmailService) {
return new SimpleEmailServiceJavaMailSender(amazonSimpleEmailService);
}
}
In order to send emails with attachments we need to configure the SimpleEmailServiceJavaMailSender
which is an implementation of the JavaMailSender
interface from Spring’s mail abstraction.
Check out our hands-on, practical guide to learning Git, with best-practices, industry-accepted standards, and included cheat sheet. Stop Googling Git commands and actually learn it!
We will also define the properties to retrieve the information from application.yml
:
cloud:
aws:
region:
static: eu-central-1
auto: false
stack:
auto: false
credentials:
access-key: ********
secret-key: **************************
Sending Simple Email
We can send simple emails using the SimpleEmailServiceMailSender
bean that we configured above. Let’s define a service layer to use this bean:
@Service
public class EmailService {
@Autowired
private MailSender mailSender;
public void sendMessage(SimpleMailMessage simpleMailMessage) {
this.mailSender.send(simpleMailMessage);
}
}
We are calling the send()
method in the MailSender
bean to send our email. We also need to pass the SimpleMailMessage
that would contain attributes like from
, to
, the text and subject for our email. So, let’s define a Controller
class to call the above service using a REST API:
@RestController
public class EmailController {
@Autowired
private EmailService emailService;
@PostMapping("/sendEmail")
public String sendMessage(@RequestBody EmailDetails emailDetails) {
SimpleMailMessage simpleMailMessage = new SimpleMailMessage();
simpleMailMessage.setFrom(emailDetails.getFromEmail());
simpleMailMessage.setTo(emailDetails.getToEmail());
simpleMailMessage.setSubject(emailDetails.getSubject());
simpleMailMessage.setText(emailDetails.getBody());
emailService.sendMessage(simpleMailMessage);
return "Email sent successfully";
}
}
Now, if we run the application and execute the following curl it will send an email to the verified email address:
curl -i -X POST \
-H "Content-Type:application/json" \
-d \
'{
"fromEmail": "[email protected]",
"toEmail": "[email protected]",
"subject": "test email",
"body": "Hi, This is a test email."
}' \
'http://localhost:8080/sendEmail'
Next, we can login to the recipient's email address and verify if the recipient has received the email.
Sending Simple Email with Attachment
We will define a service layer to pass the attachment as mime and set the other email attributes like from
, to
, text and subject:
@Service
public class EmailService {
@Autowired
private JavaMailSender javaMailSender;
public void sendMessageWithAttachment(SimpleMailMessage simpleMailMessage) {
try {
MimeMessage message = javaMailSender.createMimeMessage();
// Set mediaType
MimeMessageHelper helper = new MimeMessageHelper(
message,
MimeMessageHelper.MULTIPART_MODE_MIXED_RELATED,
StandardCharsets.UTF_8.name());
// Add attachment
helper.addAttachment("logo.png", new ClassPathResource("logo.png"));
helper.setTo(Objects.requireNonNull(simpleMailMessage.getTo()));
helper.setText(Objects.requireNonNull(simpleMailMessage.getText()));
helper.setSubject(Objects.requireNonNull(simpleMailMessage.getSubject()));
helper.setFrom(Objects.requireNonNull(simpleMailMessage.getFrom()));
javaMailSender.send(message);
} catch (MessagingException e) {
System.err.println("Exception: " + e.getMessage());
}
}
}
Here we are using MimeMessageHelper
to create an email with an attachment. Finally, we will define Controller
layer to pass the SimpleMailMessage
attributes:
@RestController
public class EmailController {
@Autowired
private EmailService emailService;
@PostMapping("/sendEmailWithAttachment")
public String sendMessageWithAttachment(@RequestBody EmailDetails emailDetails) {
SimpleMailMessage simpleMailMessage = new SimpleMailMessage();
simpleMailMessage.setFrom(emailDetails.getFromEmail());
simpleMailMessage.setTo(emailDetails.getToEmail());
simpleMailMessage.setSubject(emailDetails.getSubject());
simpleMailMessage.setText(emailDetails.getBody());
emailService.sendMessageWithAttachment(simpleMailMessage);
return "Email sent successfully";
}
}
Now, if we run the application and execute the following curl it will send an email to the verified email address:
curl -i -X POST \
-H "Content-Type:application/json" \
-d \
'{
"fromEmail": "[email protected]",
"toEmail": "[email protected]",
"subject": "test email",
"body": "Hi, This is a test email with attachment."
}' \
'http://localhost:8080/sendEmailWithAttachment'
Next, we can login to the recipient's email address and verify if the recipient has received the email.
Sending Template Email with Attachment
The previous use-cases that we had seen are good for development or test scenarios but in production, we generally use an email template with variables that would be replaced using an API's responses. We had earlier added the dependency for Apache FreeMarker. We will use it to define a template and load it to process!
For this, let’s first define a simple template, name it as email-template.ftl
and place it in templates
folder under resources
:
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>StackAbuse Email</title>
</head>
<body>
<table width="100%" border="0" cellspacing="0" cellpadding="0">
<tr>
<td align="center" valign="top" bgcolor="#838383"
style="background-color: #838383;"><br> <br>
<table width="600" border="0" cellspacing="0" cellpadding="0">
<tr>
<td align="center" valign="top" bgcolor="#d3be6c"
style="background-color: #d3be6c; font-family: Arial,
Helvetica, sans-serif; font-size: 13px; color: #000000;
padding: 0px 15px 10px 15px;">
<div style="font-size: 48px; color:blue;">
<b>StackAbuse</b>
</div>
<div style="font-size: 24px; color: #555100;">
<br> Sending Email using Spring Cloud with <b>FreeMarker</b> template !!! <br>
</div>
<div>
<br> Want to learn a new technology or become an in-demand full-stack developer?<br>
<br> We teach the skills you need to level up in your career.<br>
<br>"Sharing knowledge is the biggest learning" <br> <br>
<br> <br> <b>${Name}</b><br>${location}<br>
<br>
</div>
</td>
</tr>
</table> <br> <br></td>
</tr>
</table>
</body>
</html>
Next, we need to define a configuration class to load the template from the path and add a bean. For this, we will define FreeMarkerConfigurationFactoryBean
:
@Configuration
public class FreemarkerConfig {
@Primary
@Bean
public FreeMarkerConfigurationFactoryBean factoryBean() {
FreeMarkerConfigurationFactoryBean bean = new FreeMarkerConfigurationFactoryBean();
bean.setTemplateLoaderPath("classpath:/templates");
return bean;
}
}
Next, we will define our service layer to load this template and create a final message to send to SES:
@Service
public class EmailService {
@Autowired
private JavaMailSender javaMailSender;
@Autowired
private Configuration config;
public void sendTemplateMessageWithAttachment(SimpleMailMessage simpleMailMessage) {
try {
MimeMessage message = javaMailSender.createMimeMessage();
// Set mediaType
MimeMessageHelper helper = new MimeMessageHelper(
message,
MimeMessageHelper.MULTIPART_MODE_MIXED_RELATED,
StandardCharsets.UTF_8.name());
Template t = config.getTemplate("email-template.ftl");
Map<String, Object> model = new HashMap<>();
model.put("Name", "StackAbuse Admin");
model.put("location", "Bangalore, India");
String html = FreeMarkerTemplateUtils.processTemplateIntoString(t, model);
// Add attachment
helper.addAttachment("logo.png", new ClassPathResource("logo.png"));
helper.setTo(Objects.requireNonNull(simpleMailMessage.getTo()));
helper.setText(html, true);
helper.setSubject(Objects.requireNonNull(simpleMailMessage.getSubject()));
helper.setFrom(Objects.requireNonNull(simpleMailMessage.getFrom()));
javaMailSender.send(message);
} catch (MessagingException | IOException | TemplateException e) {
System.err.println("Exception: " + e.getMessage());
}
}
Finally, we will define a Controller
layer to pass the dynamic email attributes:
@RestController
public class EmailController {
@Autowired
private EmailService emailService;
@PostMapping("/sendTemplateEmailWithAttachment")
public String sendTemplateMessageWithAttachment(@RequestBody EmailDetails emailDetails) {
SimpleMailMessage simpleMailMessage = new SimpleMailMessage();
simpleMailMessage.setFrom(emailDetails.getFromEmail());
simpleMailMessage.setTo(emailDetails.getToEmail());
simpleMailMessage.setSubject(emailDetails.getSubject());
simpleMailMessage.setText(emailDetails.getBody());
emailService.sendTemplateMessageWithAttachment(simpleMailMessage);
return "Email sent successfully";
}
}
Now, if we run the application and execute the following curl it will send an email to the verified email address:
curl -i -X POST \
-H "Content-Type:application/json" \
-d \
'{
"fromEmail": "[email protected]",
"toEmail": "[email protected]",
"subject": "test email",
"body": "Hi, This is a test template email with attachment."
}' \
'http://localhost:8080/sendTemplateEmailWithAttachment'
Next, we can login to the recipient's email address and verify if the recipient has received the email:
Sending Personalized Email using Templates in AWS SES
In the previous use-case we used a static template to send emails. How can we enable templates to be designed dynamically for different purposes and different types of recipients? AWS SES allows us to create email templates to send personalized emails to one or more destinations in a single operation.
We can create up to 10,000 email templates per Amazon SES account. Each template can be up to 500KB in size, including both the text and HTML parts. We can send up to 50 destinations in each call.
So let’s quickly create an email template. First, we can define a JSON file using the following template:
{
"Template": {
"TemplateName": "MyTemplate",
"SubjectPart": "Greetings from {{name}}!",
"HtmlPart": "<html xmlns=\"http://www.w3.org/1999/xhtml\"><head><meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\"><title>StackAbuse Email</title></head><body><table width=\"100%\" border=\"0\" cellspacing=\"0\" cellpadding=\"0\"><tr><td align=\"center\" valign=\"top\" bgcolor=\"#838383\" style=\"background-color:#838383\"><br><br><table width=\"600\" border=\"0\" cellspacing=\"0\" cellpadding=\"0\"><tr><td align=\"center\" valign=\"top\" bgcolor=\"#d3be6c\" style=\"background-color:#d3be6c;font-family:Arial,Helvetica,sans-serif;font-size:13px;color:#000;padding:0 15px 10px 15px\"><div style=\"font-size:48px;color:#00f\"><b>StackAbuse</b></div><div style=\"font-size:24px;color:#555100\"><br>Sending Email using Spring Cloud with AWS SES Email template !!!<br></div><div><br>Want to learn a new technology or become an in-demand full-stack developer?<br><br>We teach the skills you need to level up in your career.<br><br>\"Sharing knowledge is the biggest learning\"<br><br><br><br><b>{{name}}</b><br>{{location}}<br><br></div></td></tr></table><br><br></td></tr></table></body></html>",
"TextPart": "Dear {{name}},\r\nHere is your StackAbuse Email."
}
}
This template contains the following attributes:
- TemplateName: This contains the name of the template.
- SubjectPart: This holds the email's subject line. Replacement tags might be present on this asset. These tags are formatted as follows:
{{tagname}}
. You can enter a value for{{tagname}}
for each destination when sending the email. - HtmlPart: This contains the HTML body of the email and it can also contain replacement tags.
- TextPart: This represents the email's text body. This version of the email is sent to recipients whose email clients do not view HTML emails. Replacement tags might be present on this asset.
We can save this file as mytemplate.json
. Finally we can use an AWS CLI command to create the template as follows:
$ aws ses create-template --cli-input-json file://mytemplate.json
Next, let’s define a service layer to define attributes and send templated emails:
@Service
public class EmailService {
@Autowired
private AmazonSimpleEmailService simpleEmailService;
public void sendTemplatedMessage(SimpleMailMessage simpleMailMessage) {
Destination destination = new Destination();
List<String> toAddresses = new ArrayList<>();
String[] emails = simpleMailMessage.getTo();
Collections.addAll(toAddresses, Objects.requireNonNull(emails));
destination.setToAddresses(toAddresses);
SendTemplatedEmailRequest templatedEmailRequest = new SendTemplatedEmailRequest();
templatedEmailRequest.withDestination(destination);
templatedEmailRequest.withTemplate("MyTemplate");
templatedEmailRequest.withTemplateData("{ \"name\":\"StackAbuse Admin\", \"location\": \"Bangalore, India\"}");
templatedEmailRequest.withSource(simpleMailMessage.getFrom());
simpleEmailService.sendTemplatedEmail(templatedEmailRequest);
}
}
We can add multiple Destination
addresses to send bulk emails to multiple recipients. We are using the sendTemplatedEmail()
method from the AmazonSimpleEmailService
interface to send this templated email. We also need to pass the replacement tags to be replaced in the HTML text of our template that we created earlier.
Finally, we will define a Controller
layer to define the REST API to pass the attributes:
@RestController
public class EmailController {
@Autowired
private EmailService emailService;
@PostMapping("/sendAWSTemplatedEmail")
public String sendTemplatedMessage(@RequestBody EmailDetails emailDetails) {
SimpleMailMessage simpleMailMessage = new SimpleMailMessage();
simpleMailMessage.setFrom(emailDetails.getFromEmail());
simpleMailMessage.setTo(emailDetails.getToEmail());
simpleMailMessage.setSubject(emailDetails.getSubject());
simpleMailMessage.setText(emailDetails.getBody());
emailService.sendTemplatedMessage(simpleMailMessage);
return "Email sent successfully";
}
}
Next, when we run the app, we can execute the following curl
to send templated emails:
curl -i -X POST \
-H "Content-Type:application/json" \
-d \
'{
"fromEmail": "[email protected]",
"toEmail": "[email protected]",
"subject": "Greetings from StackAbuse Admin",
"body": "Hi, This is an AWS templated email."
}' \
'http://localhost:8080/sendAWSTemplatedEmail'
Now the recipient finally gets to see a templated email:
Request Production Access
Finally, in order to send emails to any recipient, regardless of whether the receiver's address or domain is validated, we must ultimately take our account out of the sandbox. All of our identities, including From
, Source
, Sender
, and Return-Path
addresses, must still be verified. We can submit a request for Production access from the “Account Dashboard” page as follows:
We can submit the request filling up all the above details from the AWS Console. The same can also be submitted using AWS CLI. This would be helpful when we need to request access for a large number of identities and would like to automate the process.
Conclusion
The key ideas of Amazon Simple Email Service (SES) and the libraries offered by Spring Cloud AWS to interface with it were covered in this article. Additionally, we created a Spring Boot application with a REST API that can send emails through the Spring Cloud AWS SES module.
You should now have a solid understanding of what Amazon Simple Email Service (SES) is and how to utilize it to send emails.