/**
* "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) => `- ${escapeHtml(s)}
`).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 = `
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
${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,
)}
|
${escapeHtml(
brand.name,
)}
|
|
Your eSIM is ready.
Install it now and it stays dormant until you arrive. ${
order.planName ? `Your plan: ${escapeHtml(order.planName)}.` : ""
}
|
${installButton}
${qrBlock}
${manualBlock}
|
${
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,
};
}