Let's say you have Exim or another email app on a server that you have to maintain (and maintenance of email servers isn't easy). You want to just hand off the work to AWS Simple Email Service.
How many moving pieces do you need?
Well, I love the low complexity of event driven architecture. And in this case, since email will take a while to get to a person's inbox anyway, might as well make an event driven email.
First you'll want to configure SES and start getting it out of sandbox mode (see details here):
setup for SES
Ok, so assuming SES is on it's way, and you've added at least your own email to the whitelist on the sandbox, now we can get into the fun stuff:
- Create a lambda with just a test event
- Create an EventBridge rule to trigger the lambda on certain events of detail-type
- Update the lambda contents to send out the email
- Create the event that will trigger the email sendout... ...from whatever codebase or language you want
Ok, more specifics:
Lambda Hello World
- Create a hello world lambda, any will do for now, just log the event and leave the SES stuff for later.
EventBridge Setup
- Create an EventBus for your application:
- Create a rule against that new bus:
- Sample Event:
{
"version": "0",
"id": "6a7e8feb-b491-4cf7-a9f1-bf3703467718",
"detail-type": "CommandNWEmailRequest",
"source": "aws.apigateway",
"account": "123456789012",
"time": "2017-12-22T18:43:48Z",
"region": "us-west-1",
"resources": ["arn:aws:apigateway:us-west-2::/domainnames/my.domain.com"],
"detail": {
"emailParams": {
"to": "some-test-email@gmail.com",
"from": "some-source-email@example.com",
"subject": "Test event fired via lambda email sendout nwEmailSendout function",
"text": "Raw text: of the email body that is sent in: Test event fired via lambda email sendout nwEmailSendout function",
"html": "<h1>Hi You</h1> <div> <p> Raw text: of the email body that is sent in: Test event fired via lambda email sendout nwEmailSendout function </p></div>"
}
}
}
- Create a matching pattern in the rule, I recommend on the detail-type for application events:
{
"detail-type": ["CommandNWEmailRequest"]
}
Simple is better, so just matching on detail-type is usually quite good enough.
Run the [Test Pattern] button to make sure it matches what you want:
Finally just pick the EventBus for your application that you created earlier.
- Back to the lambda, let's hook up the event rule to the lambda so that the lambda runs when the rule is triggered:
Here is the lambda with a trigger from the eventbridge:
Send a test event to your newly created bus:
https://us-east-1.console.aws.amazon.com/events/home?region=us-east-1#/eventbuses/sendevents?eventBus=NWEventBus
And see if your hello-world lambda logged anything.
Once you have got your lambda being triggered, you're going to have to give the lambda access to the aws-sdk. Use layers for this from the console (or cdk locally, which is out of scope of this).
aws lambda publish-layer-version --layer-name aws-sdk --zip-file fileb://aws-sdk.zip --compatible-runtimes nodejs18.x --description "NW AWS SDK" --profile=YourProfileToUploadHere
Ok, hopefully you have the layer available, you'll need to attach it to the aws function:
Now you should be able to just easily import aws-sdk
in modern js module files .mjs
Here is a lambda for SES sending:
// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
import aws from 'aws-sdk'
const ses = new aws.SES({ region: "us-east-1" });
const validate = (params)=>{
if(!params || params?.length === 0){
throw new Error('No params passed to lambda function to send the email.')
}
const shouldExist = {
to: true,
from: true,
subject: true,
html: true,
text: true,
};
Object.keys(shouldExist).map((key)=>{
if(typeof params?.[key] !== 'string' || params?.[key] === ''){
throw new Error('Cannot send out email with missing or blank parameters')
}
});
}
export const handler = async(event) => {
console.info('Initial event', event);
const incoming = event?.detail?.emailParams;
validate(incoming)
const {
to,
from,
subject,
html,
text
} = incoming;
var params = {
Destination: {
ToAddresses: [to],
},
Message: {
Body: {
Html: {
Charset: 'utf-8',
Data: html,
},
Text: { Data: text},
},
Subject: { Data: subject },
},
Source: from,
};
return ses.sendEmail(params).promise()
};
And here is a test event to fire against the lambda:
{
"version": "0",
"id": "6a7e8feb-b491-4cf7-a9f1-bf3703467718",
"detail-type": "CommandNWEmailRequest",
"source": "aws.apigateway",
"account": "123456789012",
"time": "2017-12-22T18:43:48Z",
"region": "us-west-1",
"resources": [
"arn:aws:apigateway:us-west-2::/domainnames/my.domain.com"
],
"detail": {
"emailParams": {
"to": "roy.ronalds@gmail.com",
"from": "tchalvak@ninjawars.net",
"subject": "Test event fired via lambda email sendout nwEmailSendout function",
"text": "Raw text: of the email body that is sent in: Test event fired via lambda email sendout nwEmailSendout function",
"html": "<h1>Hi Roy</h1> <div> <p> Raw text: of the email body that is sent in: Test event fired via lambda email sendout nwEmailSendout function </p></div>"
}
}
}
You will also need to allow the lambda to run calls against SES, follow the instructions here for that:
https://aws.amazon.com/premiumsupport/knowledge-center/lambda-send-email-ses/
In broad strokes, you'll need to add a policy like this:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"ses:SendEmail",
"ses:SendRawEmail"
],
"Resource": "*"
}
]
}
And attach it to a role, at least to the role that the lambda is using to run:
Email Sent And Received
A Powerful System
You will have a full fledged system on serverless AWS with a ton of different powers coming from SES, including mail bounce measuring, trust measures of whether your email is being flagged as spam too often, and at many email volumes, low to no cost, all driven by event-based triggers.
Top comments (0)