codeflood logo

Sending Email from Cloudflare Pages Functions

In my last post, I mentioned I rebuilt this website using Eleventy. As part of the rebuild I also wanted to modernize my hosting, which previously was a basic cPanel hosting plan with a local Australian hosting provider. My website is statically generated, so the cPanel hosted was fine. Although, I did have one piece of functionality that required server-side logic, and that is the comment submission form.

There are a lot of options out there to farm off your comments to some other service and just inject them into your pages using JavaScript. But I wanted to retain full ownership over the comment data and make sure I didn't have any issues rendering them in the future. Although new comments coming in are dynamic, once a comment has been submitted and is approved to be published, it then becomes static content as well. No need to inconvenience countless electrons to load up a block of data which hasn't changed.

So when I decided to host my website on Cloudflare pages, I also needed to migrate the comment submission form functionality. This was actually really easy, thanks to Cloudflare Pages Functions. The gist of it is I use a Cloudflare Pages Function to accept the comment and save it to KV store. I don't want to log into Cloudflare every day to check for new comments. Instead, I want Cloudflare to notify me when a new comment is submitted, so I know I need to go and review it. So I wanted to send myself an email from the function.

The Cloudflare documentation mentions 2 methods for sending email.

The first is using Email Routing. This sounded exactly like what I wanted; I didn't want to send email to any address, I just wanted to send it to my email account. But I had a few issues in getting Email Routing to work for me. Firstly, it requires binding the email service into the worker through the wrangler.toml configuration file. But Cloudflare Pages Functions don't use a wrangler.toml configuration file. All service bindings for Cloudflare Pages Functions are done through the Cloudflare UI. So I attempted to use a normal Cloudflare Worker for the comment submission, rather than a Cloudflare Pages Function. But still, I couldn't get the email service to send email. When calling the service my worker would eventually receive a timeout on the call. This might be because the email service is only available to Email Workers, and not "regular" Cloudflare Workers.

So I looked into the second option, which is to use MailChannels to send email. MailChannels allows sending email to any address, as long as you authorize both MailChannels and the Cloudflare Pages Function to send email on behalf of your domain. The method to allow this authorization is to create some TXT DNS records on your domain. The instructions on how to setup these records are in both the Cloudflare documentation (Enable MailChannels for your account - Domain Lockdown and SPF support for MailChannels) and the MailChannels documentation (Secure your domain name against spoofing with Domain Lockdown).

Being an impatient developer, I initially only skimmed the documentation. So when I first attempted to call the MailChannels API from my Cloudflare Pages Function it failed with a 500 status and a text response:

Error code: 1016

This was very cryptic. The MailChannels API documentation states the response format for a call to the https://api.mailchannels.net/tx/v1/send endpoint (which is what I was using) should be JSON, and if there are any errors, they will be listed in the JSON response. But that's not what I was getting. I also couldn't find any reference to this error code anywhere in the MailChannels documentation.

Reading over the documentation more thoroughly I realized I was missing the DNS records. But the thing about DNS is the records are cacheable, and often will be cached by appliances and applications for up to 24 hours. That means if you need to adjust DNS records and they're not quite right, you may have to wait another 24 hours for the records to expire and the updated records to be fetched again. And this was also the case I faced, as I tried to get the name of my Cloudflare Pages Function correct. In the end, I needed 2 DNS TXT records:

name: _mailchannels
content: v=mc1 cfid=codeflood.pages.dev

name: codeflood.net
content: "v=spf1 include:relay.mailchannels.net ~all"

The first record is the Domain Lockdown record which tells MailChannels I'm authorizing the Cloudflare Worker with id codeflood.pages.dev to send email on behalf of the domain. The second record is the SPF (Sender Policy Framework) record which tells MailChannels it is allowed to send email on behalf of the domain. There can be multiple include entries in the SPF record, if you've authorized multiple services to send email for your domain.

So the moral of the story: read the documentation properly and if you have issues with DNS, you'll only see the changes in 24 hours. So try again tomorrow.

Comments

Leave a comment

All fields are required.