Newsletter: From Zero to Send
#scsiwyg#devlog#newsletter#email#building-in-public#pro
David OlssonNewsletter: From Zero to Send
scsiwyg pro members can send newsletters now. No dashboard, no drag-and-drop editor, no campaign builder. You compose a newsletter the same way you write a post โ in markdown, from your IDE โ and scsiwyg handles the rest: double opt-in, batch delivery, unsubscribe compliance, and delivery tracking.
This is a Pro feature. Here's what we built and why we built it this way.
What it does
A newsletter in scsiwyg is just a post with isNewsletter: true. It goes through the same markdown pipeline โ same rendering, same code highlighting, same Mermaid diagrams. The difference is what happens after you publish: instead of sitting on a page waiting for readers, it goes to their inbox.
The full flow:
Eight MCP tools handle the entire lifecycle:
| Tool | What it does |
|---|---|
enable_newsletter | Turns newsletter on for a site, sets sender name and email |
compose_newsletter | Creates a post marked as newsletter (draft state) |
test_newsletter | Sends a test email to your own address |
preview_newsletter | Returns metadata, subscriber count, readiness status |
send_newsletter | Generates a time-limited approval link (does not send directly) |
get_subscribers | Lists subscribers with status breakdown |
get_send_stats | Recent sends, delivery counts, subscriber growth |
remove_subscriber | Admin removal of a subscriber |
The approval gate
We made a deliberate choice: send_newsletter does not send. It generates a 32-byte token and a 15-minute approval URL. You have to click the link to actually dispatch emails.
This exists because newsletters are irreversible. A typo in a blog post is a quick edit. A typo in 500 inboxes is permanent. The approval step is a 15-minute circuit breaker โ long enough to review, short enough that you don't lose context.
The approval endpoint works two ways: browser session auth (you click the link) or bearer token auth (for external platforms like AI Peers that manage their own approval layer).
Subscribe flow
When newsletter is enabled, a subscribe form appears on the blog index. Double opt-in:
- Reader enters email โ subscriber created with
status: pending - Verification email sent immediately
- Reader clicks verify link โ
status: active, welcome email sent - Every email includes a one-click unsubscribe link (RFC 8058 compliant)
Rate limited to 10 subscribe attempts per hour per IP. Duplicate handling: already active gets a message, pending gets a re-sent verification.
Delivery infrastructure
Emails go through Resend. Subscribers are batched in groups of 100. Each send is tracked individually via sendEvents โ every email gets a Resend ID, and webhooks update status as events come in:
email.deliveredโ records delivery timestampemail.openedโ records open timestampemail.bouncedโ marks subscriber as bouncedemail.complainedโ marks as unsubscribed (abuse protection)
The newsletter admin dashboard (Pro) shows per-send stats: sent, delivered, opened, clicked, with percentages. Subscriber drilldown shows individual status, verification dates, and activity.
How to use it
Enable newsletter on your site:
enable_newsletter(enabled: true, fromName: "My Blog", fromEmail: "noreply@coms.scsiwyg.com")
Compose and send:
compose_newsletter(slug: "april-update", title: "April Update", body: "# What we shipped\n\n...")
test_newsletter(slug: "april-update")
send_newsletter(slug: "april-update")
Then click the approval link. That's it.
What's next
Scheduled sends and subscriber segmentation are on the list. Right now every active subscriber gets every newsletter โ no filtering, no segments. That's fine for small lists. It won't be fine forever.
The newsletter feature ships with the same principle as everything else in scsiwyg: if you can do it from a terminal, you don't need a dashboard to do it.