Send email with AWS Lambda

A handler that sends, with the key in Secrets Manager or in the function config.

You need a verified sending domain and an API key from Settings › API Keys. If neither exists yet, the quickstart covers both in a couple of minutes.

Send

const KEY = process.env.SENDING_API_KEY;
 
export const handler = async (event) => {
  const { email, userId } = JSON.parse(event.body ?? "{}");
  const res = await fetch("https://sending.dev/api/v1/emails", {
    method: "POST",
    headers: { Authorization: `Bearer ${KEY}`, "Content-Type": "application/json" },
    body: JSON.stringify({
      from: "Acme <[email protected]>",
      to: email,
      subject: "Welcome",
      html: "<p>You're in.</p>",
      // Lambda retries on error: the key must come from the event, not from Date.now().
      idempotencyKey: `welcome-${userId}`,
    }),
  });
  return { statusCode: res.status, body: await res.text() };
};

A successful call answers 202 with the message id: accepted and queued, not yet delivered. Delivery, opens, bounces and complaints arrive later, on webhooks or in the dashboard.

What changes in AWS Lambda

Node 18 and later on Lambda have fetch built in, so this handler needs no bundling and no layer.

The thing that catches people

Asynchronous invocations are retried twice by default, and an SQS trigger retries until the message expires. Derive the idempotency key from the event, or a transient error turns into three copies of the same email.

Next

  • All the fields: cc and bcc, reply-to, attachments, templates, scheduling.
  • Webhooks: delivery, bounce and complaint events, signed with HMAC.
  • Domains: SPF, DKIM and MAIL FROM, and why sending is refused until they are in place.
  • MCP: the same operations as tools, when the one writing the code is an agent.

Nearby: Platforms and edge runtimes

All 30 stacks