New from emailrHealthCheck Email iconHealthCheck Email
emailr_

Send email from Cloudflare Workers with emailr_

Send transactional email from Cloudflare Workers with the fetch-based emailr_ SDK, store keys as Worker secrets and verify webhooks with Web Crypto.

Cloudflare iconCloudflarePlatform

The emailr SDK has no Node.js dependencies, it just uses fetch, so it runs in Cloudflare Workers and Pages Functions as-is. This guide sends from a Worker and verifies webhooks with the Web Crypto API.

Set up emailr_ with Cloudflare

  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.

    If Cloudflare hosts your DNS, add the records in the zone's DNS tab. TXT and MX records are DNS-only, so there is no proxy setting to worry about.

    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 add a secret

    Install the SDK and store the key as an encrypted Worker secret. Locally, put it in .dev.vars.

    Terminalbash
    npm install emailr
    npx wrangler secret put EMAILR_API_KEY
  4. 04

    Send from a Worker

    Secrets arrive on the env binding. Use ctx.waitUntil when the email shouldn't delay the response.

    src/index.tsts
    import { Emailr } from 'emailr'
    
    interface Env {
      EMAILR_API_KEY: string
    }
    
    export default {
      async fetch(request, env, ctx): Promise<Response> {
        const { email } = await request.json<{ email: string }>()
        const emailr = new Emailr({ apiKey: env.EMAILR_API_KEY })
    
        ctx.waitUntil(
          emailr.emails.send({
            from: 'Acme <hello@mail.acme.com>',
            to: email,
            subject: 'Welcome to Acme',
            html: '<p>Thanks for signing up.</p>',
          }),
        )
    
        return Response.json({ ok: true })
      },
    } satisfies ExportedHandler<Env>
  5. 05

    Verify webhooks with Web Crypto

    crypto.subtle.verify compares the HMAC in constant time. Read the body with request.text() before parsing.

    src/verify.tsts
    // Verify X-Emailr-Signature (hex HMAC-SHA256 of the raw body) with Web Crypto
    async function isValidEmailrWebhook(rawBody: string, signature: string | null, secret: string) {
      if (!signature || !/^[0-9a-f]{64}$/i.test(signature)) return false
      const enc = new TextEncoder()
      const key = await crypto.subtle.importKey(
        'raw', enc.encode(secret), { name: 'HMAC', hash: 'SHA-256' }, false, ['verify'],
      )
      const sig = new Uint8Array(signature.match(/../g)!.map((h) => parseInt(h, 16)))
      return crypto.subtle.verify('HMAC', key, sig, enc.encode(rawBody))
    }
    
    // const body = await request.text()
    // if (!(await isValidEmailrWebhook(body, request.headers.get('x-emailr-signature'), env.EMAILR_WEBHOOK_SECRET))) {
    //   return new Response('Invalid signature', { status: 401 })
    // }

Tips

  • ctx.waitUntil doesn't retry. For sends that must not be lost, push a message to a Cloudflare Queue and send from the consumer, which retries on failure.
  • Pages Functions read secrets from context.env the same way.
  • Cron Triggers work well for digests: send from the scheduled handler.

Frequently asked questions

Does the emailr_ SDK run on Cloudflare Workers?

Yes. It only depends on fetch and standard Web APIs, so it works in Workers and Pages Functions without the nodejs_compat flag.

How do I store my API key in a Worker?

Use wrangler secret put EMAILR_API_KEY. Secrets are encrypted and available on the env binding at runtime.

How do I verify emailr_ webhooks without node:crypto?

Import the secret as an HMAC key with crypto.subtle.importKey and call crypto.subtle.verify with the hex-decoded X-Emailr-Signature value.

Is emailr_ the same as Cloudflare Email Routing?

No. Email Routing forwards incoming mail. emailr_ sends transactional and marketing email from your domain and can also host shared inboxes.

Start sending in minutes

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