Skip to content

Run a newsletter

Step-by-step: from “newsletter is disabled” to “first campaign sent.” Architecture rationale lives in Concepts → Newsletter; this guide is operational.

  • A Melbora website at status: "active"
  • The website’s per-client Vercel project deployed (a vercel service row exists)
  • Optional: a verified email domain attached to the website (without it, campaigns send from noreply@vantageconnections.dev)

Three equivalent ways:

Portal:

  • Open the website detail page → Newsletter tab → “Enable newsletter”

CLI:

Terminal window
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.

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:

Terminal window
vantage newsletter unsubscribe-url [<websiteId>] --email test@example.com

Paste the returned URL in a browser to confirm the page renders and a “Confirm unsubscribe” click flips the contact’s flag.

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.

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”.

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 ![alt](url) to that field; the preview updates immediately.

You can also type the Markdown syntax directly anywhere in a Markdown body:

![A photo of the new storefront](https://cdn.example.com/photos/storefront.jpg)
  • 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.

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.

Terminal window
# Freeform body
vantage 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
# Schedule
vantage newsletter send [<id>] cmp_… --schedule-at 2026-12-31T18:00:00Z

CLI (one-shot):

Terminal window
vantage newsletter compose [<id>] \
--subject "Our autumn lineup is here" \
--body-file ./this-months-letter.md \
--send

CLI (draft then send):

Terminal window
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 (![alt](https://...), http(s) only — block-centered when on their own line, inline otherwise), bullet lists (-), bare URLs, and paragraph/line breaks.

After send, status flips queuedsendingsent 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:

Terminal window
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:

Terminal window
vantage newsletter campaigns [<id>] --status drafts # filter
vantage newsletter campaigns [<id>] --status archived # archived only
vantage newsletter update [<id>] cmp_… --subject "Fix" # edit draft
vantage newsletter delete [<id>] cmp_… --yes # delete draft
vantage newsletter archive [<id>] cmp_… # hide sent
vantage newsletter unarchive [<id>] cmp_… # restore
vantage newsletter test [<id>] cmp_… --to you@example.com # preview

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)
Terminal window
vantage newsletter subscribers [<id>] # active list
vantage newsletter subscribers [<id>] --json # for piping
vantage newsletter remove-subscriber [<id>] <id-or-email> # hard delete
vantage newsletter resubscribe [<id>] <id-or-email> # flip unsubscribed → false
vantage 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)”
RequirementHow it’s met
Gmail/Yahoo bulk-sender RFC 8058 one-click unsubscribeList-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 emailAuto-injected into every campaign body
CAN-SPAM physical mailing addressSourced from the website’s CMS business profile — fill in business.streetAddress if not done
Suppression honored across campaignsProvider tracks contact.unsubscribed; campaigns auto-skip flagged contacts
Suppression survives provider switchAdapter pattern — setUnsubscribed is part of the interface, every provider implementation has to honor it
  • 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.dev will deliver fine but recipients can’t reply. For real-world newsletters, attach an email-kind domain to the website so campaigns send from newsletter@<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.

DESTRUCTIVE — drops the audience and every subscriber:

Terminal window
vantage newsletter disable [<id>] --yes

The 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.