Skip to content

Instantly share code, notes, and snippets.

@dector
Created August 31, 2023 11:36
Show Gist options
  • Save dector/9cac3c3ea217defe92f03bf5cc004cff to your computer and use it in GitHub Desktop.
Save dector/9cac3c3ea217defe92f03bf5cc004cff to your computer and use it in GitHub Desktop.
Send email from Cloudflare Worker
/*
Also requires DNS setup for domain:
TXT
_mailchannels
v=mc1 cfid=<YOUR_WORKER_DOMAIN_IN_CLOUDFLARE>.workers.dev
Also DKIM:
https://developers.cloudflare.com/pages/platform/functions/plugins/mailchannels/
https://support.mailchannels.com/hc/en-us/articles/7122849237389-Adding-a-DKIM-Signature
And SPF:
https://support.mailchannels.com/hc/en-us/articles/200262610-Set-up-SPF-Records
https://www.mailerlite.com/help/how-to-merge-spf-records
*/
export interface Env {
EXPECTED_KEY: string;
FROM_EMAIL: string;
FROM_NAME: string;
DKIM_DOMAIN: string;
DKIM_PRIVATE_KEY: string;
}
export default {
async fetch(request, env, ctx): Promise<Response> {
return await handler({ env, request });
},
};
const handler = async ({ env, request }: { env: Env; request: Request }) => {
if (!env.EXPECTED_KEY) {
throw new Error('EXPECTED_KEY not set');
}
if (!env.FROM_EMAIL) {
throw new Error('FROM_EMAIL not set');
}
if (!env.FROM_NAME) {
throw new Error('FROM_NAME not set');
}
if (!env.DKIM_DOMAIN) {
throw new Error('DKIM_DOMAIN not set');
}
if (!env.DKIM_PRIVATE_KEY) {
throw new Error('DKIM_PRIVATE_KEY not set');
}
if (request.method !== 'POST') {
return new Response('Not supported', {
status: 405,
});
}
const requestUrl = new URL(request.url);
const apiKey = requestUrl.searchParams.get('key') ?? '';
if (apiKey !== env.EXPECTED_KEY) {
return new Response('Wrong API KEY', {
status: 401,
});
}
let json;
try {
json = await request.json();
} catch {
return new Response('JSON body is missing or incorrect', {
status: 400,
});
}
const recipient = {
email: json.to?.email?.trim() ?? '',
name: json.to?.name?.trim() ?? '',
};
if (!recipient.email) {
return new Response('Recipient email not provided', {
status: 400,
});
}
const sender = {
email: env.FROM_EMAIL,
name: env.FROM_NAME,
}
const mail = {
subject: json.subject ?? '',
html: json.content?.html,
plainText: json.content?.plain,
};
const email_request = new Request('https://api.mailchannels.net/tx/v1/send', {
method: 'POST',
headers: {
'content-type': 'application/json',
},
body: JSON.stringify({
personalizations: [
{
to: [recipient],
dkim_domain: env.DKIM_DOMAIN,
dkim_selector: 'mailchannels',
dkim_private_key: env.DKIM_PRIVATE_KEY,
},
],
from: sender,
subject: mail.subject,
content: buildContent(mail),
}),
});
const res = await fetch(email_request);
const resJson = await res.json();
return new Response(`${res.status}\n${res.statusText}\n${JSON.stringify(resJson, null, 2)}`, {
status: res.status,
});
};
const buildContent = (mail) => {
const result: object[] = [];
if (mail.plainText) {
result.push({
type: 'text/plain',
value: mail.plainText,
});
}
if (mail.html) {
result.push({
type: 'text/html',
value: mail.html,
});
}
return result;
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment