cloudflare-email-mailchannels
Version:
Send emails via Mailchannels on Cloudflare Workers.
125 lines (124 loc) • 3.53 kB
JavaScript
// src/mailchannels.ts
import debug from "debug";
import { Mailbox } from "mimetext/browser";
var _dryrun = false;
function set_default_dryrun(dryrun) {
_dryrun = dryrun;
}
async function mailchannels(message, dry = _dryrun) {
const log = debug("mailchannels");
let recipients = message.getRecipients();
if (!recipients) {
throw new Error("No recipients found.");
}
if (!Array.isArray(recipients)) {
recipients = [recipients];
}
const sender = message.getSender();
if (!sender) {
throw new Error("No sender found.");
}
const personalizations = recipients.map((box) => {
if (box.addr.includes("@mailchannels.net")) {
log(`Skipping MailChannels address "${box.addr}" to prevent feedback loop.`);
return null;
}
if (box.type === "To") {
return { to: [{ email: box.addr, name: box.name }] };
} else if (box.type === "Cc") {
return { cc: [{ email: box.addr, name: box.name }] };
} else if (box.type === "Bcc") {
return { bcc: [{ email: box.addr, name: box.name }] };
}
return null;
}).filter((it) => it !== null);
if (personalizations.length === 0) {
throw new Error("No recipients found.");
}
const content = [];
const text = message.getMessageByType("text/plain");
if (text) {
log("Found plain text message");
content.push({ type: "text/plain", value: text.data });
}
const html = message.getMessageByType("text/html");
if (html) {
log("Found HTML message");
content.push({ type: "text/html", value: html.data });
}
for (const attachment of message.getAttachments()) {
const type = attachment.getHeader("Content-Type");
if (typeof type !== "string") {
continue;
}
log(`Found attachment with type "${type}"`, attachment.data.length);
content.push({ type, value: attachment.data });
}
const reserved = [
"received",
"dkim-signature",
"content-type",
"content-transfer-encoding",
"to",
"from",
"subject",
"reply-to",
"cc",
"bcc"
];
const headers = message.headers.fields.reduce(
(acc, field) => {
if (typeof field.name === "string" && typeof field.value === "string") {
if (reserved.includes(field.name.toLowerCase())) {
return acc;
}
acc[field.name] = field.value;
}
return acc;
},
{}
);
const from = {
email: sender.addr,
name: sender.name
};
const reply_to_raw = message.headers.get("Reply-To");
const reply_to = [];
if (typeof reply_to_raw === "string")
reply_to.push({ email: reply_to_raw });
else if (reply_to_raw instanceof Mailbox)
reply_to.push({ name: reply_to_raw.name, email: reply_to_raw.addr });
else if (reply_to_raw instanceof Array)
reply_to_raw.forEach((reply) => {
reply_to.push({ name: reply.name, email: reply.addr });
});
const payload = {
personalizations,
from,
headers,
subject: message.getSubject() || "",
content,
reply_to
};
log(personalizations, from, headers);
const req = new Request(
"https://api.mailchannels.net/tx/v1/send" + (dry ? "?dry-run=true" : ""),
{
method: "POST",
headers: {
"content-type": "application/json"
},
body: JSON.stringify(payload)
}
);
const res = await fetch(req);
if (!res.ok) {
throw new Error(`Failed to send email: ${res.status} ${await res.text()}`);
}
log("Email sent successfully.");
log(await res.text());
}
export {
mailchannels,
set_default_dryrun
};