Serverless pipeline that receives emails and forwards them as SMS messages.
Some services only send notifications by email — there is no push notification, no SMS option, and no API. If you want to receive those notifications on your phone while away from a screen, you have to build that bridge yourself.
Email to SMS is a serverless pipeline that receives emails at a custom domain and forwards them as SMS messages. Incoming emails are stored in S3 via an SES receipt rule, which triggers a Lambda function. The Lambda parses the email, matches the sender against a configured allowlist, and publishes an SMS to each configured recipient via SNS.
The initial use case is Garmin LiveTrack: when a tracked athlete starts an activity, Garmin sends a notification email. This pipeline intercepts that email and delivers it as a text message within seconds, without requiring the recipient to have a Garmin account or the Garmin Connect app installed.
Adding support for a new email sender requires implementing a single parser interface and registering it by key — no changes to the core handler or infrastructure. Recipient phone numbers live in SSM Parameter Store and can be updated without a redeploy.
If this kind of integration could solve a problem you're working on, we'd be happy to talk through it. Use the contact form at the bottom of this page to get in touch.
Routes inbound SMTP traffic to SES via an MX record pointing to the SES inbound SMTP endpoint for the deployment region. Also holds the DKIM CNAME records used by SES to verify domain ownership and sign outbound messages.
Matches emails addressed to the ingestion domain and stores them to an S3 bucket. Spam scanning is enabled. SES receipt rules filter by recipient domain only — sender filtering is handled in the Lambda.
Stores each raw email as an object under the emails/ prefix, named by the SES message ID. A 7-day lifecycle rule expires objects automatically, providing a debug artifact window without unbounded storage growth. An S3 event notification triggers the Lambda on every new object.
The core of the pipeline. Reads the raw email from S3, parses it with mailparser, matches the sender against the configured allowlist, invokes the appropriate parser, and publishes an SMS to each recipient. Written in TypeScript and bundled with esbuild; AWS SDK packages are excluded since they are provided by the Lambda Node.js runtime.
Holds the pipeline configuration as a JSON document: sender rules, parser types, and recipient phone numbers. Sensitive values stay out of the repository and can be updated without a redeploy. The Lambda reads config once per container instance and caches it in memory; changes take effect on the next cold start.
Publishes SMS messages directly to recipient phone numbers without a topic. Messages use SMSType: Transactional for higher delivery priority. A provisioned toll-free origination number is used for consistent sender identity.
Each email type has a dedicated parser class that implements the EmailParser interface. Parsers are registered by string key in a central registry. The current registry includes a Garmin LiveTrack parser that extracts the athlete name and LiveTrack URL from the notification email body using cheerio.
SES can invoke Lambda directly, but routing through S3 first provides a 7-day debug artifact — the raw email — without any extra effort. It also decouples ingestion from processing: a Lambda failure or cold start delay does not affect email receipt, and emails can be reprocessed by replaying the S3 event.
Recipient phone numbers are sensitive and change independently of the codebase. Storing them in SSM Parameter Store keeps them out of the repository and allows live updates without a redeploy. The Lambda reads config once per container and caches it in memory; changes take effect on the next cold start.
SES receipt rules filter by recipient domain only — they cannot filter by sender address. The Lambda applies the sender allowlist immediately after parsing the email. Unknown senders are dropped without invoking any parser or making any SNS calls.
Each email type has a dedicated parser class registered by string key. Adding support for a new sender requires implementing the EmailParser interface and adding one entry to the registry — no changes to the main handler or infrastructure.
The project started with AWS SAM but migrated to Terraform before the first production deployment. SAM's Events: Type: S3 creates a circular CloudFormation dependency when the S3 bucket is defined in the same template. Terraform's explicit depends_on chain resolves this cleanly and also manages aws_ses_active_receipt_rule_set natively, eliminating a manual post-deploy activation step.
Runtime dependencies (mailparser, cheerio) are bundled into the deployment zip. @aws-sdk/* packages are excluded because they are provided by the Lambda Node.js runtime. This keeps the bundle small and cold start times low.