Sending Emails With AWS SES
Wednesday, May 29, 2019
So in my last post I mentioned using your event bus to send emails. I skimmed over the implementation though. I wanted to write a follow up about how you can send emails with SES quite easily. It’s how we send all our emails at DropConfig
SES
AWS SES stands for Simple Email Service. The name is fairly accurate. I’m also not going to go into specifics about setting up your account. AWS has some good guides
Anyway let’s get into it.
We break down our email sending into two parts triggered by events.
- Fetching and marshaling the data for an email from a given event.
- Sending the actual email.
For example.
We may get an event come in
USER_COMMENTED_ON_POST
first thing we do is lookup an email in our email triggers
const emailTriggers = {
"USER_COMMENTED_ON_POST": {
"to": "user.email",
templateName: "user-commented"
}
}
Our trigger has a few parts. We have a JSON path to look up in the event data for who to send the email to. Then we have an email template which might look something like this.
{
"Subject": "A user commented on your post",
"Body": "hello {{user.name}} a user commented on your post {{post.link}}"
}
We use mustache for templates in our emails. Also you could make your email body have HTML tags etc.
So we look up in our email trigger list and put together and email
const trigger = emailTriggers[event.type];
if(!trigger){
//We don't have a trigger so we don't care
return true
}
//Here we are fetching the template. Maybe we store these in their own DropConfig?
const template = await loadTemplateFromName(trigger.templateName);
//Using lodash here because it can lookup by a path string
const to = _.get(task.data.data, trigger.to);
if(to && template){
const body = mustache.render(template.Body, task.data.data);
const subject = mustache.render(template.Subject, task.data.data);
const params = {
Destination: {
toAddresses: [to]
},
Source: "sputnik@dropconfig.com",
Message: {
Body: {
Html: {
Data: body
}
},
Subject: {
Data: subject
}
}
}
}
Next we do create a
send-email
event and put the params in there.
server.createEvent("send-email", params);
Why do we not just send the email?
The big reason is: Imagine you have many different emails to send based off of a single event. If a user comments on a post you might want to send an email to the owner of the post. But also a different email to other commenters. Now if sending to the owner succeeds but sending to the commenters fails we have a problem. Without splitting the sending of emails into two distinct events. We would re-run the event and notify the people we sent to successfully over and over (or as many times as we retry).
If we split it into two steps only the failed emails ever get retried.
Now that we have created the
send-email
event. It is a breeze to actually send the email.
if(task.data.type === "send-email"){
try {
const params = task.data.data;
const res = await ses.sendEmail(params).promise();
return true;
} catch(e){
return false;
}
}
We don’t need much more logic than that!
Putting it all together.
//This is a task runner as explained in my previous post.
exports.handler = async (task, queue, sqs, server) => {
const emailTriggers = {
"USER_COMMENTED_ON_POST": {
"to": "user.email",
templateName: "user-commented"
}
}
if(task.data.type === "send-email"){
try {
const params = task.data.data;
const res = await ses.sendEmail(params).promise();
return true;
} catch(e){
return false;
}
}
const trigger = emailTriggers[event.type];
if(!trigger){
//We don't have a trigger so we don't care
return true
}
//Here we are fetching the template. Maybe we store these in their own DropConfig?
const template = await loadTemplateFromName(trigger.templateName);
//Using lodash here because it can lookup by a path string
const to = _.get(task.data.data, trigger.to);
if(to && template){
const body = mustache.render(template.Body, task.data.data);
const subject = mustache.render(template.Subject, task.data.data);
const params = {
Destination: {
toAddresses: [to]
},
Source: "sputnik@dropconfig.com",
Message: {
Body: {
Html: {
Data: body
}
},
Subject: {
Data: subject
}
}
}
}
}
Thanks for reading this far. Checkout https://dropconfig.com for awesome version control and hosting of your config files.
Let me know if you have any questions. I might be able to help!