UNPKG

cloudflare-email-mailchannels

Version:

Send emails via Mailchannels on Cloudflare Workers.

125 lines (124 loc) 3.53 kB
// 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 };