Stem the email marketing tide using masking
This story presents a proof-of-concept that allow me to give a unique email address
email@example.com when subscribing to mailing lists, and registering unimportant accounts.
anything can be any word that I decide on the spot when I give the address. The emails are forwarded to my normal email address — and if I find that I am receiving too many, I can unsubscribe and blacklist the address…
As an added benefit, I am now using a different password and email address when registering on websites… That should keep me safer.
The views/opinions expressed in this story are my own. This story relates to my personal experience and choices and is provided in the hope that it will be useful but without any warranty.
I like to subscribe to news, forums, etc. But, I have been pwned… and I do not read most emails — just casually scan through subjects to see if anything spike my interest… I can unsubscribe, but I have the impression that these emails just keep on coming. I recently started to use a brand new email address — that I would like to keep clean and hence have been looking for a way to control who I give this email address to.
A hacker news post  got me onto email masking. Google led me to the AWS blog post (Forward Incoming Email to an External Destination ) and this implementation.
AWS Infrastructure Diagram
The diagram below shows the AWS infrastructure used in the implementation of the email masking service — namely, receiving and storing the email, checking the address and forwarding it if appropriate.
The AWS infrastructure consists of:
- A route53 record to direct incoming mail to the SES service;
- SES rules that handle incoming emails to
firstname.lastname@example.org them to S3 and invoke a lambda function;
- A lambda function that processes the incoming email, and forwards it to my normal email
email@example.com the incoming email address
firstname.lastname@example.org not blacklisted.
I personally use terraform to manage the AWS infrastructure, but one can create it via the console easily — it is the same infrastructure as described in  — or using any other Infrastructure-as-Code tool.
The main elements of the terraform script read as follows:
- SES rule: The following script sets a SES rule that handles any incoming email with an address
@mydomain.com. The rule has 2 actions. The first action save the email to an S3 bucket
aws_s3_bucket.x.id— this bucket is created using an
aws_s3_bucketresource and, importantly, a policy is applied to that bucket that allows write, aka
PutObjectaction, from SES. The second action triggers the lambda function
aws_lambda_function.x.arn. The rule is assigned to the rule set
aws_ses_receipt_rule_set.mail.idthat is defined using an
- Lambda function: The lambda function is defined with a provided runtime — ie the function is a self contained executable with no dependencies as described in . The maximum memory usage is set 128MB and a runtime of 5s that has proved sufficient for my use. The function is initialized with an empty zip file as I deploy the executable using the AWS command line interface.
To allow the SES rule and lambda to work, permissions need to be setup:
- A resource is defined that allows the SES service to call the lambda function — I attempted to nominate the SES caller using the source arn entries, but was unable to this approach led to a circular reference.
- Define the role used by the lambda function with a policy that allows it to read from the S3 bucket and send emails usingthe AWS SES service. The role also allows the lambda function to write logs — for CloudWatch.
I chose to write the lambda function in Rust using the Rust Runtime for AWS  and the AWS SDK for Rust  — currently in developer preview. The implementation is fairly similar to the python implementation in  to the exception that I wanted to (a) check the email address against a blacklist and (b) forward the email content rather than a link to access the email from the S3 bucket.
rusta new binary application is created using
cargo new ses_handler. The following dependencies are added to the
lambda_runtime— The glue required to write an AWS lambda function in Rust;
aws-sdk-s3— Components of the Rust SDK to access to SES and S3 services;
mailparseto parse the email received by SES and saved to S3. There are a number of libraries available — I started using this one and as it works satisfactorily I did not investigate any other one.
The implementation is structured around the following 3 Rust structs:
SesEventstruct maps the SES event into an object that identifies the email ID, sender, subject and list of destinations. The struct and its construction method is shown below. The construction method is intimately coupled with the SES incoming email event format  that is provided as the
Addressstruct stores the fields used by the lambda function when forwarding the email — the
fromfield is used to store the email address to which the email was originally sent to, ie
email@example.com. This is used as the originator of the forwarded email. The
keyfield stores the email address key, ie
- Instances of the
Addressstruct are constructed from the email address
keyis used to decide whether the email is forwarded or not as shown below. For
keythat are not forwarded, the method returns a
- The above method is used in the following method that translates the SES events into a list of addresses:
- The final struct
message_id— the identifier of the email given by the SES event. The other fields,
messageallow for caching of information in the event that a message is to be sent multiple times — an edge case if the email was part of a mailing list with different
- The email parsing is handled by the following function that extracts the subject, plain text, and HTML components of the email:
- The implementation of the
sendfunctionality of the
Using the above structs, the main function takes on the following form where the incoming event is processed into an
SesEvent message id is used to create a
SesEvent list of destinations is converted to
Address that are used to call the
send method of the
I have been using my
firstname.lastname@example.org proof-of-concept for a few weeks with quite a bit of success. But as the blacklist is hardcoded, I am not updating it very often. Whilst there is room for improvement, the implementation of the proof-of-concept went quite smoothly despite my lack of familiarity with the SES service.
The AWS Rust SDK works effectively with the AWS SES and S3 services — despite being in developer preview. I am currently using it with Amazon DynamoDB which is proving a little less stable currently.