Versatile email infrastructure that operates on AWS.
See mailbox-browser
| Dark mode | Light mode |
|---|---|
![]() |
![]() |
go install github.com/harryzcy/mailbox-cliFor details, refer to mailbox-cli
Deployment is managed with Terraform.
Two S3 buckets are created outside Terraform, because one must exist before
terraform init can run and the other holds mail that must outlive any stack:
- A state bucket, for Terraform state. Enable versioning so a bad write can be rolled back.
- An email bucket, where SES delivers raw messages.
A third bucket, for build artifacts, is created by Terraform, but you have to
name it through aws_s3_artifacts_bucket_override: bucket names are global, so
default reliably works. Lambda deployment packages are uploaded there to be
signed, and its contents are rebuildable from a release.
The provider sets only a region, so Terraform uses the standard AWS credential chain: environment variables, a named profile, or IAM Identity Center.
Deploying needs write access to IAM, Lambda, API Gateway, CloudWatch Logs, SQS,
Signer, SES, the state and artifact buckets, and — unless
aws_dynamodb_table_override is set — DynamoDB. Signing packages needs
signer:StartSigningJob and read and write on the artifacts bucket. Creating the artifacts bucket also needs s3:CreateBucket and the
s3:Get*/s3:Put* bucket-configuration actions behind versioning, encryption,
public access block, ownership controls, policy and lifecycle. One more is easy
to miss: iam:PassRole for the Lambda execution role, because the resulting
error names CreateFunction instead.
Attach those permissions to a role and assume it, rather than granting them to a user directly:
# ~/.aws/config
[profile mailbox]
role_arn = arn:aws:iam::<account-id>:role/mailbox-deploy
source_profile = default
region = us-west-2AWS_PROFILE=mailbox make applyWith IAM Identity Center, aws sso login --profile mailbox instead — no stored
key at all.
CI does not use any of this. It assumes a short-lived, plan-only role through
GitHub OIDC; see oidc.tf.
The two bucket names are required. Everything else has a working default, and
the remaining variables are optional overrides that each turn a feature on. Set
them as TF_VAR_ environment variables.
| Variable | Purpose |
|---|---|
aws_s3_bucket_override |
Required. Names the email bucket. There is no default: bucket names are global, so nothing generated from the project name reliably works. |
aws_s3_artifacts_bucket_override |
Required. Names the artifacts bucket Terraform creates for code signing. Global names again, so again no default. |
aws_region |
Region to deploy into. Defaults to us-west-2. |
project_name, environment |
Name resources. Default to mailbox-v2 and dev. |
aws_dynamodb_table_override |
Use an existing table instead of creating one. |
aws_dynamodb_point_in_time_recovery |
Continuous backups on the managed table. Defaults to true; incurs additional cost. |
ses_receipt_rule_set_name, ses_receipt_rule_name |
Manage an existing SES receipt rule. Both required to enable; otherwise SES is left alone. |
github_repository, github_oidc_provider_arn |
Create a CI role for that repo. Both required to enable. |
export TF_STATE_BUCKET=<your-state-bucket>
export TF_VAR_aws_s3_bucket_override=<your-email-bucket>
export TF_VAR_aws_s3_artifacts_bucket_override=<your-artifacts-bucket>
make init
make deploymake plan previews what deploying the latest release would change, and
make deploy deploys it. Use make apply to deploy binaries built from your
working tree instead.
SES applies one active receipt rule set per region, and it may already hold rules unrelated to this project. Terraform therefore manages a single rule inside an existing set rather than the set itself.
Create a rule set and a rule with two actions, in this order:
- Deliver to S3, naming your email bucket.
- Invoke Lambda, selecting
<project>-<env>-email_receive.
Then activate the rule set. To let Terraform manage the rule's Lambda action
from then on, set ses_receipt_rule_set_name and ses_receipt_rule_name and
import it:
terraform import 'aws_ses_receipt_rule.receive[0]' <rule-set-name>:<rule-name>Finally, deploy mailbox-browser or use mailbox-cli.
See doc/API.md
It runs on AWS services, including SES, Lambda, API Gateway, DynamoDB, and SQS.
- Go >= 1.27
Note that only the most recent minor versions of Go are officially supported.

