AWS in Switzerland and Austria (Alps)

Using Amazon Simple Email Service across Regions from an isolated VPC

Removing the tedious infrastructure work in IT is one of the key benefits of AWS. An example is Amazon Simple Email Service (Amazon SES), which allows you to reach customers confidently without an on-premises Simple Mail Transfer Protocol (SMTP) email server, using the Amazon SES API or SMTP interface. Customers who need to build in a certain AWS Region might find that Amazon SES is not available yet in that required Region. Email is, by definition, not data-resident, as the sender cannot control where the recipient’s mailbox is located and has no control over the path it takes.

This blog post outlines a solution for enabling cross-region access to Amazon Simple Email Service while keeping your sensitive workloads isolated from the public internet. The solution uses a VPC endpoint powered by AWS PrivateLink, offering a secure and scalable method to connect your virtual private cloud (VPC) directly to Amazon SES. This approach eliminates the need for an internet gateway, NAT device, VPN connection, or AWS Direct Connect. This way, you can securely communicate with SES across AWS regions through the AWS backbone network, ensuring encryption in-transit and avoiding any exposure to the internet.

The VPC endpoint allows accessing SES via its SMTP interface and SMTP credentials. In case you prefer an alternative based on IAM credentials, this post also covers a method for accessing the SES API via a private REST API. We will explore both options in detail:

  1. Via the authenticated SMTP protocol.
  2. Via a REST API served by API Gateway.

You should choose the SMTP option if you have existing applications or services that rely on SMTP for email delivery, as it provides a straightforward integration path.

The private REST API option is ideal for building new applications or for existing applications that can use AWS SDKs for API-based communication with Amazon SES across regions without bothering about managing SMTP credentials.

Both solutions ensure that your workloads remain isolated from the public internet by leveraging VPC endpoints.

Prerequisites

To get started, you should first set up the necessary infrastructure outlined in the following initial architecture steps. Completing these prerequisites will lay the foundation before proceeding with the solutions in this blog post

Initial architecture

Figure 1 – Initial architecture

Note: In this blog post, I use the term primary Region to indicate the region where the workload is hosted but where Amazon SES is not available (e.g.: Zurich) and SES Region to indicate the region where Amazon SES is available (e.g.: Frankfurt). Also check: Regions and Amazon SES

  1. Have an existing virtual private cloud (VPC) available or create a new one in your primary AWS Region. Include a private subnet within the VPC and, optionally, deploy an Amazon EC2 instance. For procedures, see Get started with Amazon VPC.

Note: This Amazon EC2 instance is intended solely for testing purposes.

  1. Have an existing Amazon SES account in the SES Region, see Amazon SES
  2. Provision Amazon SES SMTP credentials
    1. Sign in to the AWS Management Console
    2. In the navigation bar, choose the name of the currently displayed Region. Then choose the Region where Amazon SES is available (SES Region)
    3. Open the Amazon SES console at https://console.thinkwithwp.com/ses/
    4. Choose SMTP settings in the left navigation pane – this will open the Simple Mail Transfer Protocol (SMTP) settings page
    5. Choose Create SMTP Credentials in the upper-right corner – the IAM console will open
    6. (Optional) If you need to view, edit, or delete SMTP users you’ve already created, choose Manage my existing SMTP credentials in the lower-right corner – the IAM console will open.
    7. For Create User for SMTP, type a name for your SMTP user in the User Name Alternatively, you can use the default value that is provided in this field. When you finish, choose Create user in the bottom-right corner
    8. Select Show under SMTP password – your SMTP credentials are shown on the screen
    9. Download these credentials by choosing Download .csv file or copy them and store them in a safe place, because you will not be able to view or save your credentials after you close this dialog box
    10. Choose Return to SES console

For more information, please visit https://docs.thinkwithwp.com/ses/latest/dg/smtp-credentials.html

  1. Securely store Amazon SES SMTP credentials

While AWS recommends using Amazon Secrets Manager in the same region as your workload for optimal performance and compliance, the decision is ultimately yours. Carefully evaluate your application’s needs and constraints to choose the best region for hosting your secrets.

    1. Sign in to the AWS Management Console
    2. In the navigation bar, choose the name of the currently displayed Region. Then choose the Region where Amazon SES is available (SES Region)
    3. Open the Amazon Secret Manager console at https://console.thinkwithwp.com/secretsmanager
    4. Choose Store a new secret
    5. Choose Other type of secret as Secret type
    6. Copy and paste your SMTP credentials ad Key/Value
    7. For Secret Name, enter a unique name that identifies the secret
    8. Configure rotation – optional
    9. Download AWS SDK to retrieve the secret in your application – optional
    10. Choose Store

Option 1: VPC endpoint with SMTP Credentials

Architecture Overview

This option provides secure access to Amazon SES’s SMTP endpoint from any AWS Region using a VPC endpoint with SMTP credentials. With connectivity established, applications running on resources (e.g. EC2 instances) within the primary VPC can initiate SMTP sessions with the VPC endpoint’s private IP address. All SMTP traffic flows over the VPC peering connection and AWS backbone to reach Amazon SES, bypassing the public internet.

Your workload retrieves authenticated SMTP credentials from Secrets Manager and submits with each SMTP session. This allows Amazon SES to verify and accept inbound email sending requests on the private endpoint.

This private SMTP integration increases the security posture by by using private isolated networks of SMTP traffic. It also enables centralized Amazon SES usage across AWS Regions while ensuring compliance with data residency requirements.

Option 1 architecture diagram

Figure 2 – Option 1 architecture diagram

Deploy the solution using the AWS Management Console

Step 1: Create the security group

In this step, you create a security group that allows Amazon EC2 instances to communicate with the VPC interface endpoint you will create in the next step.

  1. In the navigation pane of the Amazon EC2 console, under Network & Security, choose Security Groups
  2. Choose Create security group
  3. Under Basic details, do the following:
    1. For Security group name, enter a unique name that identifies the security group.
    2. For Description, enter some text that describes the purpose of the security group.
    3. For VPC, choose the VPC that you want to use Amazon SES in.
  4. Under Inbound rules, choose Add rule
  5. For the new Inbound rule, do the following:
    1. For Type, choose Custom TCP
    2. For Port range, enter the port number that you want to use to send email. You can use any of the following port numbers: 465, 587, 2465, or
    3. For Source type, choose
    4. For Source, enter the private IP CIDR range or other Security Group IDs that contain the resources that will use the VPC endpoint to communicate with the SES service.
    5. (Repeat steps 4 – 5 for each CIDR range or Security Group you wish to allow access from.)
  6. When you finish, choose Create security group.

Step 2: Create the VPC endpoint.

In Amazon VPC, a VPC endpoint allows connecting private isolated subnets in a VPC to supported AWS services. In this example, you configure the VPC you deployed earlier to allow the Amazon EC2 security group to connect to Amazon SES.

  1. Open the Amazon VPC console at https://console.thinkwithwp.com/vpc/.
  2. Under Virtual private cloud, choose Endpoints
  3. Choose Create endpoint to open the Create endpoint page
  4. (Optional) In the Endpoint settings panel, create a tag in the Name tag
  5. For Service category, select AWS services
  6. In the Services panel, filter on smtp in the search bar, then select its button.
  7. In the VPC panel, click inside the search bar and select a VPC from the list box (see Prerequisites)
  8. In the Subnets panel, select Availability Zones and Subnet IDs
  9. In the Security groups panel, select the security group you created earlier
  10. (Optional) In the Tags panel, you can create one or more tags
  11. Choose Create endpoint. Wait approximately 5 minutes while Amazon VPC creates the endpoint. When the endpoint is ready to use, the value in the Status column changes to Available

For more information, please visit https://docs.thinkwithwp.com/ses/latest/dg/send-email-set-up-vpc-endpoints.html

Step 3: Connect the two VPCs

You have two options to connect the VPC deployed in the primary Region with the VPC deployed in SES Region:

  1. VPC peering, see the Amazon VPC peering guide.
  2. Transit gateway attachment, see the Transit gateway attachments to a VPC

Deploy the solution using AWS CloudFormation

The deployment of the provided AWS CloudFormation template automatically creates the following resources:

  • A VPC in the Region where Amazon SES is available (see Regions and Amazon SES) with two private subnets
  • A VPC peering connection between the new VPC and the existing VPC
  • A VPC endpoint interface with Amazon SES

To deploy the Cloudformation template

  1. On step 2 – Set the following parameters:
    1. The ID of the existing VPC in the first region.
    2. The name of the region where the existing VPC is located.
    3. The CIDR block for the new VPC.
    4. The CIDR block for the private subnet in the first region.
    5. The CIDR block for the private subnet in the second region.
  2. On step 3 – Configure stack options, you can leave the default settings, or change them for your environment. Choose Next to continue.

Figure 3 – IAM Role creation

At the bottom of Step 4 – Review, select I acknowledge that AWS CloudFormation might create IAM resources, and choose Create stack. Wait for the stack to reach status

Running the solution

At this point, you’ve deployed the solution and your resources are created.

To test it, you can create a simple Python script [Link] that sends an email using SMTP credentials. This script can be executed on your Amazon EC2 instance or as a Lambda function, targeting the VPC endpoint connected with Amazon SES

Note: You can use the AWS SDK downloaded before to retrieve the secret

Your application deployed in the primary region can now seamlessly send emails using Amazon SES in another region via the VPC endpoint.

If you deployed this for testing purposes, or want to clean this up and move to your production account, you can delete the CloudFormation stack:

  1. Open the CloudFormation console
  2. Select the stack and choose Delete

Option 2: VPC endpoint with a private API in Amazon API Gateway

Architecture Overview

This option enables access to Amazon SES’s email sending capabilities via a private API deployed in Amazon API Gateway. Once deployed, applications in the primary Region can invoke the private SES API by calling the API Gateway endpoint’s DNS name from the Interface VPC endpoint. All API traffic traverses the VPC peering connection to reach the API Gateway endpoint over AWS’ private network. The API method handles constructing the SendEmail request from the application’s inputs and invoking SES transparently. SES processes the request in its Region and returns the response back through the private API integration. This serverless approach reduces the overhead of managing SMTP credentials or email transmission logic. It also allows centralized, auditable access to SES across Regions while mitigating public attack surfaces. Moreover, your workload uses an Identity and Access Management (IAM) role for authentication and authorization when accessing the API.

Option 2 architecture diagram

Figure 4 – Option 2 architecture diagram

The API Gateway component service for API execution is called execute-api. To access your private API once it’s deployed, you need to create an interface VPC endpoint for it in your VPC.

After you’ve created the VPC endpoint, you can use it to access multiple private APIs.

Deploy the solution using AWS Management Console

Step 1: Create an interface VPC endpoint for API Gateway execute-api

  1. Sign in to the AWS Management Console and open the Amazon VPC console at https://console.thinkwithwp.com/vpc/.
  2. In the navigation pane, choose Endpoints, Create endpoint.
  3. For Service category, ensure that the option AWS services is selected.
  4. For Service name, choose the API Gateway service endpoint, including the AWS Region that you want to connect to. This is in the form amazonaws.region.execute-api—for example, com.amazonaws.us-east-1.execute-api
    1. For Type, ensure that it indicates
  5. Complete the following information:
    1. For VPC, choose the VPC that you want to create the endpoint in.
    2. For Subnets, choose the subnets in which to create the endpoint network interfaces. To improve the availability of your API, choose multiple subnets.
  • For Enable DNS name, leave the check box selected. Private DNS is enabled by default.

When private DNS is enabled, you’re able to access your API via private or public DNS. This setting doesn’t affect who can access your API, only which DNS addresses they can use. However, you cannot access public APIs from a VPC by using an API Gateway VPC endpoint with private DNS enabled. Note that these DNS settings don’t affect the ability to call these public APIs from the VPC if you’re using an edge-optimized custom domain name to access the public API. Using an edge-optimized custom domain name to access your public API (while using private DNS to access your private API) is one way to access both public and private APIs from a VPC where the endpoint has been created with private DNS enabled.

Note: Leaving private DNS enabled is the recommended choice. If you choose not to enable private DNS, you’re only able to access your API via public DNS. To learn more, see How to invoke a private API.

To use the private DNS option, the enableDnsSupport and enableDnsHostnames attributes of your VPC must be set to true. For more information, see DNS Support in Your VPC and Updating DNS Support for Your VPC in the Amazon VPC User Guide.

  • For Security group, select the security group to associate with the VPC endpoint network interfaces.
  • The security group you choose must allow TCP Port 443 inbound HTTPS traffic from either an IP range in your VPC or another security group in your VPC.
  1. Choose Create endpoint.

Step 2: Create IAM Execution Role for a private API

  1. Sign in to the Identity and Access Management console at https://console.thinkwithwp.com/iam
  2. In the navigation pane, choose
  3. Choose Create role.
  4. In the Trusted entity type section, choose Custom trust policy.
  5. Copy and paste this JSON within the Custom trust policy section:
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "AllowApiGatewayServiceToAssumeRole",
      "Effect": "Allow",
      "Principal": {
        "Service": "apigateway.amazonaws.com"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}
  1. In the Permission policies section, choose AmazonSESFullAccess

Note: In accordance with the least-privilege principle, it is advisable to create a new permission policy that only includes the required actions (e.g.: SendEmail).

  1. Enter a meaningful name to identify this role (e.g.: APItest-ApiGatewaySESRole).
  2. Choose Create role.

Step 3: Set up a resource policy for a private API

Before your private API can be accessed, you need to create a resource policy and attach it to the API. This grants access to the API from your VPCs and VPC endpoints, or from VPCs and VPC endpoints in other AWS accounts that you explicitly grant access.

To do this, follow the instructions in Create and attach an API Gateway resource policy to an API. In step 5, choose the Source VPC example. Replace {{vpceID}} (including the curly braces) with your VPC endpoint ID, and then choose Save to save your resource policy.

You should also consider attaching an endpoint policy to the VPC endpoint to specify the access that’s being granted. For more information, see Use VPC endpoint policies for private APIs in API Gateway.

Step 4: Create a private API using the API Gateway console

  1. Sign in to the API Gateway console at https://console.thinkwithwp.com/apigateway.
  2. Choose Create API
  3. Under REST API, choose Build
  4. For Name, enter a name
  5. (Optional) For Description, enter a description
  6. For API endpoint type, select Private
  7. Choose Create API

Step 5: Configure your private API

  1. In the navigation pane, choose Resources
  2. Choose the API created in previous step
  3. In the Methods section, choose Create Method
  4. Choose AWS Service as Integration Type
  5. Choose the AWS Region where your Amazon SES account
  6. Choose Amazon SES as AWS service
  7. Choose POST as HTTP method
  8. Write SendEmail ad Action name
  9. Copy and paste the Exectuion Role ARN created in Step 2 ad Execution Role
  10. In the Method request settings:
    1. Choose Validate Body as Request validator
    2. Check the API Request validator box.
  11. In the HTTP request headers:
    1. Choose Add header
    2. Write Content-Type as Name
  12. Choose Create method
  13. In the Method execution section, choose Integration request
  14. Choose Edit
  15. In the Method details:
    1. Choose Do not add caller credentials to cache key
    2. Choose Passthrough as Content handling
    3. Select Default timeout
    4. Choose When there are no templates defined (recommended) as Request body passthrough
  16. In the URL request headers parameters, choose Add request header parameter.
    1. Insert Content-Type as Name
    2. Insert ‘application/x-www-form-urlencoded’ as Mapped from
  17. In the Mapping templates
    1. Insert application/json as Content type
    2. Insert the following code as Template body
#set($inputRoot = $input.path('$'))
Action=SendEmail
&Source=$inputRoot.from
#foreach($to in $inputRoot.to)
&Destination.ToAddresses.member.$foreach.count=$to
#end
#foreach($bcc in $inputRoot.bcc)
&Destination.BccAddresses.member.$foreach.count=$bcc
#end
#foreach($cc in $inputRoot.cc)
&Destination.CcAddresses.member.$foreach.count=$cc
#end
&Message.Subject.Data=$inputRoot.subject
&Message.Body.Text.Data=$inputRoot.text
&Message.Body.Html.Data=$inputRoot.html
  1. Choose Save
  2. In the Method execution section, choose Integration response.
  3. Choose Edit
  4. In the Response details section, choose Passthrough as Content handling
  5. In the Mapping templates, Insert application/type as Content type
  6. Choose Save
  7. In the Method execution section, choose Methodresponse.
  8. Choose Edit
  9. In the Response details section, choose Add model
  10. Insert application/json as Content model 
  11. Choose Save
  12. Choose Deploy API
  13. Choose New stage as Stage
  14. Define a Stage name
  15. Choose Deploy

Step 6: Associate an additional VPC endpoint with a private API

  1. Sign in to the API Gateway console at https://console.thinkwithwp.com/apigateway
  2. Choose your private API
  3. In the main navigation pane, choose Resource policy
  4. Edit your resource policy to allow calls from your additional VPC endpoint.
  5. In the main navigation pane, choose API settings
  6. In the API details section, choose Edit
  7. For VPC endpoint IDs, select additional VPC endpoint IDs
  8. Choose Save
  9. Redeploy your API for the changes to take effect

Step 7: Connect the two VPCs

You have two options to connect the VPC deployed in the primary Region with the VPC deployed in SES Region:

  1. VPC peering, see the Amazon VPC peering guide
  2. Transit Gateway Attachment, see Transit gateway attachments to a VPC

Deploy the CloudFormation template with the solution

The deployment of the CloudFormation template automatically creates the following resources:

  • A VPC in the Region where Amazon SES is available (see Regions and Amazon SES) with two private subnets
  • A VPC peering connection between the new VPC and the existing VPC
  • A VPC endpoint interface with Amazon SES
  • A private REST API

To deploy the Cloudformation template

  1. On step 2 – Set the following parameters:
    1. The ID of the existing VPC in the first region.
    2. The name of the region where the existing VPC is located.
    3. The CIDR block for the new VPC.
    4. The CIDR block for the private subnet in the first region.
    5. The CIDR block for the private subnet in the second region.
  2. On step 3 – Configure stack options, you can leave the default settings, or change them for your environment. Choose Next to continue.

Figure 5 – IAM Role creation

At the bottom of Step 4 – Review, select I acknowledge that AWS CloudFormation might create IAM resources, and choose Create stack. Wait for the stack to reach status

Running your solution

  1. In the main navigation pane, choose API keys
  2. Choose Create API key
  3. Enter a meaningful name to identify this API Key
  4. Choose Auto generate
  5. Choose Save
  6. In the main Resources section, choose your API and the method
  7. Choose Test
  8. Enter https://<Resource ID>.execute-api.eu-central.amazonaws.com/<stage-name> as Query strings
  9. Enter the following header as Headers
Content-Type: ‘application/json’
x-api-keyValue: <API-Key>
  1. Enter the following JSON as Request body
{
  "to": [
    "email-address"
  ],
  "from": "email-address",
  "subject": "This is a test email subject",
  "text": "This is a test email content",
  "html": "<p>This is a test email content</p>"
}
  1. Choose Test

Your application deployed in your primary region can now seamlessly send emails using Amazon SES in another region via the VPC endpoint, ensuring data residency compliance.

If you deployed this for testing purposes, or want to clean this up and move to your production account, you can delete the Cloudformation stack:

  1. Open the CloudFormation console
  2. Select the stack and choose Delete

Pricing Considerations

With Amazon SES, you are billed separately for sending and receiving emails, data usage, and additional features. Amazon SES gives you transparency and low prices regardless of your use case, and you only pay for the features you use.

Example: You use Amazon SES to send about 10,000 email per month. You receive 1,000 emails per month. Every message you send and receive is 32KB.

Unit Price Cost
Amazon SES [1]
Outgoing messages 10000 $0 $1
Outgoing mail data (KB) 32 $0.12 $0.04
Incoming messages 1000 $0 $0.10
Incoming mail chunks 32 $0 $0.01
Total charges for using SES $1.15
Option 1 [2]
Total charges for using SES $1.15
Data Transfer OUT From Zurich To Frankfurt (per GB) 0.32 $0.02 $0.01
Pricing per VPC endpoint per AZ ($/hour) 720 $0.01 $7.20
Data Processed per month in an AWS Region (per GB) 0.32 $0.01 $0
Total charges for using SES with Option 1 $8.36
Option 2 [3]
Total charges for using SES $1.15
Data transfer OUT from Zurich to Frankfurt (per GB) 0.32 $0.02 $0.01
Pricing per VPC endpoint per AZ ($/hour) 720 $0.01 $7.20
Data Processed per month in an AWS Region (per GB) 0.32 $0.01 $0
API Calls per Month 1,000,000 $3.70
Total charges for using SES with Option 2 $12.06

[1] https://thinkwithwp.com/ses/pricing/?nc1=h_ls

[2] https://thinkwithwp.com/privatelink/pricing/

[3] https://thinkwithwp.com/api-gateway/pricing/