/** * "Your eSIM is ready" — the message a customer actually installs from. * * Written as a plain HTML string rather than React Email components, for the * reason given in resend.ts: this file ships into somebody else's repository, * and an operator re-wording their own delivery email should not first have to * learn a component library. Inline styles and a single-column table are the * only layout most mail clients render the same way twice. * * The layering is deliberate, worst case first: * * 1. A QR code, attached and referenced as `cid:` — never a `data:` URI, * which Gmail strips. * 2. A direct-install link, which needs no typing at all on iOS 17.4+. * 3. The SM-DP+ address and the activation code as two separate values, * because those are two separate fields on the phone. One opaque * `LPA:1$…$…` blob is not something a person can act on. * 4. The steps, in words, for iPhone and for Android. * * A QR code alone fails the commonest case there is: the customer is reading * this on the phone the eSIM is for, and cannot photograph its own screen. */ import { ANDROID_INSTALL_STEPS, IPHONE_INSTALL_STEPS, directInstallUrl, manualActivationCode, } from "@/lib/esim-install"; import type { EmailAttachment, EmailMessage } from "./resend"; /** The QR image, already fetched and base64-encoded by the caller. */ export interface QrImage { base64: string; contentType: string; } export interface QrDeliveryBrand { name: string; supportEmail?: string; supportUrl?: string; appUrl?: string; colors?: { accent?: string; text?: string }; } export interface QrDeliveryOrder { orderId: string; planName?: string; iccid?: string; activationCode?: string; smdpAddress?: string; matchingId?: string; } export interface QrDeliveryInput { to: string; order: QrDeliveryOrder; brand: QrDeliveryBrand; /** Absent when the operator's fulfilment produced no renderable QR. */ qr?: QrImage | null; } /** * The content id the HTML points at. Any stable token works; this one is * spelled out so the test can assert the HTML and the attachment agree. */ export const QR_CONTENT_ID = "esim-qr"; function escapeHtml(value: string): string { return value .replace(/&/g, "&") .replace(//g, ">") .replace(/"/g, """) .replace(/'/g, "'"); } function steps(title: string, items: readonly string[]): string { return `

${escapeHtml( title, )}

    ${items.map((s) => `
  1. ${escapeHtml(s)}
  2. `).join("")}
`; } /** One labelled credential, in a font a person can read a hyphen in. */ function credential(label: string, value: string): string { return `

${escapeHtml(label)}

${escapeHtml( value, )}

`; } /** * Build the message. Pure: every input is an argument, so the shape of what * goes out can be asserted without a network or an API key. */ export function buildQrDeliveryEmail(input: QrDeliveryInput): EmailMessage { const { to, order, brand, qr } = input; const accent = brand.colors?.accent ?? "#1a1a1a"; const lpa = manualActivationCode(order); const attachments: EmailAttachment[] = []; let qrBlock = ""; if (qr) { attachments.push({ filename: "esim-qr.png", content: qr.base64, content_type: qr.contentType, content_id: QR_CONTENT_ID, }); qrBlock = ` eSIM activation QR code

Scan this from another device’s camera.

`; } const installButton = lpa ? ` Install on this iPhone

One tap, no typing. iPhone on iOS 17.4 or later. On Android, use the steps below.

` : ""; const credentials = [ order.smdpAddress ? credential("SM-DP+ address", order.smdpAddress) : "", order.matchingId ? credential("Activation code", order.matchingId) : "", order.iccid ? credential("ICCID", order.iccid) : "", ].join(""); const manualBlock = credentials ? `

Install manually

${credentials}
${steps("On iPhone", IPHONE_INSTALL_STEPS)} ${steps("On Android", ANDROID_INSTALL_STEPS)}
` : ""; const activateUrl = brand.appUrl ? `${brand.appUrl.replace(/\/+$/, "")}/activate/${encodeURIComponent(order.orderId)}` : null; const supportLine = brand.supportEmail ? `Questions? ${escapeHtml(brand.supportEmail)}` : brand.supportUrl ? `Questions? Contact support` : ""; const html = ` ${escapeHtml( brand.name, )}
${installButton} ${qrBlock} ${manualBlock}

${escapeHtml( brand.name, )}

Your eSIM is ready.

Install it now and it stays dormant until you arrive. ${ order.planName ? `Your plan: ${escapeHtml(order.planName)}.` : "" }

${ activateUrl ? `

All of this is also on your activation page.

` : "" }

${supportLine}

Order ${escapeHtml( order.orderId, )} · You are receiving this because you bought a plan from ${escapeHtml( brand.name, )}.

`; // null drops a line that does not apply. "" is a blank line on purpose: // filtering every empty string turns the five install routes into one paragraph. const text = [ "Your eSIM is ready.", order.planName ? `Plan: ${order.planName}` : null, "", lpa ? `Install on iPhone (iOS 17.4+), one tap: ${directInstallUrl(lpa)}` : null, "", "Install manually:", order.smdpAddress ? ` SM-DP+ address: ${order.smdpAddress}` : null, order.matchingId ? ` Activation code: ${order.matchingId}` : null, order.iccid ? ` ICCID: ${order.iccid}` : null, "", "On iPhone:", ...IPHONE_INSTALL_STEPS.map((s, i) => ` ${i + 1}. ${s}`), "", "On Android:", ...ANDROID_INSTALL_STEPS.map((s, i) => ` ${i + 1}. ${s}`), "", activateUrl ? `Activation page: ${activateUrl}` : null, brand.supportEmail ? `Questions? ${brand.supportEmail}` : null, `Order ${order.orderId}`, ] .filter((line) => line !== null) .join("\n"); return { to, subject: `Your ${brand.name} eSIM is ready`, html, text, attachments, }; }