DEV Community

Coco
Coco

Posted on

How to quickly move from Exim4 server-ful email to AWS SES serverless email

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:

created a new event bus

  • Create a rule against that new bus: Creating an event bus rule
  • Sample Event:

Sample Event Creation

  {
    "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>"
      }
    }
  }
Enter fullscreen mode Exit fullscreen mode
  • Create a matching pattern in the rule, I recommend on the detail-type for application events:
{
  "detail-type": ["CommandNWEmailRequest"]
}
Enter fullscreen mode Exit fullscreen mode

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:

Image description

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:

Go to the lambda

pick the trigger

Here is the lambda with a trigger from the eventbridge:

Lambda triggered from 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

Sending a test event

sent event

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

Enter fullscreen mode Exit fullscreen mode

Ok, hopefully you have the layer available, you'll need to attach it to the aws function:

Image description
Image description

Image description

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()
};
Enter fullscreen mode Exit fullscreen mode

And here is a test event to fire against the lambda:

Image description


{
  "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>"
    }
  }
}

Enter fullscreen mode Exit fullscreen mode

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": "*"
        }
    ]
}


Enter fullscreen mode Exit fullscreen mode

And attach it to a role, at least to the role that the lambda is using to run:

Image description

Email Sent And Received

Image description

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.

Heroku

Amplify your impact where it matters most — building exceptional apps.

Leave the infrastructure headaches to us, while you focus on pushing boundaries, realizing your vision, and making a lasting impression on your users.

Get Started

Top comments (0)

AWS Q Developer image

Your AI Code Assistant

Automate your code reviews. Catch bugs before your coworkers. Fix security issues in your code. Built to handle large projects, Amazon Q Developer works alongside you from idea to production code.

Get started free in your IDE