Tool reference

Comms tools

All 11 comms MCP tools with input shape, response shape, and example calls.

Eleven tools cover transactional email — sending, your vanity sender alias, deliverability logs, and the suppression list. Comms is email-only today; SMS returns 501 Not Implemented.

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

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

Sending

comms.send_message

Send a transactional email.

Scope: comms:send

Input: { to: string, subject?: string, html?: string, text?: string, metadata?: object } (to required; provide html and/or text)

Response: { messageId: string, to: string, sent: boolean, status: string, reason?: string }

IMPORTANT

Rate-limited to 60 sends per 60 seconds per tenant (429 once exceeded). Billed at 1 credit per 5 emails. If the recipient is on your suppression list, the send is silently skipped — you get sent: false, status: "suppressed", reason: "Recipient is suppressed (bounce or complaint)" back, not an error, and it isn't billed.

curl -X POST https://mcp.vlozi.app/tools/comms.send_message \
  -H "Authorization: Bearer $VLOZI_API_KEY" \
  -d '{"to": "ben@example.com", "subject": "Your export is ready", "html": "<p>Download it here.</p>"}'

Sender identity

comms.claim_alias

Claim a vanity sending alias — <slug>@mail.vlozi.app.

Scope: comms:sender.write

Input: { slug: string } (required, 3–30 chars, lowercase letters/digits/single hyphens)

Response (new claim): { alias: string, fromEmail: string, chargedCredits: 20 } Response (already yours): { alias: string, fromEmail: string, alreadyOwned: true }

IMPORTANT

Costs 20 credits, charged only on a genuinely new claim (idempotent re-claiming your own alias is free). Rejects reserved/role addresses (postmaster, support, …) and common brand names with 422. Returns 409 if another workspace claims the same slug in a race — the charge is automatically refunded in that case.


comms.check_alias_availability

Check whether a slug is available before claiming it.

Scope: comms:sender.read

Input: { slug: string } (required)

Response: { available: boolean, slug: string, address: string, reason: string | null }

NOTE

reason explains an unavailable result (too_short, too_long, invalid_format, reserved, or taken) — always 200, even for malformed input. This check can't tell "you already own it" apart from "someone else owns it"; both read as unavailable. claim_alias itself does handle that distinction.


comms.get_sender_settings

Get your workspace's sender configuration.

Scope: comms:sender.read

Input: none

Response: { settings: { senderName, senderEmail, fromEmail, vloziAlias, phoneNumber, brandLogoUrl, brandPrimaryColor, orgName, orgStreet, orgCity, orgRegion, orgPostalCode, orgCountry, updatedAt } }


comms.update_sender_settings

Update sender configuration. Pass only the fields to change.

Scope: comms:sender.write

Input: { sender_name?, sender_email?, from_email?, phone_number?, brand_logo_url?, brand_primary_color?, org_name?, org_street?, org_city?, org_region?, org_postal_code?, org_country? } (all optional)

Response: { settings: object } — the full updated row

WARNING

from_email is validated against what you're actually allowed to send as: on mail.vlozi.app you can only use the shared notify@mail.vlozi.app or your own claimed alias; on any other domain, that exact domain must already be verified under Domains. An invalid choice returns 422 with a message explaining what to do — claim an alias or verify a domain first. Pass null to clear it back to the platform default.


comms.get_sender_identities

List every address you're allowed to send from right now.

Scope: comms:sender.read

Input: none

Response: { identities: { address, label, kind: "default" | "alias" | "domain", domain? }[], current: string | null }

NOTE

Always includes the shared notify@mail.vlozi.app, plus your claimed alias (if any) and one entry per verified custom domain. Check this before calling update_sender_settings so the address you pick is guaranteed to pass validation.

Deliverability

comms.list_logs

List recent send logs.

Scope: comms:logs.read

Input: { status?: string, channel?: string, limit?: number } (limit max 200, default 50)

Response: { logs: { id, channel, recipient, subject, status, provider, providerMessageId, error, metadata, sentAt, createdAt, deliveryStatus }[], count: number }

NOTE

deliveryStatus is the latest provider webhook event (delivered, bounced, opened, clicked, complained) and can differ from status — e.g. status: "sent" with deliveryStatus: "bounced" once the bounce webhook lands.


comms.get_analytics

Send/delivery stats over a trailing window.

Scope: comms:logs.read

Input: { days?: number } (1–365, default 30)

Response:

{
  "window_days": 30,
  "sends": { "sent": 0, "failed": 0, "suppressed": 0, "queued": 0 },
  "events": { "delivered": 0, "bounced": 0, "complained": 0, "opened": 0, "clicked": 0 },
  "rates": { "delivery_rate": 0, "bounce_rate": 0, "complaint_rate": 0 }
}

comms.list_suppression

List your do-not-mail list.

Scope: comms:logs.read

Input: { q?: string, limit?: number, offset?: number } (limit max 200, default 50)

Response: { entries: { email, reason, createdAt }[], total: number }

NOTE

Rows land here automatically on a hard bounce or spam complaint, in addition to whatever you add manually.


comms.add_suppression

Manually add an address to the suppression list.

Scope: comms:sender.write

Input: { email: string, reason?: string } (reason defaults to "manual")

Response: { email: string } (normalized) — idempotent, adding an already-suppressed address is a no-op

CAUTION

Be careful suppressing an address tied to your own account's auth email (password reset, invites) — there's no in-product way to un-suppress your way back into account recovery. Use remove_suppression deliberately if that happens.


comms.remove_suppression

Remove an address from the suppression list.

Scope: comms:sender.write

Input: { email: string } (required)

Response: { email: string } (normalized) — a no-op, still 200, if the address wasn't suppressed

NOTE

Only use this once the recipient has explicitly re-opted in — nothing in the API enforces that, it's on you.

MCP · Tool referenceEdit on GitHub