Tool reference

Newsletter tools

All 13 newsletter MCP tools with input shape, response shape, and example calls.

Thirteen tools cover subscribers, templates, segments, and campaigns.

All endpoints follow the pattern POST https://mcp.vlozi.app/tools/newsletter.<name> with Authorization: Bearer ls_xxx. All responses use this envelope:

{ "data": <payload>, "error": null }   // success
{ "data": null,      "error": "..." }  // failure (with HTTP 4xx/5xx)

Subscribers

newsletter.add_subscriber

Add a subscriber (or resubscribe one who had unsubscribed). Idempotent per email.

Scope: newsletter:subscribers.create

Input:

Field Type Notes
email string (required)
name string
tags string[] Merged with the workspace's configured default tags
source "form" | "import" | "api" Default api

Response: { subscriberId, email, created: boolean, resubscribed: boolean, pending?: boolean }

IMPORTANT

Double opt-in is on by default. A brand-new subscriber is created pending: true and sent a confirmation email — pending subscribers are never mailed by any campaign (enforced at send time regardless of segment filters). Re-adding someone who previously unsubscribed always comes back pending: true too — their earlier consent does not carry over. An already-active, already-confirmed subscriber is a pure no-op (created: false, resubscribed: false).

curl -X POST https://mcp.vlozi.app/tools/newsletter.add_subscriber \
  -H "Authorization: Bearer $VLOZI_API_KEY" \
  -d '{"email": "ben@example.com", "tags": ["from-form"]}'

newsletter.list_subscribers

List subscribers with paging.

Scope: newsletter:subscribers.read

Input:

Field Type Notes
status "all" | "active" | "pending" | "unsubscribed" Default all
tag string Only subscribers carrying this tag
limit number Max 200, default 50
offset number Default 0

Response: { subscribers: { id, email, name, status, tags, createdAt }[], total, limit, offset }

NOTE

status: "pending" means active-but-unconfirmed (double opt-in awaiting click); status: "active" means active AND confirmed.


newsletter.tag_subscriber

Add one or more tags to a subscriber — a union, not a replace.

Scope: newsletter:subscribers.create

Input: { id: string, tags: string[] } (both required, tags non-empty)

Response: { subscriberId, tags } — the full tag set after the merge


newsletter.unsubscribe_subscriber

Unsubscribe an active subscriber by id, stopping all future campaign sends.

Scope: newsletter:subscribers.create

Input: { id: string } (required)

Response: { unsubscribed: true }

NOTE

Returned unconditionally — the response doesn't distinguish "unsubscribed now" from "wasn't active to begin with."


newsletter.remove_subscriber

Permanently delete a subscriber (GDPR erase).

Scope: newsletter:subscribers.delete

Input: { id: string } (required)

Response: { deleted: true }

CAUTION

Hard delete, irreversible — the record and its history are gone. Confirm with the user first.

Templates & segments

newsletter.list_templates

List reusable email templates.

Scope: newsletter:templates.read

Input: none

Response: { templates: { id, name, subject, updatedAt }[] } — capped at 100, newest-updated first


newsletter.create_template

Create a reusable email template.

Scope: newsletter:templates.create

Input:

Field Type Notes
name string (required)
subject string (required) May contain merge variables
htmlBody string (required) May contain merge variables
textBody string Auto-derived from htmlBody if omitted
previewText string Inbox preview text
category string

Response: { template: { id, name, variables: string[] } }variables are merge tags like subscriber.name, auto-detected from the subject/body text


newsletter.create_segment

Create a saved, named, filtered subscriber list to target campaigns at.

Scope: newsletter:subscribers.create

Input:

Field Type Notes
name string (required)
description string
filterRules { tags?: string[], status?: "active" | "all", subscribed_after?: string, subscribed_before?: string } Omit entirely to match all active, confirmed subscribers

Response: { segment: { id, name, subscriberCount } }subscriberCount is a snapshot at creation time, not live-updated

IMPORTANT

Confirmed opt-in is enforced unconditionally — even status: "all" never includes unconfirmed (pending), unsubscribed, bounced, or complained subscribers. status: "all" only widens beyond "active" within the confirmed set. tags matches on any overlap. Invalid dates in subscribed_after/subscribed_before are silently ignored, not rejected.

curl -X POST https://mcp.vlozi.app/tools/newsletter.create_segment \
  -H "Authorization: Bearer $VLOZI_API_KEY" \
  -d '{"name": "Engaged last 30 days", "filterRules": {"subscribed_after": "2026-07-01T00:00:00Z"}}'

Campaigns

newsletter.create_campaign

Create a draft email campaign. Always created as a draft — call send_campaign to actually send it.

Scope: newsletter:campaigns.create

Input:

Field Type Notes
name string (required) Internal name
subject string (required)
htmlBody string (required)
textBody string
templateId string Must exist in the workspace
segmentId string Must exist in the workspace; default targets all active subscribers

Response: { campaign: { id, name, status: "draft" } }

NOTE

Returns 400 up front if segmentId or templateId doesn't exist in this workspace — validated before the campaign row is created.


newsletter.send_campaign

Send an existing draft or scheduled campaign immediately.

Scope: newsletter:campaigns.send

Input: { id: string } (required)

Response: { campaignId, status: "sending", queued: number, creditsCharged: number }

IMPORTANT

Charges credits up front — 1 credit per 5 emails (Math.ceil(recipientCount / 5)) — refunded on failure to enqueue. Only fires campaigns currently draft or scheduled (an atomic guard prevents double-firing on a race); anything else fails closed. Recipients always exclude anyone on the suppression list, unsubscribed, bounced, or complained, and always require confirmed opt-in — regardless of the target segment's own filter. Consent is re-checked again at actual dispatch time, so someone who unsubscribes between enqueue and send is skipped, not sent. Runs a CAN-SPAM pre-flight (workspace must have a configured postal address) before doing anything.

Failure reasons map to: 404 not found, 402 insufficient credits, 500 billing misconfigured, 400 for everything else (wrong_status, no_subscribers, too_many_recipients [cap 100,000], segment_not_found, message_too_large [110KB combined body], missing_postal_address, sender_settings_unavailable, queue_unavailable).

curl -X POST https://mcp.vlozi.app/tools/newsletter.send_campaign \
  -H "Authorization: Bearer $VLOZI_API_KEY" \
  -d '{"id": "camp_xxx"}'

newsletter.cancel_campaign

Cancel a scheduled campaign before it fires.

Scope: newsletter:campaigns.create

Input: { id: string } (required)

Response: { cancelled: true }

NOTE

Only acts on campaigns currently in the scheduled state — a draft campaign was never scheduled and can't be "cancelled" this way, and an already sending/sent/cancelled campaign is untouched. Any of those returns 404 with "campaign not found or not scheduled".

AI

newsletter.generate_subject

AI-write catchy subject-line candidates from a short brief.

Scope: newsletter:campaigns.create

Input: { brief: string, tone?: string, count?: number } (brief required; count 1–10, default 5, clamped server-side)

Response: { subjects: string[], creditsCharged: number, model: string }

IMPORTANT

Charges a flat 5-credit fee, refunded automatically if generation fails. Returns 503 if no AI model key is configured, 402 if you're out of credits, 403 if the plan is inactive.

curl -X POST https://mcp.vlozi.app/tools/newsletter.generate_subject \
  -H "Authorization: Bearer $VLOZI_API_KEY" \
  -d '{"brief": "We just shipped OAuth login for MCP", "tone": "playful", "count": 3}'
MCP · Tool referenceEdit on GitHub