/**
* Turn one ready order into one delivered email.
*
* The interesting part is getting the QR out of whatever the operator's
* fulfilment handed us and into an attachment. `EsimOrder.qrCode` is
* documented as "a data: URI or an https URL" because both are things a
* fulfilment API plausibly returns, and neither can be dropped into an email
* as-is:
*
* - a `data:` URI in `
` is stripped by Gmail and shows as a broken
* image, which is the single most expensive way for this email to fail;
* - an `https` URL survives, but only while that host is up and only if the
* reader's client loads remote images, which many do not by default.
*
* So both are normalised to bytes and attached with a `content_id`, and the
* HTML points at `cid:`. An attachment is part of the message; it cannot 404
* next month and it does not need remote images enabled.
*/
import brand from "@/brand.config";
import type { EsimOrder } from "@/vendor/carrier/types";
import { buildQrDeliveryEmail, type QrImage } from "@/vendor/email/qr-delivery";
import { emailConfigured, fromAddress, sendEmail, type EmailSendResult } from "@/vendor/email/resend";
/** A QR code is a few kB. Anything this size is not one, and is not attached. */
const MAX_QR_BYTES = 2 * 1024 * 1024;
// `[\s\S]` rather than the `s` flag: the template's tsconfig targets an older
// ES level than dotAll, and a base64 payload can contain newlines.
const DATA_URI = /^data:([^;,]+)?(;base64)?,([\s\S]*)$/;
function toBase64(bytes: Uint8Array): string {
// Chunked so a large-ish buffer cannot blow the argument limit of
// String.fromCharCode. btoa exists in both Workers and Node 18+.
let binary = "";
const chunk = 0x8000;
for (let i = 0; i < bytes.length; i += chunk) {
binary += String.fromCharCode(...bytes.subarray(i, i + chunk));
}
return btoa(binary);
}
/**
* Fetch or decode the QR into base64 bytes. Null whenever that cannot be done
* — the email is still worth sending, because the manual credentials and the
* written steps below the QR are what most people use anyway.
*/
export async function resolveQrImage(qrCode: string | undefined): Promise {
if (!qrCode) return null;
const data = DATA_URI.exec(qrCode);
if (data) {
const [, contentType, isBase64, payload] = data;
if (!isBase64 || !payload) return null;
return { base64: payload.replace(/\s+/g, ""), contentType: contentType || "image/png" };
}
if (!/^https:\/\//i.test(qrCode)) return null;
try {
const res = await fetch(qrCode, { cache: "no-store" });
if (!res.ok) return null;
const buffer = await res.arrayBuffer();
if (buffer.byteLength === 0 || buffer.byteLength > MAX_QR_BYTES) return null;
return {
base64: toBase64(new Uint8Array(buffer)),
contentType: res.headers.get("content-type")?.split(";")[0]?.trim() || "image/png",
};
} catch {
return null;
}
}
export type QrDeliveryResult =
| { sent: true; id?: string; withQrImage: boolean }
| { sent: false; reason: "unconfigured" | "no-from-address" | "rejected" | "network-error"; detail?: string };
/**
* Send the delivery email for one ready order.
*
* Never throws and never reports success it did not get. A storefront with no
* Resend key returns `unconfigured`, which the caller shows as a plain
* sentence — the order is complete either way, and pretending an email went
* out is worse than saying none did.
*/
export async function deliverQrEmail(order: EsimOrder, to: string): Promise {
if (!emailConfigured()) return { sent: false, reason: "unconfigured" };
if (!fromAddress()) return { sent: false, reason: "no-from-address" };
const qr = await resolveQrImage(order.qrCode);
const message = buildQrDeliveryEmail({
to,
order,
brand: {
name: brand.name,
supportEmail: brand.supportEmail,
supportUrl: brand.supportUrl,
appUrl: brand.appUrl,
colors: { accent: brand.colors.accent },
},
qr,
});
const result: EmailSendResult = await sendEmail(message);
if (!result.ok) return { sent: false, reason: result.reason, detail: result.detail };
return { sent: true, id: result.id, withQrImage: qr !== null };
}