Sending Emails with Cloudflare Workers

Cloudflare has a partnership with MailChannels which has allowed Cloudflare to introduce email sending into its web workers. There is some documentation to set this up but its all over the place. Here are the steps I took to get email sending running, using workers.

Before getting started you want to generate public and private keys for your DKIM records.
I grabbed steps from this guide -> https://github.com/cloudsecurityalliance/webfinger.io/blob/main/docs.webfinger.io/DKIM-setup.md

Generate a DKIM private and public key:

Private key as PEM file and base64 encoded txt file:

1
openssl genrsa 2048 | tee priv_key.pem | openssl rsa -outform der | openssl base64 -A > priv_key.txt

Public key as DNS record:

1
2
echo -n "v=DKIM1;p=" > pub_key_record.txt && \
openssl rsa -in priv_key.pem -pubout -outform der | openssl base64 -A >> pub_key_record.txt
Setup DNS records:

SPF record:
TXT, YOUR DOMAIN.COM, v=spf1 ~all

DMARC record:
TYT,
_dmark,
v=DMARC1; p=none;rua=mailto:[email protected]

Mail Channels Record:

Important note, this is the domain of your worer sending emails. Its probably something like “dev.yourdomain.com” check your worker setup.
TXT,_mailchannels,v=mc1 cfid=YOUR DOMAIN.COM

Mailchannels Domain Key Record:
TXT,mailchannels._domainkey,CONTENTS OF pub_key_record.txt

Setup a new worker for sending emails

Go into cloudflare and setup a new worker.js and paste in the following code.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
addEventListener("fetch", event => {
event.respondWith(handleRequest(event.request))
})

async function handleRequest(request) {

let respContent = "";

// only send the mail on "POST", to avoid spiders, etc.
if( request.method == "POST" ) {

const formData = await request.formData();
const body = {};
for (const entry of formData.entries()) {
body[entry[0]] = entry[1];
}
fls = JSON.parse(JSON.stringify(body));

let send_request = new Request("https://api.mailchannels.net/tx/v1/send", {
"method": "POST",
"headers": {
"content-type": "application/json",
},
"body": JSON.stringify({
"personalizations": [
{ "to": [ {"email": "[email protected]",
"name": 'Email Tester'}],
"dkim_domain": 'YOURDOMAIN.com',
"dkim_selector": 'mailchannels',
"dkim_private_key": `<${PrivateKey}>`,

}
],
"from": {
"email": "[email protected]",
"name": "admin",
},
"reply_to": {
"email": `${body.email}`,
"name": `${body.name || 'No Name Provided'}`,
},
"subject": `${body.subject || 'No Subject'}`,
"content": [{
"type": "text/plain",
"value":
`Email: ${body.email}
Name: ${body.name || 'No Name Provided'}

${body.message || 'No Message'}
`,
}],
}),
});

const resp = await fetch(send_request);
respContent = resp.status + " " + resp.statusText
}

let htmlContent = "<html><head></head><body><pre>" +
`
<form method="post">
Sub: <input type="text" name="subject" value="Testing email sending" /><br>
Name: <input type="text" name="name" value="Testman Testerguy" /><br>
Email: <input type="text" name="email" value="[email protected]" /><br>
Msg: <input type="text" name="message" value="This is a test email to test the email send worker" /><br>
<input type="submit" value="Send"/>
</form>
` +
"<pre>" + respContent + "</pre>" +
"</body></html>";
return new Response(htmlContent, {
headers: { "content-type": "text/html" },
})
}

This code is crap and extremely insecure. Its a combination of code from various sources. Its super crappy. Its just a simple foundation to get your own worker set up.

Pay close attention to the status messages after posting the form.

  • 200 - Success
  • 400 - DNS records are not set up properly, or didn’t propagate. Mailchannels is rejecting request.
  • 500 - Post data isn’t correct. You won’t get any error messages, just a 500

Best of luck getting everything running.

Sources: