Merge pull request #294 from Coffey-Labs/theme-styled-mail

Force the theme onto mail that styles itself
This commit is contained in:
Coffey Labs
2026-09-06 16:04:55 -07:00
committed by GitHub
16 changed files with 272 additions and 16 deletions
+35 -5
View File
@@ -189,13 +189,42 @@ function addSignedEmail(o: { which: keyof typeof SIGNED_MESSAGES; from: [string,
return e;
}
function addEmail(o: { from: [string, string]; to?: string; subject: string; daysAgo: number; mailbox: string; threadId?: string; unread?: boolean; flagged?: boolean; html?: boolean; attach?: boolean; winmail?: boolean; inReplyTo?: string }) {
/*
* A marketing template of the shape #290 was reported against.
*
* Nothing in it is unusual — an outer 600px wrapper on `bgcolor="#ffffff"`, a
* `<style>` block, a coloured call to action, a grey footer — and that is the
* point. Every one of those is enough to make `htmlDeclaresColors` true, so a
* mock without one could not show what "apply the theme to messages too" does
* to the mail people actually receive: nothing at all.
*/
const STYLED_MARKETING_HTML = `<html><head><style>
a { color:#1155CC; text-decoration:underline }
.h { font-size:20px; color:#111111 }
</style></head><body style="margin:0;background-color:#f4f4f4">
<table width="100%" bgcolor="#f4f4f4" cellpadding="0" cellspacing="0"><tr><td align="center">
<table width="600" bgcolor="#ffffff" cellpadding="0" cellspacing="0" style="background-color:#ffffff">
<tr><td style="padding:24px"><p class="h">Your order is on its way</p>
<p style="color:#333333">Thanks for shopping with us. Your parcel left the warehouse this morning.</p>
<table cellpadding="0" cellspacing="0"><tr>
<td bgcolor="#1155CC" style="border-radius:4px;padding:12px 20px">
<a href="https://example.com/track" style="color:#FFFFFF;text-decoration:none">Track your parcel</a>
</td></tr></table>
<p style="color:#666666;font-size:12px">Order #4471 &middot; placed 2 September</p>
</td></tr>
<tr><td bgcolor="#222222" style="padding:16px;color:#dddddd;font-size:12px">
You are receiving this because you bought something. <a href="https://example.com/x" style="color:#88bbff">Unsubscribe</a>
</td></tr>
</table>
</td></tr></table></body></html>`;
function addEmail(o: { from: [string, string]; to?: string; subject: string; daysAgo: number; mailbox: string; threadId?: string; unread?: boolean; flagged?: boolean; html?: boolean; styled?: boolean; attach?: boolean; winmail?: boolean; inReplyTo?: string }) {
const id = `e${counter++}`;
const received = new Date(Date.now() - o.daysAgo * 86400_000 - Math.random() * 3600_000 * 5).toISOString().replace(/\.\d{3}Z$/, "Z");
const text = `Hi,\n\nThis is a sample message about "${o.subject}". It was generated by the ihasmail mock server so you can try the interface without a real mailbox.\n\nSome highlights:\n- Keyboard shortcuts (press ? )\n- Conversation view\n- Drag & drop to folders\n\nCheers,\n${o.from[0]}\n\n> On Monday, someone wrote:\n> This is the quoted part of an earlier message.\n> It should be collapsed by default.`;
const html = `<html><body style="font-family:Arial"><p>Hi,</p><p>This is a <b>sample HTML message</b> about “${o.subject}”. It was generated by the ihasmail mock server.</p><ul><li>Keyboard shortcuts (press ?)</li><li>Conversation view</li><li><a href="https://stalw.art">Drag &amp; drop</a> to folders</li></ul><p><img src="https://example.com/tracker.gif" width="1" height="1" alt=""> <img src="cid:logo@mock" width="120" alt="logo"></p><p>Cheers,<br>${o.from[0]}</p><div class="gmail_quote">On Monday, someone wrote:<blockquote>This is the quoted part of an earlier message. It should be collapsed by default.</blockquote></div></body></html>`;
const textBlob = putBlob(text, "text/plain");
const htmlBlob = putBlob(html, "text/html");
const htmlBlob = putBlob(o.styled ? STYLED_MARKETING_HTML : html, "text/html");
const attachments: Obj[] = [];
if (o.attach) {
attachments.push({ partId: "3", blobId: putBlob("%PDF-1.4 mock", "application/pdf"), size: 48213, name: "contract-v3.pdf", type: "application/pdf", charset: null, disposition: "attachment", cid: null });
@@ -215,10 +244,10 @@ function addEmail(o: { from: [string, string]; to?: string; subject: string; day
from: [{ name: o.from[0], email: o.from[1] }], to: [{ name: "Demo User", email: o.to ?? USER }], cc: null, bcc: null, replyTo: null, sender: null,
subject: o.subject, hasAttachment: Boolean(o.attach), preview: text.slice(0, 120).replace(/\n/g, " "),
textBody: [{ partId: "1", blobId: textBlob, size: text.length, name: null, type: "text/plain", charset: "utf-8", disposition: null, cid: null }],
htmlBody: o.html ? [{ partId: "2", blobId: htmlBlob, size: html.length, name: null, type: "text/html", charset: "utf-8", disposition: null, cid: null }] : [],
htmlBody: o.html ? [{ partId: "2", blobId: htmlBlob, size: (o.styled ? STYLED_MARKETING_HTML : html).length, name: null, type: "text/html", charset: "utf-8", disposition: null, cid: null }] : [],
attachments,
bodyValues: { "1": { value: text, isEncodingProblem: false, isTruncated: false }, ...(o.html ? { "2": { value: html, isEncodingProblem: false, isTruncated: false } } : {}) },
bodyStructure: { partId: null, blobId: null, size: 0, type: "multipart/mixed", name: null, charset: null, disposition: null, cid: null, subParts: [{ partId: "1", blobId: textBlob, size: text.length, type: "text/plain", name: null, charset: "utf-8", disposition: null, cid: null }, ...(o.html ? [{ partId: "2", blobId: htmlBlob, size: html.length, type: "text/html", name: null, charset: "utf-8", disposition: null, cid: null }] : []), ...attachments] },
bodyValues: { "1": { value: text, isEncodingProblem: false, isTruncated: false }, ...(o.html ? { "2": { value: o.styled ? STYLED_MARKETING_HTML : html, isEncodingProblem: false, isTruncated: false } } : {}) },
bodyStructure: { partId: null, blobId: null, size: 0, type: "multipart/mixed", name: null, charset: null, disposition: null, cid: null, subParts: [{ partId: "1", blobId: textBlob, size: text.length, type: "text/plain", name: null, charset: "utf-8", disposition: null, cid: null }, ...(o.html ? [{ partId: "2", blobId: htmlBlob, size: (o.styled ? STYLED_MARKETING_HTML : html).length, type: "text/html", name: null, charset: "utf-8", disposition: null, cid: null }] : []), ...attachments] },
"header:List-Unsubscribe:asText": o.from[1].includes("newsletter") ? "<mailto:[email protected]?subject=unsubscribe>, <https://newsletter.example/unsub>" : null,
"header:X-Priority:asText": o.subject.startsWith("Security") ? "1 (Highest)" : null,
// Stalwart's spam filter writes the SpamAssassin-shaped set at delivery, so
@@ -244,6 +273,7 @@ for (let i = 0; i < 45; i++) {
addEmail({ from: [p[0]!, p[1]!], subject: `Re: ${subj}`, daysAgo: i * 0.7 - 0.4, mailbox: "inbox", threadId: e.threadId as string, unread: i % 8 === 0, inReplyTo: `${e.id}@mock`, html: i % 3 === 0 });
}
}
addEmail({ from: ["Shop Updates", "[email protected]"], subject: "Your order is on its way", daysAgo: 0.3, mailbox: "inbox", html: true, styled: true });
addEmail({ from: ["Demo User", USER], to: "[email protected]", subject: "Draft: ideas for the retreat", daysAgo: 0.1, mailbox: "drafts", html: true }).keywords = { $draft: true, $seen: true };
/*