DEV Community

Guido Zuidhof
Guido Zuidhof

Posted on

Sending e-mails from Cloudflare Workers

In most serverless environments, Cloudflare Workers included, you can not send e-mail through SMTP. Also, many client SDKs for providers such as Mailgun or Sendgrid assume you are on the Node platform and will not work in many serverless runtimes. Luckily most providers also provide a REST API to send e-mail.

I ended up going with Mailgun, below is some example Typescript code that works in Cloudflare Workers.

declare const MAILGUN_API_BASE_URL: string;
declare const MAILGUN_API_KEY: string;

export interface EmailData {
    from: string;
    to: string;
    subject: string;
    text: string;
    html: string
    cc?: string;
    bcc?: string;
    "h-Reply-To"?: string;
    "o:testmode"?: boolean;
}


function urlEncodeObject(obj: {[s: string]: any}) {
  return Object.keys(obj)
    .map(k => encodeURIComponent(k) + "=" + encodeURIComponent(obj[k]))
    .join("&");
}


export function sendMail(data: EmailData) {
    const dataUrlEncoded = urlEncodeObject(data);
    const opts = {
        method: "POST",
        headers: {
            Authorization: "Basic " + btoa("api:" + MAILGUN_API_KEY),
            "Content-Type": "application/x-www-form-urlencoded",
            "Content-Length": dataUrlEncoded.length.toString()
        },
        body: dataUrlEncoded,
    }

    return fetch(`${MAILGUN_API_BASE_URL}/messages`, opts);
}

You will have to set the global environment variables MAILGUN_API_BASE_URL and MAILGUN_API_KEY for your worker script using wrangler. The API base url can be put in the environment variables, MAILGUN_API_KEY should be added as a secret which you can do using the wrangler cli:

wrangler secret put MAILGUN_API_KEY

It will prompt you for the secret value afterwards.

Top comments (1)

Collapse
 
ourmaninindia profile image
Alfred Tuinman

I tried this with wrangler running the worker:

[WARNING] The entrypoint src/index.ts has exports like an ES Module, but hasn't defined a default export like a module worker normally would. Building the worker using "service-worker" format...

⬣ Listening at localhost:8787
Total Upload: 0.63 KiB / gzip: 0.38 KiB
✘ [ERROR] Error on remote worker: ParseError: A request to the Cloudflare API (/accounts/57267b529e6d603103c1a98d4e7aa9ae/workers/scripts/contact-us/edge-preview) failed.

  at throwFetchError
Enter fullscreen mode Exit fullscreen mode

(/data/webapps/cloudFlare/workers-site/contact-us/node_modules/wrangler/wrangler-dist/cli.js:117147:17)
at fetchResult
(/data/webapps/cloudFlare/workers-site/contact-us/node_modules/wrangler/wrangler-dist/cli.js:117120:5)
at processTicksAndRejections (node:internal/process/task_queues:96:5)
at async createPreviewToken
(/data/webapps/cloudFlare/workers-site/contact-us/node_modules/wrangler/wrangler-dist/cli.js:120828:29)
at async createWorkerPreview
(/data/webapps/cloudFlare/workers-site/contact-us/node_modules/wrangler/wrangler-dist/cli.js:120843:17)
at async start
(/data/webapps/cloudFlare/workers-site/contact-us/node_modules/wrangler/wrangler-dist/cli.js:121364:16)
{
text: 'A request to the Cloudflare API
(/accounts/57267b529e6d603103c1a98d4e7aa9ae/workers/scripts/contact-us/edge-preview)
failed.',
notes: [
{
text: 'No event handlers were registered. This script does nothing.\n' +
' [code: 10021]'
}
],
location: undefined,
kind: 'error',
code: 10021
}