Skip to content

feat(mail): send a plain-text part and link a hosted web version - #85

Merged
clbotdev merged 2 commits into
mainfrom
issue-35-plain-text-web-view
Oct 19, 2026
Merged

clbotdev merged 2 commits into
mainfrom
issue-35-plain-text-web-view

Conversation

@clbotdev

Copy link
Copy Markdown
Contributor

Closes #35. Part of #79.

  • Every send now has a text/plain part, generated from the final HTML with renderEmailToPlainText (new in @sendlit/email-editor, built on react-email's toPlainText): links as text (url), • bullets, no images/styles/preheader, collapsed blank lines. Transactional sends accept their own text (stored on the row, migration 0011).
  • "View in browser": the managed footer links a hosted, signed, per-recipient web version, also available as {{ view_in_browser_url }}. GET /view/:token renders it with the recipient's merge data, without tracking, as a sandboxed (CSP sandbox), noindex document; bad links get a friendly 404.
  • The editor default no longer starts with a plain "View in browser" line that linked nowhere.
  • Changesets for @sendlit/email-editor and @sendlit/email-blocks.

Testing

  • Unit: plain-text conversion, footer link, web-view route (personalised, untracked, sandboxed, 404s for tampered/tracking tokens and other teams), sendMail text handling, transactional text.
  • Browser E2E: broadcast arrives with a clean text part (address, View in browser (url), Unsubscribe (url)); the view link opens the personalised web version (no /track/, CSP sandbox) whose unsubscribe link still works; invalid link → "This email is not available"; transactional sends get generated text or the caller's own; the editor lists the new merge tag.

POST /sequences/:id/emails/:emailId/test (and the MCP tool
send_sequence_email_test) renders the email exactly like a real send,
through the delivery source the send would use, with a [Test] subject.
It defaults to the signed-in user, takes up to five addresses, skips
suppressed ones and is capped at 50 recipients per team per hour.

Tracking tokens carry a contact id that matches nobody and the
unsubscribe link uses a test token whose page explains it was a test.
The email records lastTestSentAt. The broadcast editor and the
full-screen email editor get a Send test dialog.

The per-recipient render pipeline moves to mail/campaign.ts so real and
test sends share it.

Refs #34, #79
Every send now carries a text/plain alternative generated from the final
HTML (links as "text (url)", bulleted lists, no images, styles or
preheader), or the caller's own text for transactional sends (new
optional `text`, stored on the row).

Marketing emails link a hosted web version from the managed footer, also
available as the {{ view_in_browser_url }} merge tag. GET /view/:token
serves it for the signed recipient with their merge data and no
tracking, as a sandboxed document. The editor default no longer starts
with a plain "View in browser" line that linked nowhere.

Refs #35, #79
@clbotdev
clbotdev merged commit c4ca6b1 into main Oct 19, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add a plain-text part to every send and a working "view in browser" link

2 participants