Run a newsletter
Step-by-step: from “newsletter is disabled” to “first campaign sent.” Architecture rationale lives in Concepts → Newsletter; this guide is operational.
Prerequisites
Section titled “Prerequisites”- A Melbora website at
status: "active" - The website’s per-client Vercel project deployed (a
vercelservice row exists) - Optional: a verified email domain attached to the website (without it, campaigns send from
noreply@vantageconnections.dev)
1. Enable newsletter
Section titled “1. Enable newsletter”Three equivalent ways:
Portal:
- Open the website detail page → Newsletter tab → “Enable newsletter”
CLI:
vantage newsletter enable [<websiteId>]MCP / Claude:
vantage_newsletter_enable { websiteId: "ws_…" }Behind the scenes the platform mints a Resend audience, pushes VANTAGE_WEBSITE_ID and NEXT_PUBLIC_NEWSLETTER_ENABLED=1 to the Vercel project, and triggers a redeploy. ~60s later the site has a working subscribe form + unsubscribe page.
The output reports the new audience id, the resolved default from-address, and the enable timestamp.
2. Verify the site picked it up
Section titled “2. Verify the site picked it up”Hit the deployed URL. The subscribe form is rendered by the template’s <SubscribeForm> component — present in places the operator chose to drop it (typically the footer or end of an About page). You can also navigate to /unsubscribe?token=<test> to confirm the page renders (it’ll show “Missing unsubscribe token” without a valid token).
To mint a test unsubscribe URL without sending a real campaign:
vantage newsletter unsubscribe-url [<websiteId>] --email test@example.comPaste the returned URL in a browser to confirm the page renders and a “Confirm unsubscribe” click flips the contact’s flag.
3. Compose + send your first campaign
Section titled “3. Compose + send your first campaign”Portal (recommended): Newsletter tab → “New campaign” opens a full-screen 2-pane composer (overlay on top of the page so the preview gets the full viewport width; stacks vertically on narrow screens):
- Form pane: subject, sender (optional), template picker, per-template fields OR freeform Markdown body, schedule (optional).
- Preview pane: live email preview in a sandboxed iframe — re-renders on every keystroke so you see exactly what subscribers will see. Mock email-client chrome shows the From + Subject as the recipient would see them.
- Bottom toolbar: Send now (or Schedule send when a future time is set), Save draft, and Send test (enter your email + click — receives the rendered email tagged with
[TEST]in the subject).
The From field is optional — leave it blank and the campaign goes out from your resolved default sender (a verified newsletter@<your-domain> if you’ve wired an email-kind domain, otherwise the platform fallback noreply@vantageconnections.dev). The placeholder text shows you what the default actually is so you don’t have to guess.
Templates
Section titled “Templates”Pick from the template dropdown:
- Default newsletter — clean header band + Markdown body + optional CTA + sign-off. Best for monthly updates.
- Announcement — single-CTA hero layout for launches, events, time-sensitive offers.
- Freeform Markdown — bare body wrapped in the standard email shell. Use when you want full control.
Each template ships with operator-fillable fields (headline, CTA text, brand color, etc.). The preview updates as you fill them in. Templates are platform-managed — vantage newsletter templates list shows the registry and each one’s field schema.
Conditional fields: some fields are gated on others. On the default template, the CTA button link field only appears once you’ve typed CTA button text — and at that point it becomes required (so a “button text without a link” half-state can’t ship). Clear the text and the link field disappears; any value you typed there is dropped from the payload.
Multi-line text fields (like the default template’s sign-off) accept newlines — each newline renders as a line break in the email. Use them for two-line closings like “Thanks,\nthe team”.
Adding pictures
Section titled “Adding pictures”Every Markdown body field has an + Insert image button below it. Click it, paste a publicly-accessible image URL (your existing CDN, S3 bucket, or media library), optionally add alt text for screen readers, click Insert. The composer appends standard Markdown  to that field; the preview updates immediately.
You can also type the Markdown syntax directly anywhere in a Markdown body:
- Standalone image lines are centered and full-width up to the email’s 600px shell.
- Inline images (mid-paragraph) flow with the surrounding text.
- Only
http:///https://URLs are accepted —file://,javascript:, etc. are stripped. - Email clients fetch images directly with no cookies, so the URL must be public.
Schedule a send
Section titled “Schedule a send”Set the “Schedule (optional)” datetime to a future local time. The send button label switches to Schedule send. The campaign goes into queued status with scheduledAt set; Resend releases it at that time.
# Freeform bodyvantage newsletter compose [<id>] \ --subject "Our autumn lineup is here" \ --body-file ./this-months-letter.md \ --send
# Template-driven (note: keep the JSON in single quotes — inner apostrophes# would break the shell-quoting; use plain text or double-escape if needed)vantage newsletter compose [<id>] \ --subject "We launched!" \ --template announcement \ --vars '{"headline":"We are live","intro":"Read on...","ctaText":"Try it","ctaUrl":"https://acme.example/launch"}' \ --send
# Schedulevantage newsletter send [<id>] cmp_… --schedule-at 2026-12-31T18:00:00ZCLI (one-shot):
vantage newsletter compose [<id>] \ --subject "Our autumn lineup is here" \ --body-file ./this-months-letter.md \ --sendCLI (draft then send):
vantage newsletter compose [<id>] --subject "…" --body-file letter.md# returns { id: "cmp_…", status: "draft" }
vantage newsletter send [<id>] cmp_…MCP:
vantage_newsletter_create_campaign { websiteId, subject, body }vantage_newsletter_send_campaign { websiteId, campaignId }The body is Markdown. The platform renders it to HTML before handing to the provider. Supported: headings (#, ##, ###), bold (**), italic (*), links ([text](url)), images (, http(s) only — block-centered when on their own line, inline otherwise), bullet lists (-), bare URLs, and paragraph/line breaks.
4. Monitor delivery + engagement
Section titled “4. Monitor delivery + engagement”After send, status flips queued → sending → sent over a few minutes (provider-side). The portal polls in-flight campaigns every 8 seconds automatically — you don’t need to refresh. Each in-flight campaign shows a spinner badge with an ETA (“delivering — ~2 min until done”) computed from the audience size.
Once delivered, stats populate. Open the campaign in the portal for a stats grid (sent / delivered / opened / clicked / bounced / unsubscribed). Or via CLI:
vantage newsletter campaign [<id>] cmp_…5. Drafts, archives, and the campaign list
Section titled “5. Drafts, archives, and the campaign list”The campaign list has status filter pills — All / Drafts / Scheduled / Sending / Sent / Archived. Default view hides archived.
- Drafts: click any draft to re-open the composer pre-filled with its current state — edit, save, or send. Each draft has a Delete button (two-step confirm) for cleanup.
- Sent campaigns: click to open a read-only detail view with stats. Has an Archive button to hide from the default list (campaigns stay in Resend forever; archive is platform-side metadata only).
- Archived: pick the Archived filter to find them. Each has an Unarchive button.
Sent campaigns cannot be deleted — Resend doesn’t support unsending and we don’t either. Archive is the way to declutter.
CLI mirrors the same operations:
vantage newsletter campaigns [<id>] --status drafts # filtervantage newsletter campaigns [<id>] --status archived # archived onlyvantage newsletter update [<id>] cmp_… --subject "Fix" # edit draftvantage newsletter delete [<id>] cmp_… --yes # delete draftvantage newsletter archive [<id>] cmp_… # hide sentvantage newsletter unarchive [<id>] cmp_… # restorevantage newsletter test [<id>] cmp_… --to you@example.com # preview6. Manage subscribers + suppression list
Section titled “6. Manage subscribers + suppression list”Active subscribers show in the Newsletter tab’s Subscribers card. Unsubscribed contacts are on a separate suppression page, linked from the Newsletter header (“X unsubscribed”).
The suppression page exists because:
- It’s low-frequency content — clients shouldn’t see it every day
- It contains “do not send” data — easy to misread on the main view
- It needs its own per-row action: Resubscribe (flips the unsubscribed flag back; the contact rejoins future campaigns)
vantage newsletter subscribers [<id>] # active listvantage newsletter subscribers [<id>] --json # for pipingvantage newsletter remove-subscriber [<id>] <id-or-email> # hard deletevantage newsletter resubscribe [<id>] <id-or-email> # flip unsubscribed → falsevantage newsletter unsubscribe [<id>] <id-or-email> # operator-mark (someone opted out by email)The portal Newsletter tab has the same active list with inline “Remove” buttons; the suppression page has per-row “Resubscribe”. Subscriber records carry an “active” or “unsubscribed” state; unsubscribed contacts stay in the audience (so the same email can’t be re-added by accident) but receive no further campaigns.
Compliance posture (what the platform handles for you)
Section titled “Compliance posture (what the platform handles for you)”| Requirement | How it’s met |
|---|---|
| Gmail/Yahoo bulk-sender RFC 8058 one-click unsubscribe | List-Unsubscribe: <url> + List-Unsubscribe-Post: List-Unsubscribe=One-Click headers on every send; site exposes the POST endpoint |
| CAN-SPAM unsubscribe link in every commercial email | Auto-injected into every campaign body |
| CAN-SPAM physical mailing address | Sourced from the website’s CMS business profile — fill in business.streetAddress if not done |
| Suppression honored across campaigns | Provider tracks contact.unsubscribed; campaigns auto-skip flagged contacts |
| Suppression survives provider switch | Adapter pattern — setUnsubscribed is part of the interface, every provider implementation has to honor it |
Operator gotchas
Section titled “Operator gotchas”- First send from a brand-new Resend domain: deliverability is bumpy until the domain has 7-14 days of consistent send history. Start small (one campaign to a known-engaged list) before broad sends.
- No-reply addresses: the default
noreply@vantageconnections.devwill deliver fine but recipients can’t reply. For real-world newsletters, attach an email-kind domain to the website so campaigns send fromnewsletter@<your-domain>. - Body length: Resend doesn’t enforce a hard cap but mailbox-side spam filters dislike emails over ~100KB rendered. Keep markdown bodies tight.
Disable (when needed)
Section titled “Disable (when needed)”DESTRUCTIVE — drops the audience and every subscriber:
vantage newsletter disable [<id>] --yesThe platform deletes the Resend audience, removes the NEXT_PUBLIC_NEWSLETTER_ENABLED env var, redeploys to hide the subscribe form, and drops the service row. Subscriber data is gone; export first if you need it.