by skunxicat

Email Forwarding with AWS SES and Terraform

Every project needs a contact address. Every domain deserves its own inbox. But running a mail server just to forward hello@yourdomain.com to your existing inbox is overkill.

We built terraform-aws-ses-email-forwarder to solve this in one terraform apply.

The Problem

You register a domain. You want hello@yourdomain.com to land in your existing Gmail or company inbox. Your options:

  1. Google Workspace / Microsoft 365 — $6+/month per user for a mailbox you don’t need
  2. Self-hosted mail server — Postfix, DKIM, SPF, DMARC, spam filtering, maintenance forever
  3. Third-party forwarding services — Another vendor, another bill, another dependency

None of these make sense for a simple forwarding use case.

The Solution

Amazon SES can receive email. A Lambda function can forward it. Terraform can wire it all together.

Internet

Amazon SES (inbound receiving)

SES receipt rule

S3 (raw message storage)

Lambda forwarder (Go, ARM64)

Your existing inbox

The module handles everything: domain verification, DKIM, MX records, S3 bucket, receipt rules, Lambda function, and IAM permissions.

Usage

module "email" {
  source = "git::https://github.com/ql4b/terraform-aws-ses-email-forwarder.git?ref=v1.0.0"

  namespace = "cloudless"
  name      = "email"

  domain_name = "example.com"

  recipients = [
    "hello@example.com",
    "contact@example.com",
  ]

  forward_to = [
    "carlo@example.com",
  ]

  route53_zone_id = data.aws_route53_zone.cloudless.zone_id
}

That’s it. After terraform apply, emails to hello@example.com arrive in your existing inbox.

How the Forwarder Works

SES doesn’t pass the full raw email to Lambda directly. The receipt rule stores the message in S3 first, then triggers the forwarder.

The Go Lambda function:

  1. Reads the raw email from S3
  2. Adds a Reply-To header with the original sender
  3. Rewrites From to use the verified domain (SES requirement)
  4. Strips headers that break forwarding (DKIM signatures, Message-ID, Return-Path)
  5. Sends via SendRawEmail to the destination

This preserves the original message content while satisfying SES sending requirements.

Why Go

The forwarder is a compiled Go binary running on provided.al2023 (ARM64). The binary ships pre-compiled in the module — no build step required for consumers.

Why not Node.js? AWS stopped bundling the SDK in Lambda runtimes after Node 16. A Node.js forwarder would require npm install inside .terraform/modules/ before every apply. That’s impractical for a Terraform module.

Go gives us:

  • Zero runtime dependencies — single static binary
  • Ships with the module — no build step for consumers
  • ~4.7MB zip — fast cold starts
  • ARM64 — cheapest Lambda pricing

Design Decisions

No SPF record creation. Domains often have existing TXT records (Google site verification, etc.) that would conflict. SPF is documented as a manual recommendation instead.

Pre-compiled binary. The module consumer experience is just terraform apply. The Makefile exists for maintainers who need to rebuild.

Single receipt rule. S3 action + Lambda action in one rule. No validation step, no DynamoDB, no API Gateway. This module does one thing.

CloudPosse context. Consistent naming via cloudposse/label/null — same pattern as our other modules.

What It Costs

At low volume (< 1000 emails/month):

  • SES receiving: free (first 1000 messages)
  • SES sending (forwarding): $0.10 per 1000 emails
  • S3 storage: negligible (messages expire after 30 days)
  • Lambda: negligible (milliseconds per invocation)

Effectively free for most use cases.

When to Use This

Good for:

  • Contact address for a static website
  • Project inbox forwarded to a team mailbox
  • Domain aliases that forward to one or more real inboxes
  • Lightweight inbound email for small services

Not for:

  • Full email hosting
  • High-volume transactional email
  • Complex routing rules
  • Inbox management with read/unread state

SES Receiving Region

SES inbound email receiving is available in most AWS regions. Deploy this module in a region that supports SES receiving and the MX record will point to the corresponding regional endpoint automatically.

The Bigger Picture

This module is part of our approach to infrastructure: solve real problems with minimal, composable tools.

We needed hello@ourdomain.com to work. We didn’t need a mail server. Now it’s a single Terraform module that anyone can use.


terraform-aws-ses-email-forwarder is open source and available on GitHub.