How to Use AWS SES (Simple Email Service) to Send and Receive Email's For Your Website

How to Use AWS SES (Simple Email Service) to Send and Receive Email's For Your Website

Pre-requisites: You must own a domain and have control of the DNS records

💡
AWS provides a service called WorkMail which can be used to send and receive emails from your domain. However, WorkMail costs approximately $4/month per domain and doesn't allow wildcard addresses (i.e. anything@your-domain.com). The solution below utilizes SES, S3, SNS, and Lambda to allow for sending and receiving emails for as little as pennies a month. 

Table of Contents

Part 1: Set up SES Sending
Part 2: Set up SES Receiving
Part 3: Set up Lambda Function + SNS(optional)


Part 1: Set up SES Sending

💡
You can send emails from any region, however you can only receive in a handful. You can technically send from one and receive in another, but it's easier to do both from the same region. Select a region and check the navigation bar on the left hand side, if it doesn't say "Email receiving" at the bottom, pick a new region until you find one that has it.
  1. Navigate to SES within the AWS console
  2. From the main page of SES click Create Identity
  3. Select Domain and enter your domain, i.e. simscyberops.com
  4. Leave everything else default and click Create Identity
💡
If you use AWS Route 53 to mange your domain's DNS records, verification will happen automatically. If not, we'll complete verification in the next step.
  1. Once you create identity it should automatically bring you to a page with details about the SES object, if not, click Verified identities on the left and click on your domain
  2. About halfway down the page is the DomainKeys Identified Mail (DKIM) section, within it is 3 CNAME records which you need to add to your domain's DNS records
  3. Once you've added the 3 CNAME records refresh the page until the Summary section shows Identity status Verified
  4. Now that you've verified your domain you can send test emails from anything@your-domain.com, the issue is you cannot send any emails to non-verified accounts. Navigate to the Account Dashboard and you'll see a warning stating that your SES account for that region is in the sandbox. Click the button on the right that says Request production access
  5. On this page you'll need to enter some details about what your website is and what types of emails you plan on sending with AWS SES. Submit the request and you'll likely immediately get a notification saying the request has been denied, this is just an automatic response in which they request more information that is commonly left out in the initial request. Answer the follow-up questions with as much detail as possible and submit the additional correspondence. Approval for getting out of the sandbox can take 1-2 days so you can either stop here and come back, or finish the rest of the configuration with the knowledge that some things won't work until the request is approved.
  6. In the next step you'll generate SMTP credentials which you can use in a variety of ways to set up your website to be able to send emails. For example, I use Ghost to to host this website, and within a configuration file for ghost I have the following information:
 "mail": {
  "transport": "SMTP",
  "options": {
    "host": "<YOUR-SES-SERVER-NAME i.e. email-smtp.us-east-1.amazonaws.com>",
        "port": 465,
        "service": "SES",
        "auth": {
            "user": "<SES-ACCESS-KEY-ID>",
            "pass": "<SES-SECRET-ACCESS-KEY>"
    }
  }
},

Your website may have similar configuration files, or a variety of other setups which are capable of taking similar paremeters to be able to send automatic emails for things like account verification, password resets, newsletters, etc..
11. In the SES Account dashboard click Create SMTP credentials in the middle of the page
12. This process will create an IAM user automatically, on this page you can rename it to something like my-domain-ses so that it's recognizable in IAM
13. Click Create and either make note of the credentials it gives you, or download the .csv with the credentials
14. Assuming you've been approved to get out of the sandbox, you now have everything you need for your website to send emails from anything@your-domain.com
15. In the JSON example for Ghost configuration shown above you would replace the value of "user" and "pass" with your newly created credentials (and probably restart the process/service
16. Once this is complete your website should be able to send emails automatically to users for a variety of reasons such as account confirmations, password resets, etc..


Part 2: Set up SES Receiving

  1. In the SES console navigate to Email receiving on the left hand side
  2. Click Create rule set and give it an identifying name. You can only have one rule set active in any particular region, so if you have multiple domains you can name it something a little general like domain-email-receiving
  3. Within the rule set click Create rule, repeat the process if you have multiple domains
  4. Name the rule something like your-domain-receiving and leave everything else then click next
  5. Click Add new recipient condition and enter your-domain.com (nothing should preceed your domain, i.e. just simscyberops.com, NOT admin@simscyberops.com or https://simscyberops.com/) then click Next
  6. Here we can add actions for what you want to happen when an email is received, if you want to pay a few dollars a month to use AWS WorkMail you can clik Add new action and select it, for this configuration I used S3, A Lambda function, and SNS, continue if you would like to set up the same configuration
  7. Click down on Add new action and select Deliver to S3 bucket then Create an S3 bucket with the button that appears
  8. Click Next and review your rule then click Create rule
  9. Once you've finished creating the rule, ensure that the Rule set is Active
  10. You're now set up to receive emails to any address @your-domain.com!
💡
The SES action for delivering emails to an S3 bucket dumps it in there with a randomized ID as the name, if this is perfectly acceptable to you then you can stop here. If not, we'll create a Lambda function in the next step to remedy that. Once the Lambda function is created you'll come back here and edit the rule to add another SES action which triggers the Lambda function.

Part 3: Set up Lambda Function + SNS (optional)

💡
The Lambda function in this step takes care of a few things, it's triggered by SES whenever an email is received and finds the newly created email in S3, renames it with useful information, then sends information over to SNS so you can subscribe to receive notifications that your domain has received an email. 
  1. If you're going to set up a notification using SNS the easiest thing is to start by creating the topic, if you don't care about that you can skip to step 7
  2. Navigate to SNS in the AWS console, go to Topics and click Create topic
  3. Change the Type to Standard, name the topic something like my-domain-sns and optionally configure any of the other portions then click Create topic
  4. Make note of the ARN generated for the new SNS topic which we'll use later
  5. Click Create Subscription and then click down on Protocol and select the appropriate protocol for how you would like to subscribe, i.e. Email for subscribing with an email, SMS for subscribing with a phone number
  6. Enter the appropriate information for your subscription and click Create subscription
  7. Navigate to Lambda within the AWS and click Create Function
  8. Leave the selection on Author from scratch, name your function, change the Runtime language to Python (Python 3.9 is the most current as of the writing of this post)
  9. Leave everything else default and click Create function
  10. Navigate into your function and find the portion where you can edit the Code source, delete whatever is there and past in the following code:
import boto3
import json

# event will be JSON from SES incoming email rule - NOT an S3 PUT event
def lambda_handler(event, context):

    try:
        ses_mail = event['Records'][0]['ses']['mail']
        message_id = ses_mail['messageId']
        print('Commencing processing for message {}'.format(message_id))
        timestamp = ses_mail['timestamp']
        source = ses_mail['source']
        newname = timestamp+"_"+source+"_.eml"
    except:
        print('mail read broke')
        
    try:
        s3 = boto3.resource('s3')
        copy_source = {'Bucket': 'your-s3-bucket-name','Key': message_id}
        bucket = s3.Bucket('your-s3-bucket-name')
        obj = bucket.Object(newname)
        obj.copy(copy_source)
    except:
        print('copy broke')
        
    try:
        s3 = boto3.resource('s3')
        s3.Object('your-s3-bucket-name',message_id).delete()
    except:
        print('delete broke')
    
    try:
        mailTo = ses_mail['commonHeaders']['to']
        mailFrom = ses_mail['commonHeaders']['from']
        mailDate = ses_mail['commonHeaders']['date']
        mailSubject = ses_mail['commonHeaders']['subject']
        
        message = f"Message received for {mailTo}, from {mailFrom}, on {mailDate}, with subject {mailSubject}."
        
        client = boto3.client('sns')
        response = client.publish(
            TargetArn='arn-for-your-sns-topic',
            Message=message,
            Subject='New Email for your domain'
            )
    except:
        print('sns publish broke')
💡
This Python script does a handful of things and you can take/leave whatever is applicable to your build specifically. Overall the function is designed to trigger when SES receives an email, grab the item that's dropped in the S3 bucket, rename it with the timestamp and sender with a file extension of .eml (easier to open it on your machine), then delete the original object, and finally publish a message to SNS.
  1. After pasting the code in there's 4 lines you need to change (3 if you're not using SNS in which case delete the last function). The 3 parts that say 'your-s3-bucket-name' you should replace with your actual bucket name, i.e. 'your-domain-email-receiving' as well as near the bottom replace 'arn-for-your-sns-topic' with the arn from your SNS topic
  2. The next step is to configure permissions so your Lambda function can actually touch the resources it needs. There's a few ways to do this but the easiest in this case is to click the Configuration tab for your Lambda then click Permissions on the left hand side
  3. At the bottom click the Add permissions button under Resource-based policy then click AWS service
  4. In this screen we're going to set up permission for SES to invoke this function, click down on Service and select Other
  5. Under Statement ID give this policy a name, something like AllowSESInvoke
  6. For Principal enter ses.amazonaws.com
  7. For Source ARN you need the ARN from the specific rule set and rule that will be triggering this function, you can find it within SES and it should look something like this: arn:aws:ses:<region>:<your acct number>:receipt-rule-set/your-rule-set-name:receipt-rule/your-rule-name
  8. Finally for Action select lambda:InvokeFunction and click Save
  9. Back in Lambda > Configuration > Permissions, under Execution role click the role that was auto generated for your function, this will take you to the IAM console
  10. Once again, there's a few ways to do this but I'll just walk through two. Click down on Add permissions and then click Create inline policy
  11. The two ways to add permissions here are through the Visual editor or JSON, in either case you need to add 5 permissions: S3: ReplicateObject, PutObject, GetObject, and DeleteObject, then add the ARN for your s3 bucket with a /* at the end to indicate all objects in that bucket
  12. Next add SNS Publish with the ARN for your SNS topic
  13. If you're using the JSON permissions editor start by copying and pasting in the code block below and update the two ARN's for your S3 bucket and SNS topic:
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "s3:ReplicateObject",
                "s3:PutObject",
                "s3:GetObject",
                "sns:Publish",
                "s3:DeleteObject"
            ],
            "Resource": [
                "arn:aws:sns:<region>:<account>:<sns-topic>",
                "arn:aws:s3:::<s3-bucket-name>/*"
            ]
        }
    ]
}
  1. Review the policy and Save Changes
  2. The last step is to head back to SES and tell the rule set to trigger your Lambda along with sending the emails to your S3 bucket
  3. Navigate back to SES > Email Receiving, and click your Rule set then your rule
  4. Click the edit button in the Recipient conditions section
  5. Click Next until you get to Step 3 Add actions, then click Add new action and select Invoke AWS Lambda function
  6. Select your newly created Lambda, leave the Invocation type as Event invocation then click Next and save your changes
  7. You're now set up to receive emails at any address @your-domain and further those emails will be stored in an S3 bucket and automatically renamed in a way where you can keep track of everything without the use of an email client

Conclusion

If you followed this guide all the way here then congratulations! You're now set up to send and receive emails from your domain without the use of email services, which means if you're not expecting large amounts of traffic this all costs you mere pennies a month to operate. If you run into any issues when trying to configure your AWS account to be able to use SES for your domain then please feel free to email admin@simscyberops.com, or troubleshoot@simscyberops.com or help-me@simscyberops.com, or literally anything@simscyberops.com because if you paid attention you know that this configuration allows you to send and receive emails from any address from your domain!