Send email from AWS Lambda with emailr_
Send transactional email from AWS Lambda with the emailr_ SDK, process SQS batches with partial failure reporting and handle signed webhooks.
Lambda functions often send email in response to events: a queue message, an S3 upload, a scheduled rule. The emailr SDK needs no IAM permissions or SES setup, just an API key.
This guide initialises the client outside the handler, sends from an SQS-triggered function with partial batch failures, and receives webhooks through a function URL.
Set up emailr_ with AWS Lambda
- 01
Create an API key
Create a key under API keys in the dashboard. Keys start with
et_live_and are scoped to one domain workspace. Store it asEMAILR_API_KEYin your secret manager or environment, never in client-side code. - 02
Add and verify your sending domain
In the dashboard, open Domains and add the domain you send from. A subdomain such as
mail.yourcompany.comkeeps transactional reputation separate from your corporate mailbox.Emailr generates the DNS records: a DKIM TXT record on the
emailr._domainkeyselector, an SPF TXT record, and an MX + TXT pair on thesendsubdomain used as the MAIL FROM / Return-Path. A DMARC record is optional but recommended.Emailr's DKIM selector and
sendsubdomain don't collide with other providers' records, so you can add them before cutting over and send from both providers during the migration. A domain can only have one SPF record: if one already exists, merge Emailr'sinclude:into it instead of adding a second record. - 03
Install and configure
Install the SDK and pass the key as an environment variable, ideally resolved from Secrets Manager or SSM Parameter Store at deploy time. Node.js 18 or later is required for the built-in
fetch.Terminalbashnpm install emailr - 04
Send from an SQS-triggered function
Create the client at module scope so warm invocations reuse it. Returning
batchItemFailures(with ReportBatchItemFailures enabled on the event source mapping) retries only the messages that failed.handler.tstsimport type { SQSBatchResponse, SQSEvent } from 'aws-lambda' import { Emailr } from 'emailr' const emailr = new Emailr({ apiKey: process.env.EMAILR_API_KEY! }) export const handler = async (event: SQSEvent): Promise<SQSBatchResponse> => { const batchItemFailures: SQSBatchResponse['batchItemFailures'] = [] for (const record of event.Records) { const { email, orderId } = JSON.parse(record.body) try { await emailr.emails.send({ from: 'Acme <[email protected]>', to: email, subject: `Order ${orderId} confirmed`, html: `<p>We've received order ${orderId}.</p>`, tags: { category: 'order', order_id: String(orderId) }, }) } catch { batchItemFailures.push({ itemIdentifier: record.messageId }) } } return { batchItemFailures } } - 05
Receive webhooks through a function URL
With a Lambda function URL or API Gateway HTTP API, the raw body arrives as
event.body, base64-encoded whenisBase64Encodedis true. Verify the signature before parsing.webhook.tstsimport type { APIGatewayProxyEventV2 } from 'aws-lambda' import { createHmac, timingSafeEqual } from 'node:crypto' export const handler = async (event: APIGatewayProxyEventV2) => { const body = event.isBase64Encoded ? Buffer.from(event.body ?? '', 'base64').toString('utf8') : event.body ?? '' const signature = event.headers['x-emailr-signature'] ?? '' const expected = createHmac('sha256', process.env.EMAILR_WEBHOOK_SECRET!) .update(body) .digest('hex') if (signature.length !== expected.length || !timingSafeEqual(Buffer.from(signature), Buffer.from(expected))) { return { statusCode: 401, body: 'Invalid signature' } } const { event: type, data } = JSON.parse(body) return { statusCode: 200, body: 'ok' } }
Tips
- No
ses:SendEmailpermission is needed; outbound HTTPS toapi.emailr.devis enough. Functions in a private VPC subnet need a NAT gateway for that. - For scheduled sends, an EventBridge Scheduler rule can invoke the same handler pattern.
- Store
message_idfrom each send if you want to match webhook events back to records in your database.
Frequently asked questions
Do I need Amazon SES permissions to send from Lambda with emailr_?
No. emailr_ handles the sending infrastructure. Your function only needs outbound internet access and an emailr_ API key.
How do I retry failed sends from SQS?
Enable ReportBatchItemFailures on the event source mapping and return the failed message IDs in batchItemFailures. Only those messages go back to the queue.
Which Node.js runtime should I use?
Node.js 18 or later, which includes the global fetch the SDK relies on.
Can I verify emailr_ webhooks behind API Gateway?
Yes. Decode event.body if isBase64Encoded is true, then compute an HMAC-SHA256 with your webhook secret and compare it to the X-Emailr-Signature header.
Keep going
Related guides
- Send email from Cloudflare WorkersSend transactional email from Cloudflare Workers with the fetch-based emailr_ SDK, store keys as Worker secrets and verify webhooks with Web Crypto.
- Send email from Vercel FunctionsSend transactional email from Vercel Functions with emailr_, manage keys with Vercel environment variables and schedule digest emails with Vercel Cron.
- Send email from FastAPISend transactional email from FastAPI with the async emailr_ Python SDK, run sends as background tasks and verify signed delivery webhooks.
Start sending in minutes
Create a free account, verify your domain and send your first email. No credit card required.