One Rust Lambda function that receives AWS messaging events from Amazon SNS, records them in DynamoDB, and republishes them to an EventBridge bus your applications subscribe to. It handles:
- AWS End User Messaging (EUM): inbound SMS and delivery receipts.
- Amazon SES: sending events (bounces, complaints, opens, clicks) and inbound mail.
Optionally, it also runs a mailbox on SES with an HTTP API to read and send mail.
EUM / SES ─► SNS ─► Function URL (HTTPS) or direct invoke
│
├─ verify SNS signature + topic allowlist
├─ persist DynamoDB, one conditional write per event
├─ act opt-outs, suppression, delivery feedback
│
└─ DynamoDB stream ─► relay ─► EventBridge ─► your apps
- Verify. Every message must carry a valid SNS signature and come from an allowlisted topic.
- Persist. A conditional write makes SNS redeliveries harmless.
- Act.
STOP/STARTupdate the EUM opt-out list. Hard bounces and complaints go on the SES suppression list. Delivery receipts send message feedback. - Publish. A stream relay publishes each saved event, with retries and a dead-letter queue.
Subscriptions confirm themselves. If someone abuses an UnsubscribeURL, the function
re-subscribes.
Install Rust, cargo-lambda and the AWS SAM CLI, then:
sam build
sam deploy --guided # set pAllowedTopics to your account idSubscribe your SNS topics to the endpoints the stack outputs. See docs/deploy.md.
Important
Set pAllowedTopics. A valid signature proves only that a message came from SNS in some
account. Keep raw message delivery off on every subscription.
Set pMailDomain and pMailInbox to receive mail at <inbox>@<domain>. Inbound mail is stored
in S3 and DynamoDB, threaded, and exposed through a /v0 API that also sends mail.
- Setup: parameters and five one-time steps after the first deploy
- API: routes, labels, filtering and sending
- Events:
message.*events and webhook delivery - Tracking: opens, clicks and custom tracking domains
- Storage: mail table, bucket layout and retention
- Runbook: stuck sends and failed ingests
| Doc | Covers |
|---|---|
| Deploy | Parameters, topic subscriptions, retry behavior |
| Events | EventBridge detail types and payloads |
| Data model | Events table and cross-account read access |
| Operations | Logs, metrics, alarms and an end-to-end check |
| Mailbox design | Why the mailbox is built the way it is |
cargo test --workspace # no AWS account needed
cargo clippy --all-targets --all-features -- -D warnings
prek run # fmt, clippy, deny, actionlint, zizmor| Crate | Purpose |
|---|---|
crates/mailbox |
Pure mailbox domain: message/thread/send types, ids, labels, MIME parsing and building, wire shapes |
crates/mailbox-store |
The mail table: keys, transaction planner, the Table seam and the Mailbox over it |
crates/sns-message-verifier |
SNS signature verification. The test-fixtures feature signs test messages |
crates/webhook |
The Lambda function (package aws-messaging-webhook) |
crates/webhook-test-support |
Fakes and request harness shared by the webhook crate's integration tests |
Dependency direction: aws-messaging-webhook -> mailbox-store -> mailbox.
Debug builds read SNS_CERT_HOST_OVERRIDE to run against a local fake SNS under
cargo lambda watch. Release builds always verify.
Licensed under either of the Apache License 2.0 or the MIT license, at your option.