Start a Project
All guides

Dev Stack

Cloudflare Workers email for South African teams.

Cloudflare Email Routing is the quietly brilliant feature that every South African indie builder should know about. For a .co.za domain on Cloudflare DNS, you can route hi@yoursite.co.za, orders@yoursite.co.za and support@yoursite.co.za to a single Gmail or Outlook inbox — for free. Add Email Workers on top and you can parse inbound email, trigger webhooks, store messages in KV or D1, and send automated replies via Resend or Postmark. What Cloudflare Workers cannot do is send email directly over SMTP (no port 25 outbound), so the pattern is always: inbound via Email Routing → process in a Worker → outbound via a provider API. This guide documents the setup we use for ecomsolve.co.za and SA client stores where Shopify order webhooks kick off custom notification flows, all running at the JNB and CPT edge.

Updated 15 April 2026 · 9 min read · Cloudflare Workers · Joshua Kaplan

Prerequisites

  • A .co.za domain on Cloudflare DNS (nameservers moved from Afrihost/Xneelo/domains.co.za)
  • A Cloudflare account on the free plan
  • A destination inbox (Gmail, Outlook, FastMail) for forwarded mail
  • A Resend or Postmark account if you need to send programmatic replies
  • Node 20+ and Wrangler CLI for writing Email Workers

Step 1. Enable Email Routing on your Cloudflare zone

Cloudflare dashboard → Email → Email Routing → Enable. Cloudflare adds the required MX, TXT (SPF) and DKIM CNAME records automatically. If you already have Google Workspace or another mail host, Email Routing conflicts with that — pick one. For forwarding-only .co.za domains, Email Routing is free; for hosted mailboxes you still need Workspace or equivalent.

Step 2. Verify a destination address

Add the Gmail/Outlook address you want forwards to land in. Cloudflare sends a verification email — click the link. You can verify multiple destinations and route different aliases to different humans (orders@ → ops, support@ → customer success).

Step 3. Create forwarding rules

Email Routing → Routing Rules → Create address. hi@yoursite.co.za → josh@gmail.com. Catch-all rules are also available — route anything@yoursite.co.za to a single inbox to never miss a typo. Free plan allows up to 200 rules.

Step 4. Write an inbound Email Worker

For anything beyond forwarding — auto-reply, webhook to CRM, lead scoring, Shopify order lookup — bind an Email Worker. The Worker receives the full raw email (headers + body) as a ReadableStream. Parse it, do work, then either forward, reply or drop.

email-worker/src/index.ts
import PostalMime from 'postal-mime';

export interface Env {
  RESEND_API_KEY: string;
  SHOPIFY_WEBHOOK: string;
}

export default {
  async email(message: ForwardableEmailMessage, env: Env) {
    const parsed = await PostalMime.parse(message.raw);
    const from = parsed.from?.address ?? '';
    const subject = parsed.subject ?? '';

    await fetch(env.SHOPIFY_WEBHOOK, {
      method: 'POST',
      headers: { 'content-type': 'application/json' },
      body: JSON.stringify({ from, subject, text: parsed.text }),
    });

    await fetch('https://api.resend.com/emails', {
      method: 'POST',
      headers: {
        Authorization: `Bearer ${env.RESEND_API_KEY}`,
        'content-type': 'application/json',
      },
      body: JSON.stringify({
        from: 'EcomSolve <hi@ecomsolve.co.za>',
        to: [from],
        subject: `Re: ${subject}`,
        text: 'Thanks — we got your email and will respond within 24 hours (SAST).',
      }),
    });

    await message.forward('josh@gmail.com');
  },
};

Step 5. Configure wrangler.jsonc for the Email Worker

Email Workers use the same wrangler.jsonc pattern as HTTP Workers, but you bind the email event separately. The script needs to be deployed before you can select it in Email Routing.

wrangler.jsonc
{
  "name": "email-worker",
  "main": "src/index.ts",
  "compatibility_date": "2026-02-18",
  "observability": { "enabled": true }
}

Step 6. Bind the Worker to an email address

After wrangler deploy, go to Email Routing → Email Workers → Create. Pick your deployed Worker and the address to bind it to (e.g. orders@yoursite.co.za). Inbound mail to that address now invokes the Worker instead of forwarding directly.

Step 7. Send transactional mail via Resend from the Worker

Workers cannot SMTP. The trust path is: your Worker → Resend API → recipient. Resend signs with DKIM using your verified .co.za domain, so the recipient sees legitimate mail from you, not from Cloudflare. See our Resend setup guide for the DNS records.

Step 8. Handle Shopify order webhooks with an email response

A common SA ecommerce pattern: Shopify fires an order/create webhook to a Cloudflare Worker at the JNB edge, the Worker looks up the customer, sends a branded confirmation via Resend, logs to D1. Total time: sub-300ms from order placement to send, and cheaper than Klaviyo for low-volume stores.

SA gotchas

  • Cloudflare Workers cannot send email directly — no SMTP, no port 25. You must use a provider HTTP API (Resend, Postmark, SES). Plan around this before you start.
  • Email Routing is forwarding-only — you cannot host a mailbox on it. If you need to reply from hi@yoursite.co.za in Gmail, configure Gmail's Send Mail As feature and authorise it via SMTP submission through Resend or Google Workspace.
  • DMARC alignment across Cloudflare Email Routing (inbound) and Resend (outbound) requires both to use your .co.za domain in the From header. If Resend is signing with resend.com as fallback, Gmail will show "via resend.com" — fix the DKIM CNAMEs.
  • Email Workers count toward your Workers request quota. Free plan = 100k requests/day shared across all Workers. A Worker triggered by every inbound lead email is fine; one triggered by newsletter bounces might exhaust the quota.
  • PostalMime is the usual MIME parser for Email Workers — the Worker runtime does not include one. Add it to package.json and bundle with wrangler.
  • Reply-To vs From: if your Worker sends via Resend, set Reply-To: back to the original sender's address so the human reply thread routes correctly. Otherwise replies go to a no-reply you do not monitor.

Frequently asked questions

Does Cloudflare Email Routing cost anything for a .co.za domain?

Email Routing is free on all Cloudflare plans, including the free tier. You get up to 200 routing rules, unlimited verified destinations and Email Workers (which count against your normal Workers quota). There is no per-email fee for routing itself.

Can I replace Google Workspace with Cloudflare Email for my .co.za business?

Only if you need forwarding, not hosted mailboxes. Cloudflare Email Routing forwards mail to an existing inbox — it does not host one. For a two-person SA team that wants hi@yoursite.co.za landing in a personal Gmail, Email Routing is free and perfect. For a team that needs shared inboxes, calendar and Drive, keep Workspace (R135/user/month).

Why can Cloudflare Workers not send email directly?

Outbound port 25 (SMTP) is blocked on Cloudflare Workers for abuse-prevention reasons shared across the industry. The workaround is always to send via a provider HTTP API — Resend, Postmark, Amazon SES, Mailgun. The Worker makes an HTTPS call; the provider handles SMTP handoff to the recipient.

How does this work with POPIA for South African senders?

Email Routing stores mail briefly in transit at Cloudflare edges during forwarding — document this transfer in your privacy notice alongside your outbound provider (Resend, Postmark). POPIA Section 72 cross-border transfer rules apply. Unsubscribe and consent obligations under POPIA Section 69 apply to outbound marketing regardless of the Worker pattern.

What happens when my Email Worker throws an error?

If the Worker throws, Cloudflare returns a bounce to the sender — not ideal. Wrap your logic in try/catch, log to the observability endpoint, and fall back to message.forward() so humans still see the mail. Never let an uncaught exception drop legitimate inbound email.