Serverless email sender


A good friend asked me for help with a simple problem: he needed a way to send emails from an HTML form without too high costs. He creates many static websites for different clients and almost all of them requested an email form on the contact page. To have an email submission form, you need a server implementation that exposes an API. Of course, having a server just for such a simple action was not an option.

Solution
API Gateway Configuration
Lambda function deployment
Client configuration
Testing

Solution

  • A simple serverless API exposing one endpoint for sending email. This endpoint can then be used from all its web sites for sending email to clients.
  • Each send email request will have a client id. This will be used to identify the exact configuration for each client. Client configuration is stored in S3
  • Solution can be easy extended to support other send email providers (now SMTP is supported but it can be added very easy support for AWS SES)
  • Solution can be easy extended to allow HTML template rendering for email body, with possibility to customize template fo each client. Templates can also be stored in S3
  • Other types of emails can be added very easy by extending the current API.
  • The approach was to have a simple solution, adding AWS SQS between API Gateway and Lambda was not required in this case.
  • Another step will be to configure Route 53 to expose the API as a sub-domain.

Here is my solution implementation details.

I wanted to implement this:

API Gateway Configuration

Here is how API Gateway should be configured for our endpoint:
  • Start new API
  • Create resources for path /clients/{clientId}/email
  • Create method POST. In the end you should have something like this:
  • Create a Lambda function
  • Attach S3 access policy to Lambda role.
  • Select Lambda as Integration type for API Gateway.
  • Accept adding API Gateway permissions to invoke the new Lambda function.
  • Create a model for request body. This will help validating the request.


  • Model
        {
            "type": "object",
            "properties": {
                "subject": {
                    "type": "string",
                    "minLength": 1
                },
                "email": {
                    "type": "string",
                    "minLength": 1,
                    "format": "email"
                },
                "body": {
                    "type": "string",
                    "minLength": 1
                }
            },
            "required": ["subject", "email", "body"]
        }
                                            

  • Activate request validation body by selecting "Method Request" from POST method configuration of our endpoint. Make sure to select Validate body for Request Validator and then to add new created model in Request Body section (with application/json Content type)
  • Create a request mapping. This is a good practice, making possible to implement the AWS Lambda completely separated from the source event. If a request mapping is added then the Lambda function will receive an event object without any knowledge about the source (API Gateway, SQS, a.s.o.).
    To do this select "Integration Request" from POST method of your resource. Then in "Mapping Templates" select "When there are no templates defined (recommended)" and add one template for content type "application/json"


  • Mapping template
        {
            "clientId": "$input.params('clientId')",
            "email": {
                "subject" : $input.json('subject'),
                "email" : $input.json('email'),
                "body" : $input.json('body')
            }
        }
                                            
  • Add response status codes by selecting "Method Response"
  • Add mapping response for all possible response status codes
  • Configure Lambda function as described bellow.

Lambda function deployment

Here is the github repository from where you can retrieve the lambda code. Follow the next steps to deploy the lambda function and use it with your API Gateway.
  • Download the code (or clone repository).
  • cd to lambda folder. Run npm i
    Note: If you don't have node installed please follow the instructions to install it using nvm.
  • Create zip archive with the lambda folder. Files should be directly in the root of archive not in a folder.
  • Upload zip file to Lambda

Client configuration

Steps:
  • Client configuration is stored in AWS S3 in a bucket that should be created.
  • Create an S3 bucket (choose the same region as Lambda)
  • Create locally a file with name equal with the client id - up to you what you want to use as client id
  • Content of file should be (of course this is just an example, it should be adjusted according with client requirements):

  • Client configuration
        {
            "clientType": "smtp",
            "settings":{
                "host": "smtp.mailtrap.io",
                "port": 2525,
                "auth": {
                    "user": "b928...",
                    "pass": "d8ad..."
                }
            },
            "from":"mircea.alexandru@gmail.com",
            "clientEmail":"client@client.com"
        }
                                            
  • There is only one supported client type - smtp - but in the future we can add more integrations.
  • For testing I used mailtrap.io - a very easy email service that provides SMTP and also an inbox where all emails will be displayed, regardless of recipient.
  • Add env variable for Lambda function to specify the bucket name where client configuration is stored.

Testing

There are many ways to test this implementation. Test Lambda only, test API Gateway endpoint or test using an external client calling directly the endpoint.
First we need to deploy the API Gateway in order to make sure this is accessible to external clients.
  • Choose Deploy API from API Gateway Resources
  • Select (or create if not yet available) a Deployment stage and add a description (optional)
  • The url where this API is exposed is then displayed on top of the page
  • Now the endpoint can be tested using an external client - I am using curl
  • We can see the email in mailtrap.io account