New from emailrHealthCheck Email iconHealthCheck Email
emailr_

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.

AWS Lambda iconAWS LambdaPlatform

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

  1. 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 as EMAILR_API_KEY in your secret manager or environment, never in client-side code.

  2. 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.com keeps transactional reputation separate from your corporate mailbox.

    Emailr generates the DNS records: a DKIM TXT record on the emailr._domainkey selector, an SPF TXT record, and an MX + TXT pair on the send subdomain used as the MAIL FROM / Return-Path. A DMARC record is optional but recommended.

    Emailr's DKIM selector and send subdomain 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's include: into it instead of adding a second record.

  3. 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.

    Terminalbash
    npm install emailr
  4. 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.tsts
    import 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 <orders@mail.acme.com>',
            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 }
    }
  5. 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 when isBase64Encoded is true. Verify the signature before parsing.

    webhook.tsts
    import 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:SendEmail permission is needed; outbound HTTPS to api.emailr.dev is 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_id from 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.

Start sending in minutes

Create a free account, verify your domain and send your first email. No credit card required.