AWS

The awscdk-rootmail construct enhancements

Enhancing the construct, e.g. passing in your own email processing function.

Feb 17, 2024 · 2 min read

Photo by unsplash

I have been using the construct my self and also needed some updates. Furthermore, I build and enhance the tools I need myself and learn new stuff on the way.

So here the latest updates. You can read the initial post here.

Update 2024-02-17

  • Migrating to aws-sdk-js-v3: done in the PR #17 and share the experience in the referring post
  • Figuring out a method to run the test stack exclusively for faster feedback and not using Lambda log debugging: done in the integ tests PR #14
  • Fixing the cdk-nag errors and warning and/or add appropriate suppressions (done)
  • Adding a release job in projen: still WIP, see the PR #25

Update 2024-02-18

Now we can also pass in a custom Lambda function to process the received EMails, for example

import { S3 } from '@aws-sdk/client-s3';
import { ParsedMail, simpleParser } from 'mailparser';
// populated by default from the construct
const emailBucket = process.env.EMAIL_BUCKET;
const emailBucketArn = process.env.EMAIL_BUCKET_ARN;
const s3 = new S3();
 
// SESEventRecordsToLambda
// from https://docs.aws.amazon.com/ses/latest/dg/receiving-email-action-lambda-event.html
export const handler = async (event: SESEventRecordsToLambda) => {
    for (const record of event.Records) {
        
        const id = record.ses.mail.messageId;
        const key = `RootMail/${id}`;
        const response = await s3.getObject({ Bucket: emailBucket as string, Key: key });
        
        const msg: ParsedMail = await simpleParser(response.Body as unknown as Buffer);
        
        let title = msg.subject;
        console.log(`Title: ${title} from emailBucketArn: ${emailBucketArn}`);
        // use the content of the email body 
        const body = msg.html;
        // add your custom code here ...
    }
};

give it the additionally needed permissions, wrap it into a NodejsFunction

const customSesReceiveFunction = new NodejsFunction(stackUnderTest, 'custom-ses-receive-function', {
  // omitted for brevity
});
 
// Note: any additional permissions you need to add to the function yourself!
customSesReceiveFunction.addToRolePolicy(new iam.PolicyStatement({
  actions: [
    's3:List*',
  ],
  resources: ['*'],
}))

and then pass it in

export class MyStack extends Stack {
  constructor(scope: Construct, id: string, props: StackProps = {}) {
    super(scope, id, props);
 
    const domain = 'mycompany.test'
    const hostedZone = r53.HostedZone.fromLookup(this, 'rootmail-parent-hosted-zone', {
      domainName: domain,
    });
 
    const rootmail = new Rootmail(this, 'rootmail-stack', {
      domain: domain;
      autowireDNSParentHostedZoneID: hostedZone.hostedZoneId,
      env: {
        region: 'eu-west-1',
      },
      customSesReceiveFunction: customSesReceiveFunction, // pass it in here
    }); 
  }
}

And of course I added another integ-test for and could finally use of the --parallel-regions feature .

Update 2024-04-17

We can now release automatically to S3. See the PR for more details

More from the blog

Keep reading - related notes from recent engagements.

DevOps

Advanced GitLab CI features in v19

A tour from job grouping to dynamic child pipelines, and a look at the experimental functions feature, every example run on a real GitLab 19 instance.

Ready to ship faster on AWS?

Tell us what you are building. We will map the fastest safe path to production and the platform to keep it there.

Notes from production

Occasional, no-fluff writing on AWS, DevOps, and running platforms that stay up. No spam, unsubscribe anytime.

Practical, not promotional
Real lessons from real engagements - architecture, automation, and incident post-mortems.
No spam
A few emails a year at most. Your address is never shared or sold.