Skip to content
Claim your free business email
Email API for Cloudflare Workers

Send email from Cloudflare Workers with an email API

Send email from Cloudflare Workers with one fetch: the key is a secret on the env binding, and every message returns an id you can follow.

Cloudflare Worker — src/index.ts
// wrangler secret put RASKET_API_KEY — the Worker// gets it on env, the binding Cloudflare recommends.interface Env {  readonly RASKET_API_KEY?: string;}
export default {  async fetch(request: Request, env: Env) {    const key = env.RASKET_API_KEY;    if (!key) {      return new Response("not configured", {        status: 500,      });    }
    const { orderId } = (await request.json()) as {      orderId?: string;    };    // No id, no key: refuse rather than send as    // "order-undefined" and replay the first answer.    if (!orderId) {      return new Response("orderId is required", {        status: 400,      });    }
    const response = await fetch(      "https://api.rasket.com/emails",      {        method: "POST",        headers: {          Authorization: `Bearer ${key}`,          // Required on every request.          "User-Agent": "acme-billing/1.0",          "Content-Type": "application/json",          "Idempotency-Key": `order-${orderId}`,        },        body: JSON.stringify({          from: "Acme <orders@send.acme.example>",          to: ["ronald.williams@example.com"],          subject: "Your order has shipped",          html: "<p>Order 1042 shipped today.</p>",        }),      },    );
    if (!response.ok) {      return new Response("send failed", {        status: 502,      });    }
    const email = (await response.json()) as {      id: string;    };    return Response.json({ id: email.id });  },};

What sending from Cloudflare Workers actually involves

To send email from Cloudflare Workers you make one subrequest with fetch, which is all a send needs: no socket to hold open and no SMTP session to drive. The one thing to get right is where the key lives. Store it as a secret, and read it from the env argument Cloudflare passes to the fetch handler, which is the binding Cloudflare recommends.

Last updated 2026-10-13. Every field, header and limit below is the one the emails reference carries.

What you get with the key

Five things that arrive with the first request rather than with a later plan.

  • Idempotent sends

    Put an Idempotency-Key on a send and repeat it as often as you like. For 24 hours the same key and payload return the first response instead of a second email, and a different payload under that key is refused rather than sent.

  • Signed webhooks you can replay

    Delivery, bounce, complaint, open and click posted to your endpoint with a timestamp and a signature, retried on failure and replayable from the dashboard.

  • Inbound mail on your own domain

    Receive at your domain on every plan. Each message arrives on a signed webhook with its headers, text and HTML, and its attachments behind signed links.

  • Templates with typed variables

    Publish a template, then send it by ID or alias with the values it declares. Every email records the exact version it was rendered from.

  • Campaigns and automations

    Contacts with typed properties, topics people subscribe to, and per-contact workflows that wait, branch and send — through the same pipeline as the rest of your mail.

How to send email from Cloudflare Workers

Six steps, in this order. Everything before the fifth is done once, and only the fifth is about the language you write in.

  1. 1

    Create an API key

    Open API keys in the dashboard and create one. It is shown once and stored hashed, so copy it then. Put it in your environment as RASKET_API_KEY and read it from there; a key committed to a repository is a key you have to rotate.

  2. 2

    Add a sending domain

    POST /domains with a domain you control. A subdomain such as send.acme.example is the usual choice, because it keeps this mail's reputation separate from the address your people write from. The response carries a records array.

  3. 3

    Publish the DNS records

    Publish the rows of records in your DNS. Two are required: the DKIM TXT record at rasket._domainkey, and the CNAME that gives the domain its own return path; nothing sends from the domain until they resolve. Two are optional: the Receiving MX, if mail sent to the domain should arrive in Rasket, and the Tracking CNAME, if open and click tracking should use a links host on your own domain.

  4. 4

    Verify the domain

    POST /domains/{domain_id}/verify. The domain comes back with a status on each record, and once it reads verified you can send from any address on it. An unverified sender is refused rather than quietly dropped.

  5. 5

    Send your first email from Cloudflare Workers

    Post the message with the sample above. Carry an Idempotency-Key — yours or your queue's retry cannot then turn into a second email — and a User-Agent, which every request has to have. A 200 with an id means we have taken responsibility for the message.

  6. 6

    Subscribe a webhook and check its signature

    POST /webhooks with an https endpoint and the events you care about. Delivery, bounce, complaint, open and click arrive there with svix-id, svix-timestamp and svix-signature; verify against the raw body before anything parses it.

Verify webhooks in Cloudflare Workers

A Worker can receive the webhook as well as send: read await request.text() at the top of the fetch handler, then verify the three svix headers against an HMAC of id.timestamp.body with your whsec_ secret. Use crypto.subtle, which the runtime provides, and compare in constant time; the secret comes off env like the API key does.

The five steps a verifier performs, for seven runtimes in five languages, are on the webhooks reference.

Questions about Cloudflare Workers

Do I need an SMTP server to send email from a Cloudflare Worker?

No. An HTTPS API is the shape that fits the runtime: the send is one fetch subrequest, with no session to hold open. If you already have SMTP code, a Worker can open a TCP socket with connect() from cloudflare:sockets. Port 25 is blocked there, so point it at Rasket's relay, smtp.rasket.com, on 465 with TLS or 587 with StartTLS, with the username rasket and your key.

Where do I keep the API key in a Worker?

As a secret: wrangler secret put RASKET_API_KEY, then read env.RASKET_API_KEY inside the handler and check it is set, so a missing secret answers an error instead of sending Bearer undefined. With the nodejs_compat flag, process.env carries the same values too, but env is the binding Cloudflare recommends. A value in wrangler.toml is a value in your repository.

Does the send count against my Worker's subrequest limit?

Yes — one POST is one subrequest, and the limit is Cloudflare's rather than ours. If you are sending several messages in one invocation, the batch route takes up to a hundred in a single request, which is one subrequest instead of a hundred.

How do I stop a retry from sending the same email twice?

Send an Idempotency-Key header derived from the event the mail is about. A Worker that is retried, or a queue consumer that redelivers, then repeats the same key, and for 24 hours that returns the first response instead of sending a second email.

How do I verify a Rasket webhook in a Worker?

Read await request.text() first, then HMAC id.timestamp.body with your whsec_ secret using crypto.subtle and compare against each v1 signature in the svix-signature header in constant time. Refuse a svix-timestamp more than five minutes from now, and only then parse the JSON.

Make your first send

Create a key, verify a domain, and post your first message from Cloudflare Workers. The free plan does not expire.