How to set up a custom email domain through SES and SNS using (mostly) CloudFormation
Tags: aws, email, tutorial, programming
Published: 2023-01-11 19:18:30

**Prerequisites:**

- Have an AWS account
- Have a domain
- Some familiarity with CloudFormation

For one of my pet projects, [BiteMe (an online multiplayer snake game)](www.startbite.me), I wanted to have a custom email domain to receive emails without the need to set up one through paid service (I checked, you have to pay [6$/month](https://workspace.google.com/intl/en/pricing.html?v&gclid=CjwKCAiAqt-dBhBcEiwATw-ggOK4kcHj0hSrU2uSZ4corfGWbx6txcgiEFmUziaMZpBw96sTpq-KjBoCKuIQAvD_BwE&gclsrc=aw.ds) through google business). Considering that this is more than my whole AWS bill, I knew there must be another option. 

This article will show you how to set up a POC using AWS Simple Email Service and AWS Simple Notification Service using CloudFormation (apart from two manual steps), which should get you a custom email domain that is virtually free. And you get to learn how to use SES and SNS. 

### Some terminology

**Simple Email Service (SES)**
[SES](https://aws.amazon.com/ses/) is mostly known for sending emails but for 3 regions (us-west-2, us-east-1, eu-west-1) it can also receive emails. 

![SES email receiving regions](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hotg3ifplwns06aaa91m.png)


**Simple Notification Service (SNS)**
[SNS](https://aws.amazon.com/sns/) is a service that can send notifications either to other applications or to end user through email, sms or a push notification.

**CloudFormation (CF)**
[CloudFormation ](https://aws.amazon.com/cloudformation/) is an IaaC service provided by AWS. It allows for resource provisioning through a yaml or json file. 

> Tip💡: Use the CloudFormation service in AWS console to check your deployment status. Deploy to AWS step by step to isolate errors. Always check that the resource has been created in the service.

---

## The steps are:

- Create an email identity
- Verify that the domain we want to use is indeed ours
- Set up an MX record to receive emails 
- Create an SNS Topic
- Create a rule that would say what to do with the email once its received

To see how to do it in a console, [follow this link](https://docs.aws.amazon.com/ses/latest/dg/receiving-email.html). 

> Yes, using the console might be easier, but you loose so many benefits - versioning, the possibility of code reviews, integrating it into your CICD, share it with someone, ...  


### 1. Create an email Identity (SES)
First of all, we need to create the Email Identity, which is basically the @mydomain.com:

```yaml
EmailSES:
 Type: AWS::SES::EmailIdentity
 Properties:
   EmailIdentity: 'yourdomain.com'
```

Once deployed, check that an email identity was created in SES.


### 2. Verify the domain (DNS provider, Route53)
In order to verify the domain, we need to add DNS records to our DNS provider. If your DNS provider is Route53, you can use the `Fn::GetAtt` to get the three records and put them directly to your Route53:

> Note that the `EmailSES` is how we named our Email Identity in the previous step.

```yaml
WebsiteDNS:
 Type: AWS::Route53::RecordSetGroup
 Properties:
   HostedZoneId: YOURHOSTEDZONEID
   RecordSets:
     - Name:
         Fn::GetAtt: [ EmailSES, DkimDNSTokenName1 ]
       Type: CNAME
       TTL: 600
       ResourceRecords:
         - Fn::GetAtt: [ EmailSES, DkimDNSTokenValue1 ]
     - Name:
         Fn::GetAtt: [ EmailSES, DkimDNSTokenName2 ]
       Type: CNAME
       TTL: 600
       ResourceRecords:
         - Fn::GetAtt: [ EmailSES, DkimDNSTokenValue2 ]
     - Name:
         Fn::GetAtt: [ EmailSES, DkimDNSTokenName3 ]
       Type: CNAME
       TTL: 600
       ResourceRecords:
         - Fn::GetAtt: [ EmailSES, DkimDNSTokenValue3 ]
```
Once deployed, check that 3 CNAME records were created in Route53.

If your provider is not Route53, you can copy the records from the created Email Identity and paste it to your provider.

Wait until the Identity status turns to Verified (you should receive an email).

![verified email identities in SES](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ahe6c7tin4j9chpljftn.png)

### 3. Set up MX record (DNS Provider, Route53)
This is to set up the email receiving endpoint with your DNS provider. As we use Route53, we can just add another record after those 3 that we created in step two. Note that the endpoint will be dependent on the region that you are in, [see docs for more info](https://docs.aws.amazon.com/general/latest/gr/ses.html):

```yaml
- Name: yourdomain.com
  Type: MX
  TTL: 600
  ResourceRecords:
    - '10 inbound-smtp.{REGION}.amazonaws.com'  # this will depend on your region
```
Once deployed, check that the MX Record was created in Route53 / your DNS provider.

### 4. Create an SNS Topic (SNS)
So, now SES knows that the domain is yours and you can receive emails. However, we also need to tell it what should happen to the received emails. SES can trigger these actions:

- Save email to an S3 bucket
- Invoke a lambda function
- Publish to an SNS topic

We will want to publish to SNS to send us an email when such an event happens, so we need to create one. We also have to create a `subscription` (in this case an email address) through which we will receive the notification:

```yaml
EmailSNS:
 Type: AWS::SNS::Topic
 Properties:
   Subscription:
     - Endpoint: 'youremail@gmail.com' # the email to receive the notifications
       Protocol: email
   TopicName: 'my-sns-topic-name'
```
 
Once deployed, you can check that the SNS has been created. You will also see that the status of you subscription (SNS -> created topic -> subscriptions) is pending confirmation. Check the email address that you have provided as an endpoint to confirm subscription. ❗️Check the spam folder❗️

![SNS pending subscription](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jbxbsg7mfnxzyxjtts7v.png)

If you want to use an SNS topic in a different AWS account, add a step that will give SES the permission to publish it there, see the [documentation](https://docs.aws.amazon.com/ses/latest/dg/receiving-email-permissions.html#receiving-email-permissions-kms). Since the SNS that will be processing these events is part of the same account, we do not need to create this permission.

![SNS topics](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7dk8isn6zj74mcx7x6m4.png)

### 5. Create a RuleSet with a Rule (SES)

To tell SES to publish to an SNS topic, we need to create a Rule. However, a rule has to be part of a RuleSet, therefore we need to create that first:

```yaml
EmailRuleSet:
 Type: AWS::SES::ReceiptRuleSet
 Properties:
   RuleSetName: 'my-ruleset-name'
```
Once deployed you can check that the rule set was created in SES -> Email Receiving -> All rule sets. 

![SES Rule set](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gf1lnxqggcer7vykw72y.png)

You will notice that the status is inactive. Unfortunately, the `ReceiptRuleSet` type does not have a property to set the RuleSet active ([see the issue here](https://github.com/aws/aws-cdk/issues/10321)) so we need to manually do so. It is also important to note that there can only be **one active rule set per region**. So, if you were setting a staging and production rule, they would both have to be in this rule set. 

![Confirm active rule set](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cv1oycdiuv5rg0ynqsb7.png)

> Side note, deleting an active ruleset from CloudFormation will throw an error, better to delete from CloudFormation as well as manually.

Finally, we need to create the rule that will take all the emails and publish them into the created SNS. The rule can be defined with various granularity, here in the example, we are only taking emails that come from the email address info@yourdomain.com. To set a different granularity, check the [documentation here](https://docs.aws.amazon.com/ses/latest/dg/receiving-email-receipt-rules-console-walkthrough.html):

```yaml
EmailRule:
 Type: AWS::SES::ReceiptRule
 Properties:
   Rule:
     Actions:
       - SNSAction:
           TopicArn:
             Ref: EmailSNS  # name of your SNS resource
     Enabled: true
     Name: 'your-rule-name'
     ScanEnabled: true
     Recipients:
       - 'info@yourdomain.com'
   RuleSetName:
     Ref: EmailRuleSet  # name of your rule set resource
```

Once deployed you can check that the rule has been created in SES -> Email Receiving -> RuleSet. The status should be Enabled.

![SES rules](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6wnt9cjw4bzaswfw5gmr.png)


### Final checks

- Domain is verified (CNAME records are present)
- Subscription is confirmed (check spam folder of provided email endpoint)
- RuleSet is Active (manually activate)

## And that’s it! 
Sending a testing email will look something like this: 

![final email](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/l8zqpnssork0h78oo25x.png)

Unfortunately, these emails cannot be [customized](https://docs.aws.amazon.com/sns/latest/dg/sns-email-notifications.html) as the email delivery feature is intended to provide internal system alerts, not marketing messages. But as a proof of concept without any backend logic, it's not bad 🙂

---

## Where next?
Since SES can invoke a lambda, you can create a lambda function to process the received emails and then use SES to send a proper email with the received content. That way, you won’t need the SNS at all.

**Some useful links:**
https://docs.aws.amazon.com/ses/latest/dg/receiving-email.html
https://docs.aws.amazon.com/sns/latest/dg/sns-create-topic.html
https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ses-emailidentity.html
https://docs.aws.amazon.com/sns/latest/dg/sns-email-notifications.html
https://aws.amazon.com/premiumsupport/knowledge-center/sns-topic-email-notifications/