Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save landgenoot/07e3d64e3f3db49c5075e5428cbc87eb to your computer and use it in GitHub Desktop.
Save landgenoot/07e3d64e3f3db49c5075e5428cbc87eb to your computer and use it in GitHub Desktop.
Cloudflare Email Worker that parses flags in the local-part of the email address (e.g. somerandomtext-a@example.com), even if the receipient is in the BCC. Corresponding blog post: https://daanmiddendorp.com/tech/2023/06/28/parsing-bcc-recipient-with-cloudflare-email-workers
/**
* Cloudflare Email Worker that parses flags (e.g. -a) and forwards accordingly.
*
* Example:
* somerandomtext-a@example.com would forward to Alice.
* somerandomtext-b@example.com would forward to Bob.
*/
async function email(message, env, ctx) {
let messageSource = await fetchMessageSource(message.raw);
let blacklist = await fetchBlacklist("https://example.com/blacklist.txt");
let recipient = parseRecipient(messageSource);
if (blacklist.indexOf(recipient) > -1) {
return message.setReject("Address is blocked");
}
if (/\b.+-a@example.com\b/gi.test(recipient)) {
await message.forward("alice@company.com");
} else if (/\b.+-b@example.com\b/gi.test(recipient)) {
await message.forward("bob@company.com");
} else {
await message.forward("catchall@company.com");
}
}
/**
* Fetch readable stream, so that we can convert the raw message into a string.
* @param ReadableStream message
* @return string messageSource
*/
async function fetchMessageSource(message) {
const rawMessage = new Response(message);
const arrayBuffer = await rawMessage.arrayBuffer();
const messageSource = String.fromCharCode.apply(null, new Uint8Array(arrayBuffer));
return messageSource;
}
/**
* Download text file and split on newlines.
* @param string url location of the blacklist
* @return string[] blacklist
*/
async function fetchBlacklist(url) {
const response = await fetch(url, {});
const blacklist = (await response.text()).split("\n");
return blacklist;
}
/**
* Parse receipient from Received header (even if BCC'ed)
* Example header:
*
* Received: by mail-vk1-f194.google.com with SMTP id 71fab90a1359d-47167a4ce3cso2249208e0c.2
* for <iets2-d@example.com>; Wed, 28 Jun 2023 04:30:15 -0700 (PDT)
*
* @param string messageSource
* @return string recipient email address
*/
function parseRecipient(messageSource) {
return messageSource.match(/\b(?<=for <).+@example.com(?=>;)\b/gi)[0];
}
export default {
email
}
@uwolfer
Copy link

uwolfer commented Feb 23, 2025

message.to is the recipient. If you are not the recipient (because you receive the email as BCC), you cannot filter using this property.

No, this seems not to be the case. message.to is not the the same as the to property from the header. Give it a try, it really simplifies the code and works well. also for cc and bcc cases ;)

@landgenoot
Copy link
Author

You are right. RFC5322.To vs RFC5321.RcptTo I am not sure if the API has changed in the meantime (Cloudflare email workers was in beta back then) or I have been ignorant. Probably the latter. Thank you!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment