Unitpost has two ways to send: the API (transactional mail — receipts, password resets, one-to-one messages triggered by your app) and Campaigns (one-to-many marketing sends to an audience). Most of the dashboard exists to make the campaign path safe and repeatable, and to give you visibility into both.
Three resources form a pipeline, each building on the one before it: Contacts → Segments → Campaigns.
Contacts are the people you can email — an address plus optional name and custom fields. They carry a marketing subscription state; an unsubscribed or suppressed contact is never mailed.
Segments are named groups of contacts. A campaign sends to exactly one segment, so a segment is the unit of "who receives this send." Only subscribed members are mailable.
Campaigns combine a template (the content, authored on the Templates page) with a segment (the audience) and a verified from-domain, then send now or on a schedule.
Read the chain bottom-up when something won't send
A campaign can only send if its template is publishable, its segment has subscribed members, and its from-domain is verified. When a send is blocked, walk back down the chain — domain → segment members → template — and the offending link is almost always one of those three.
Deleting a segment or campaign archives it rather than removing it. Archiving keeps history intact and avoids orphaning references. Archived items are hidden from pickers, can be restored, and are swept ~30 days later once nothing active references them.
Active references block destructive actions
You can't archive a segment that a non-terminal campaign (draft, scheduled, sending) needs — cancel or finish that campaign first. This is a clean, explained stop in both the UI and the API.
Each page has its own guide below. New to the product? Follow the sidebar order: verify a Domain, build a Template, add Contacts, group them into a Segment, then create a Campaign.
The Dashboard is the home screen: a summary of sending volume, deliverability signals (bounces, complaints), and recent campaigns and messages. It's a read-only overview — every figure links through to the page where you can act on it.
Use it as a daily health check. A rising bounce or complaint rate, a domain that's slipped out of verification, or a campaign stuck in draft all surface here before they become a deliverability problem.
Start here when onboarding
If your account is new, the Dashboard guides you through the first run: verify a domain, create a template, and send a test. Those are the prerequisites every campaign depends on.
A domain is your sending identity. Before you can send from an address like hello@yourcompany.com, the domain yourcompany.com must be added here and verified by publishing the DNS records we generate (a DKIM TXT record, a custom MAIL FROM subdomain, and a DMARC policy).
Verification runs automatically once DNS propagates — you don't click anything. A domain moves through Pending → Verified; if records change or are removed it can fall back, at which point sends from it are blocked.
Re-checked at send time
A domain's status is verified again the moment a campaign sends, not just when you created it. If the domain isn't in a sending-allowed state at send time, the send is blocked with a clear message — so a domain that lapses after a campaign is scheduled won't silently send unauthenticated mail.
Domains feed directly into Templates and Campaigns: the from-address you pick when sending must belong to a verified domain. This is the first link in the contacts → segments → campaigns chain.
You can also receive mail at your domain. Enable inbound routing on the domain's Receiving tab and publish the MX records, and we'll parse incoming mail and POST it to your webhook as clean JSON. See the Inbound email guide for details.
Templates are reusable email designs — a subject and a body built from the component library (or raw HTML). A template can be used by a campaign, or referenced by API sends via its id, so you author content once and send it many ways.
Templates carry a category. Marketing templates are the ones campaigns use. Transactional templates are for API-triggered, one-to-one mail (receipts, resets) and are intentionally NOT selectable for campaigns.
Transactional templates can't be campaigned
Creating a campaign from a transactional template is blocked by the server (not just the form), because campaign mail to a marketing audience must honor unsubscribe and consent rules that transactional mail doesn't. Use — or clone into — a marketing template instead.
Images and other assets a template references live in the Library; a template that's still a draft, or that references missing assets, can't back a campaign send.
The Library is shared asset storage for your workspace — upload images and files once, then reference them from any template. Hosting assets here (rather than hot-linking from elsewhere) means they stay available and load fast for every recipient.
Assets are referenced by Templates, so deleting an asset that a live template uses will leave a broken image in that mail. Check usage before removing anything you're unsure about.
Keep originals reasonable
Large images slow rendering and can trip spam filters. Upload appropriately sized assets — the Library is for delivery, not as a full-resolution archive.
Contacts are individual recipients: an email address plus optional name and custom fields you define. Each contact has a marketing subscription state — subscribed contacts are mailable; unsubscribed or suppressed contacts are skipped automatically and never receive campaign mail.
Import contacts in bulk from CSV. The import is per-row resilient: valid rows are created even if others fail. You get a summary of imported, skipped, and rejected rows, so one bad line never sinks the file.
Custom-field limits
Custom fields are capped per contact (and on key/value length). Rows that exceed the caps are rejected in the import rather than truncated silently.
A suppression is an address-level block that excludes a single email address from every send — transactional, batch, and campaign — including CC and BCC. The match is case-insensitive. Suppressions keep you from mailing addresses that have already bounced or complained.
When a message hard-bounces or the recipient complains, the address is added to your suppression list automatically and silently skipped on future sends. This is the single most important thing you can do for deliverability.
Add addresses by hand from the Suppressions tab on the Contacts page — paste one or many and they're suppressed in a single step. The same operations are available via API at /api/v1/suppressions.
Excluded, not failed
A send to a suppressed address isn't an error — the send succeeds, the suppressed recipients are simply dropped, and the result tells you how many were excluded and why. One bad address never fails the whole send.
Addresses on the Unitpost-wide list (reputation/abuse protection) appear with a “Platform” badge and can't be removed from your workspace. They're shown so you understand why a send was skipped — if you believe one is in error, contact support.
Any member with the manage-suppressions permission can remove an address you added. Removing an address that previously bounced or complained shows a warning, as re-sending to it can damage deliverability.
A segment is a named audience — a group of contacts a campaign can target. Each campaign sends to exactly one segment, so segments are how you decide who receives a given send.
The list shows each segment's subscribed-member count alongside its total. Only subscribed members are mailable. A segment with zero subscribed contacts is flagged, because a campaign to it can't send.
Empty segments block sends
A campaign whose segment has no subscribed members is stopped before it sends — there's no one to mail. Watch for the "0 subscribed" / "Empty" badge in the list and fix the audience before scheduling.
Deleting a segment archives it. Archived segments disappear from pickers, but the historical record of campaigns stays intact. You can restore an archived segment, and it's permanently swept ~30 days later once unused.
Can't archive while actively referenced
If a non-terminal campaign (draft, scheduled, sending) still targets a segment, archiving is blocked with a 409 — cancel or finish that campaign first.
A topic is a subscription category — "Product updates," "Promotions" — that a contact can opt out of. It's orthogonal to segments and global unsubscribe: a contact can stay on your list but silence one kind of mail.
Manage topics from the Topics page in the dashboard. Recipients control membership via the unsubscribe preference page, and campaigns simply point at a topic to scope a send.
Each topic has one decision baked in — its default — which sets what "no explicit preference" means for a contact who has never touched it:
Opt-out (the default) — contacts are auto-enrolled and receive the topic unless they opt out. Use it for newsletters and product updates.
Opt-in — contacts are silent until they explicitly subscribe. Use it for beta invites or sensitive announcements you don't want going out by default.
An explicit choice always wins
Once a contact subscribes or unsubscribes from a topic, that choice overrides the default in both directions. The default only governs contacts who've never recorded a preference.
Scope a campaign to a topic and anyone opted out is skipped at send (counted as suppressed in the validation report). You can't point a campaign at an archived or missing topic.
One-click unsubscribe respects the topic
Mail for a topic-scoped campaign carries a topic-aware unsubscribe link and an RFC 8058 List-Unsubscribe-Post header, so a recipient's one-click opt-out silences just that topic — not all of your marketing. They keep getting everything else they didn't object to.
Manage topics on the Topics page (or via /api/v1/email/topics) — create, list, edit, and archive. Archiving is blocked (409) while a non-terminal campaign still targets it. A contact's per-topic state is read and set at /api/v1/contacts/{id}/topics.
A campaign is a one-to-many send: a template (content) + a segment (audience) + a from-address on a verified domain, sent immediately or scheduled. Campaigns are the end of the contacts → segments → campaigns pipeline.
Before a campaign sends, every one of these must hold (enforced on the server):
The from-domain is verified and sending-allowed (re-checked at send, not just at create).
There's a non-empty subject (from the campaign or the template default).
The template is a publishable marketing template — not transactional, not an archived/draft template.
The segment is not archived and has at least one subscribed member.
Any schedule is within bounds — not in the past, and no further than 90 days out.
When a send is blocked, the validation dialog lists exactly which gate failed and how to fix it. The API returns the same reasons as a structured 409/422.
A campaign moves through draft → scheduled → sending → sent (or canceled/failed). Terminal campaigns can be archived directly; a non-terminal campaign must be canceled first. Archived campaigns are restorable and auto-pruned after ~30 days.
Scheduling semantics
A schedule in the past is treated as "send now"; a schedule beyond 90 days is rejected at both create and send time. These bounds are consistent across the dashboard and the v1 API.
The Emails page is the message-level log: one row per email sent (campaign or API). Each row shows the recipient, subject, delivery state, and engagement (opens, clicks).
Use it to answer "what happened to this specific message?" — trace a delivery, confirm an open, or see why something bounced or was suppressed.
Suppression is automatic
A hard bounce or complaint suppresses that address, so future sends skip it. You'll see the suppression reflected here and on the contact — it protects your domain's reputation without manual cleanup.
Activity is the cross-resource event feed — domain verifications, template changes, campaign sends, key creation, webhook deliveries, and more, in one timeline. Where the Emails page is per-message, Activity is per-account.
It's the place to reconstruct "who changed what, when" — useful for debugging an unexpected state, confirming an action took effect, or auditing access. Every member can reach it.
API keys authenticate requests to the public v1 API. Create a key, choose a scope preset — Full, Sending, or Read only — and optionally narrow it to specific capabilities. The secret is shown once at creation; store it somewhere safe immediately.
Scope tightly
Give each integration the least it needs — a server that only sends mail gets a Sending key, not Full. Keys can never hold privileged capabilities (managing other keys, the team, the workspace, or billing); those stay in the dashboard behind a human login.
Revoke a key the moment it's no longer needed or may have leaked; revocation is immediate. Pair keys with Webhooks to build a full integration: keys for outbound API calls, webhooks for inbound event notifications.
Webhooks push events to your application as they happen — a delivery, a bounce, a contact change — so you can react without polling. Register a public HTTPS endpoint, subscribe to the events (or categories) you care about, and we POST a signed payload whenever one fires.
Full reference in the Webhooks guide
This page manages your endpoints; the API-reference Webhooks guide covers the payload shapes, signature verification (including timestamp tolerance), the retry schedule, the HTTPS/SSRF requirement, and rotating the signing secret. Read it before wiring up your verifier.
Each endpoint shows a live health state — Enabled, Failing, or auto-Disabled after sustained failures — so a dead URL surfaces early. Use Send test to fire a sample signed event and confirm your handler works end to end.
The Migration Assistant (Settings → Migration) moves your data from another email platform into Unitpost in about five minutes. It supports Resend, SendGrid, Mailgun, Postmark, Brevo, and Loops, and carries over seven kinds of data: contacts (with custom fields and subscription state), segments, topics, templates, domains, suppressions, and webhook endpoints.
Connect — pick your provider and paste an API key (a read-only key is all it needs; it only ever reads from your old provider). The key is validated, KMS-encrypted, used once to fetch a snapshot, then deleted.
Preview — the assistant fetches everything in the background and shows a full preview: per-entity counts, samples, conflicts with data already in your workspace, and explicit warnings for anything the provider can't export. You can leave the page; the job persists and updates live.
Accept — nothing is written until you accept. Choose which entities to include and how to resolve conflicts (skip or update), then import. A full report shows every row that was imported, skipped, or failed.
For every provider, one API key is all it takes — the assistant does the rest. A few providers have one extra detail worth knowing before you paste the key:
SendGrid — a key with Full Access, or Restricted Access with read permission on Contacts, Marketing, Templates, Suppressions, Sender Authentication, and Webhooks (Settings → API Keys).
Mailgun — your account-level Private API key (Settings → API Security), not a domain sending key.
Postmark — an Account API token (Account → API Tokens) migrates everything in one run: domains plus every server's templates, suppressions, and webhooks. A single Server token also works, but can't read domains.
Brevo — an API key from Settings → SMTP & API. One thing to check first: if your account blocks unauthorized IPs for API keys (Security → Authorized IPs), turn it off before migrating — under “Blocking unauthorized IP addresses”, choose Deactivate for API keys. Brevo rejects API calls from unlisted IPs even when the key is valid. Keys created with the MCP toggle work too.
Read-only on the source — the assistant never sends, changes, or deletes anything on your old provider.
Key custody — the API key is encrypted with KMS the moment you submit it, stored separately from the job, used once for the snapshot fetch, and deleted immediately after (also on failure, cancel, or expiry). It is never shown or re-readable.
Opt-in writes — your Unitpost workspace is untouched until you explicitly accept the preview. Cancel any time before that.
Zero downtime — your current provider keeps sending throughout. Migrate, verify DNS on the recreated domains, then switch your API key over whenever you're ready.
Domains are recreated with fresh DKIM keys — you'll publish new DNS records here; your old provider's records stay valid until you remove them.
Webhook endpoints get new signing secrets, so update your verifier after import.
Templates carry over their HTML and variables; provider-specific template syntax is flagged in the preview when it can't be converted.
Suppressions (bounces, complaints, unsubscribes) carry over so you never re-email someone who opted out.
Anything a provider's API can't export is called out in the preview — never silently dropped. The preview expires if unaccepted, and one migration runs per workspace at a time.
Ask Unit to help
The Unit assistant in the dashboard can check your migration's status, walk you through the preview, and explain any warning — just ask it about your migration.
The SMS log shows every message your workspace has sent or received, live — queued, sent, delivered, or failed, with the carrier's failure reason when there is one. Billing is per segment: 160 GSM-7 characters fit one segment (153 each when a message spans several); Unicode messages fit 70/67. The composer shows the segment count as you type.
Marketing SMS is held to a stricter bar than email: the recipient must have prior express consent on record, and sends respect recipient-local quiet hours (a marketing message queued at midnight is deferred to the morning window, never dropped and never sent at 2am). Transactional messages — receipts, alerts, verification codes — are not window-gated.
STOP always wins
When a recipient replies STOP, the carrier and Unitpost both record the opt-out immediately. Further sends to that number are suppressed on the SMS channel — it never touches their email subscription, and vice versa.
SMS needs an origination identity the way email needs a verified domain. Start with the sandbox sender: it works immediately but only delivers to verified test numbers (a carrier rule for unverified senders). A dedicated toll-free number unlocks production sending to any US number after a one-time verification.
A sender moves through registration and verification states before it's active, and its status is re-checked at send time — a suspended or released number can't silently keep sending. Number rental is billed to your card as a recurring line item, never drawn from your usage wallet.
Keep marketing and OTP apart
Carriers degrade deliverability for numbers that mix bulk marketing with time-sensitive transactional traffic. When you start sending campaigns, add a dedicated marketing sender so a blast can never slow your verification codes.
The SMS dashboard summarizes the channel's last 30 days: how many messages went out, how many the carriers confirmed delivered, and how many failed or were suppressed. It's the channel-scoped counterpart of the email dashboard — same idea, SMS-honest numbers (no opens or clicks; carriers don't report them).
From here, jump into the message log for per-message delivery detail, or into Senders to provision an origination number.
SMS Activity is the same workspace activity stream you know from email, locked to channel messages: sends, delivery receipts, retries, and opt-outs (STOP). Use it to answer "what happened on SMS recently" without scanning the full cross-resource feed.
SMS templates are plain text with the same {{variable}} merge fields as email templates — {{first_name}} and your custom contact fields resolve per recipient at send time, and a send fails closed rather than shipping a literal token. The editor shows the live segment count as you type (160 GSM-7 characters per segment; 70 for Unicode).
Templates start as drafts; publish one to use it in sends and campaigns. For marketing messages, end with an opt-out hint like "Reply STOP to opt out" — STOP is honored automatically either way.
SMS topics work like email topics — contacts opt in and out per category — but the preferences are tracked per channel: "Product updates" can be on for email and off for SMS for the same contact. A campaign tied to a topic skips recipients who opted out of it.
STOP always wins on top: a recipient who replies STOP is suppressed on the SMS channel entirely, regardless of topic preferences — and it never touches their email subscription.
An SMS campaign sends a published SMS template to every reachable contact in a segment. Marketing SMS is held to the strict bar: only recipients with prior express consent on record receive it, sends respect the recipient-local quiet-hours window (a blast fired at midnight waits for the morning window — deferred, never dropped), and delivery is paced to your sender's carrier throughput.
Budget pauses, never surprises
If your messaging balance runs out mid-campaign, the campaign pauses — remaining recipients are untouched and sending resumes once you top up. Billing is per segment actually sent.
AI in Unitpost
First-class ways AI meets email in Unitpost: UnitAI, the assistant built into your dashboard, plus a Model Context Protocol server and drop-in Agent Skills for your own agents. Everything authenticates with scoped, revocable keys and flows through the same send gate as the REST API — every action is visible in your dashboard and webhooks.
UnitAIBeta
Unit is the AI teammate inside your dashboard — open the Ask Unit widget from any page and describe the outcome you want. It drives the real product through the same gated tools you use, and always shows you the plan (and asks) before anything sends.
Design templates by describing them — “a green-themed 20%-off template for our plant app”; Unit builds it from real components, then you tweak it in the editor.
Launch and schedule campaigns — it sets up the send, shows the plan, and waits for your approval.
Build segments and manage contacts — plain English in, a real segment out; list cleanup and suppressions included.
Explain your numbers — bounces, opens, reputation, DNS: Unit reads your activity and answers with the next step.
A human is one tap away — “Talk to a human” hands the thread to our team whenever you want.
Ask UnitBeta
Hi, I'm Unit. How can I help?
Draft a campaignCheck my deliverabilityFind a contactExplain a bounceTalk to a human
The Unitpost skill packages everything a coding agent needs to integrate Unitpost correctly the first time — the API shape, the SDKs, the ID scheme, scopes, pagination, error handling, and send-safety rules — as a skill it loads on demand. Where the MCP server gives an agent the tools to act, the skill teaches it how Unitpost works.
Install it with the skills CLI — it auto-detects your agents (Claude Code, Cursor, Codex, Copilot, Gemini, Windsurf, and 60+ more) and writes the skill into each one's skills directory:
Shell
# Install the Unitpost skill into the current project
npx skills add unitpostcom/skills --skill unitpost
# …or globally, for every project
npx skills add unitpostcom/skills --skill unitpost -g
# List what's available first
npx skills add unitpostcom/skills --list
Pairs with the MCP server: the skill teaches the agent how to think about Unitpost; the MCP server below gives it the tools to act. Install both for the best experience.
Scoped by design: a skill never widens what a key can do — capability limits, suppression, tracking, and webhooks all still apply.
Open source: the skill lives at unitpostcom/skills — documentation only, no secrets; you supply the API key at runtime.
MCP server
A live Model Context Protocol server that exposes Unitpost as tools any MCP client can call — one tool per API operation (email_send, email_campaigns_send, email_domains_verify, and the rest). It authenticates with the same API key as the REST API and runs every call through the same gate.
OAuth 2.1 — add the URL, approve in the browserscopes match API capabilities; tokens refresh automatically
Auth (legacy)
Authorization: Bearer pk_live_YOUR_KEYworkspace API key from Settings → API keys; for clients that can only paste a key
Tools
one per API operation (email_send, email_campaigns_send, …)
Create the key on www.unitpost.com → Settings → API keys. In every snippet, replace pk_live_YOUR_KEY and keep the Bearer prefix. Endpoint: https://mcp.unitpost.com/mcp. Each tool needs its matching capability (e.g. email_send → emails:send).
Pick your client — each guide shows exactly where to open and what to paste. There is also an MCP guides index.
Add a remote server with the URL below — Cursor opens Unitpost in the browser to approve. No key to copy. Your client opens Unitpost in the browser — pick a workspace, approve the scopes, and you're connected. Tokens refresh automatically. If Cursor ever signs you out, the Authenticate button re-runs the same approval (a 401 from us triggers its reconnect).
Prefer this over pasting secrets. To pin credentials instead, add an auth object with CLIENT_ID: cursor-oauth (public client, PKCE — no secret needed).
2
Reload and verify
Reload MCP servers in Cursor. unitpost should show connected with tools like email_send, email_campaigns_send, email_domains_verify.
3
Send a test
Ask the agent to send a test email from a verified domain, then confirm it in Unitpost Activity.
No browser on this machine? For headless setups, CI, SSH, or clients that can only paste a key, use the API-key setup instead.
Add the server URL, then authenticate from the in-session MCP panel. The browser opens at the Authenticate step, not at add time. API keys still work as a fallback.
Run once. This only saves the URL — no browser opens yet.
Shell
claude mcp add --transport http unitpost https://mcp.unitpost.com/mcp
Use -s user to install for every project. claude mcp list now shows unitpost as ! Needs authentication — that is expected.
2
Authenticate with OAuth (recommended)
Start a Claude Code session, run /mcp, select unitpost, and choose Authenticate. Your browser opens to Unitpost — pick a workspace, approve the scopes, and you're connected. No key to copy. Your client opens Unitpost in the browser — pick a workspace, approve the scopes, and you're connected. Tokens refresh automatically.
If the browser doesn't open automatically, copy the URL shown in the terminal and open it manually. Tokens refresh automatically; if auth ever lapses, repeat this step.
3
Verify
Run claude mcp list (or /mcp in a session). unitpost should show connected with its tools listed — no ! Needs authentication flag.
Don't see it? Fully quit and reopen the client — closing the window is often not enough — then check again.
4
Send a test
Ask Claude to send an email from a verified domain and confirm it in Unitpost Activity.
No browser on this machine? For headless setups, CI, SSH, or clients that can only paste a key, use the API-key setup instead.
Claude Desktop connects with OAuth: add the server URL, approve in the browser, done. No browser or older plan? The headless guide has the mcp-remote bridge config.
In Claude Desktop: Settings → Connectors → Add custom connector. Paste the server URL below as the Remote MCP server URL and complete the browser approval. No key to copy. Your client opens Unitpost in the browser — pick a workspace, approve the scopes, and you're connected. Tokens refresh automatically.
Remote MCP server URL
https://mcp.unitpost.com/mcp
2
Verify
The unitpost connector should show as connected with its tools available in chat.
Don't see it? Fully quit and reopen the client — closing the window is often not enough — then check again.
3
Send a test
Ask Claude to send an email from a verified domain and confirm it in Unitpost Activity.
No browser on this machine? For headless setups, CI, SSH, or clients that can only paste a key, use the API-key setup instead.
In ChatGPT, add the server URL as a connector and approve in the browser. The Codex CLI reads static config only (no browser flow) — its key setup lives on the headless page.
In ChatGPT, open the MCPs tab and connect a custom MCP. Select Streamable HTTP (not STDIO), paste the server URL below, then complete the browser approval. No key to copy. Your client opens Unitpost in the browser — pick a workspace, approve the scopes, and you're connected. Tokens refresh automatically.
MCP server URL
https://mcp.unitpost.com/mcp
2
Verify
The unitpost connector should show as connected with its tools available. Codex CLI users: the CLI can't do the browser flow — follow the headless guide instead.
Adding a connector requires a restart before chats see it: restart the Codex app, then check /mcp. Don't see it? Fully quit and reopen the client — closing the window is often not enough — then check again.
3
Send a test
Ask Codex to send an email from a verified domain and confirm it in Unitpost Activity.
No browser on this machine? For headless setups, CI, SSH, or clients that can only paste a key, use the API-key setup instead.
Create or edit `.vscode/mcp.json` in your workspace (or MCP: Add Server from the Command Palette for a user-level entry) with the server URL below, then start it. No key to copy. Your client opens Unitpost in the browser — pick a workspace, approve the scopes, and you're connected. Tokens refresh automatically.
Add this block to `~/.gemini/settings.json` (no headers — Gemini discovers OAuth from the 401). No key to copy. Your client opens Unitpost in the browser — pick a workspace, approve the scopes, and you're connected. Tokens refresh automatically.
Restart the Gemini CLI and run /mcp. unitpost should list its tools. If it shows as needing authentication, run /mcp auth unitpost — the browser opens for approval, and tokens refresh automatically after that.
3
Send a test
Ask Gemini to send an email from a verified domain and confirm it in Unitpost Activity.
No browser on this machine? For headless setups, CI, SSH, or clients that can only paste a key, use the API-key setup instead.
In Windsurf: Settings → Cascade → MCP Servers → Add Server, or paste this into `~/.codeium/windsurf/mcp_config.json`. Windsurf uses serverUrl (not url) for remote HTTP. No key to copy. Your client opens Unitpost in the browser — pick a workspace, approve the scopes, and you're connected. Tokens refresh automatically.
Put this in ~/.config/opencode/opencode.jsonc (or project opencode.json). No key to copy. Your client opens Unitpost in the browser — pick a workspace, approve the scopes, and you're connected. Tokens refresh automatically.
In Grok: add a custom connector with the name and URL below, then approve in the browser. The xAI marketplace plugin lives at github.com/unitpostcom/unitpost-grok-plugin (catalog PR on hold until Connect is verified).
Add a custom connector. Name it Unitpost, paste the server URL below (the /mcp URL — streamable HTTP — not an /sse URL). No key to copy. Your client opens Unitpost in the browser — pick a workspace, approve the scopes, and you're connected. Tokens refresh automatically.
Name
Unitpost
Server URL
https://mcp.unitpost.com/mcp
Text
https://mcp.unitpost.com/mcp
Don't see it? Fully quit and reopen the client — closing the window is often not enough — then check again.
2
Send a test
Ask Grok to send a test email from a verified domain and confirm it in Unitpost Activity.
No browser on this machine? For headless setups, CI, SSH, or clients that can only paste a key, use the API-key setup instead.
Point the client at the server URL below. A spec-compliant client discovers OAuth itself: it reads the WWW-Authenticate: Bearer resource_metadata="…" challenge on the first 401, fetches /.well-known/oauth-protected-resource, runs the PKCE code flow against the advertised authorization server, and retries with the token. No key to copy. Your client opens Unitpost in the browser — pick a workspace, approve the scopes, and you're connected. Tokens refresh automatically.
Server URL
https://mcp.unitpost.com/mcp
Transport
Streamable HTTP
Auth (recommended)
OAuth 2.1 — add the URL, approve in the browser
Auth (legacy)
Authorization: Bearer pk_live_YOUR_KEY
Tools
one per API operation (email_send, email_campaigns_send, …)
2
Discover tools
The server advertises one tool per API operation (email_send, email_campaigns_send, email_domains_verify, …). A write's body maps to the API request body. Every tool declares securitySchemes: [{ type: "oauth2", scopes: […] }] so linking-aware clients (ChatGPT) show the Connect UI.
3
Verify
Call a read tool (e.g. list domains). An unauthenticated call answers 401 with a WWW-Authenticate: Bearer resource_metadata="…" challenge (start OAuth there). With a key, a 401 Invalid API key usually means a missing Bearer prefix or a mistyped key — recreate the key and retry.
Don't see it? Fully quit and reopen the client — closing the window is often not enough — then check again.
No browser on this machine? For headless setups, CI, SSH, or clients that can only paste a key, use the API-key setup instead.
Every MCP call uses the same pipeline as the REST API (scopes, rate limits, suppression, tracking, webhooks). Full tool list: /api/v1/openapi.json.
OAuth needs a browser. Where there isn't one — CI jobs, SSH sessions, containers, Codex CLI, or any client that can only paste a static header — send a workspace API key as the Bearer token instead. Same scopes, same gates, same tools.
Open https://www.unitpost.com → Settings → API keys → create a scoped key. Copy the full pk_live_… value (shown once). In every snippet below, replace pk_live_YOUR_KEY with that value.
Shown once. Keep the Bearer prefix in every snippet. Grant only the scopes the agent needs (e.g. emails:send for email_send). Prefer a revocable key dedicated to the agent.
2
Cursor (static key)
Edit `~/.cursor/mcp.json` (all projects) or `.cursor/mcp.json` (this project only) — or Cursor Settings → MCP → Add new MCP server. Put the unitpost entry under top-level mcpServers.
For plans without custom connectors. In Claude Desktop: Settings → Desktop app → Developer → Edit Config, then merge the block below — mcpServers must be a top-level key (sibling of preferences), not nested inside it.
Deliberately no space after Authorization: in args — Claude Desktop mangles spaces there; the value lives in env instead. Requires Node.js 18+. If npx is not found, set "command" to your absolute npx path (e.g. /opt/homebrew/opt/node@22/bin/npx). Save, then ⌘Q (Quit) Claude Desktop — closing the window is not enough.
5
Codex CLI (TOML)
The CLI reads static config only. Edit `~/.codex/config.toml` (create it if needed).
Call a read tool (e.g. list domains). A 401 Invalid API key usually means a missing Bearer prefix or a mistyped key — recreate the key and retry. Then send a test email from a verified domain and confirm it in Unitpost Activity.
Don't see it? Fully quit and reopen the client — closing the window is often not enough — then check again.
Send over SMTP
Point any SMTP client at Unitpost and send from your verified domain. The connection details are the same everywhere — pick your platform below for a step-by-step setup, or open the SMTP guides index.
Nodemailer is not deprecated. Create a transport with createTransport, point it at smtp.unitpost.com on port 587 (STARTTLS) or 465 (implicit TLS), and send from a verified domain. Port 25 is blocked on most clouds — don't use it. The same transport works in Express, Next.js, and NestJS.
Add it to your Node.js project (Node 18+). The package is actively maintained.
Shell
npm install nodemailer
2
Create a transport
Point createTransport at smtp.unitpost.com. Use port 587 with secure: false for STARTTLS, or port 465 with secure: true for implicit TLS. The username is always unitpost; the password is your API key with emails:send.
Port 25 vs 587: 25 is the legacy submission port and is blocked by AWS, GCP, and most VPS providers. 587 with STARTTLS is what you want. 465 is implicit TLS if 587 is filtered.
3
Send an email
The from address must be on a domain you've verified in Unitpost. This is the same sendMail call in a Next.js Route Handler or a NestJS provider.
JavaScript
await transporter.sendMail({
from: "you@yourdomain.com",
to: "customer@example.com",
subject: "Hello from Unitpost",
html: "<h1>Welcome!</h1><p>Sent via SMTP.</p>",
});
4
Verify it in the dashboard
Open Activity in your Unitpost dashboard — the message appears alongside API sends, with delivery, open, and click status. Framework-specific walkthroughs: Next.js, NestJS, Express.
Use a from address on a verified domain in your mailer.
Ruby
class UserMailer < ApplicationMailer
default from: "you@yourdomain.com"
def welcome_email
mail(to: "customer@example.com", subject: "Welcome!")
end
end
3
Deliver and verify
Trigger the mailer (UserMailer.welcome_email.deliver_now) and confirm the send in your Unitpost Activity view.
Use send_mail or an EmailMessage. The from address must be on a verified domain.
Python
from django.core.mail import send_mail
send_mail(
subject="Hello from Unitpost",
message="Sent via SMTP.",
from_email="you@yourdomain.com",
recipient_list=["customer@example.com"],
html_message="<h1>Welcome!</h1>",
)
3
Verify delivery
Check your Unitpost Activity view for the message and its status.
using MailKit.Net.Smtp;
using MailKit.Security;
using MimeKit;
var message = new MimeMessage();
message.From.Add(MailboxAddress.Parse("you@yourdomain.com"));
message.To.Add(MailboxAddress.Parse("customer@example.com"));
message.Subject = "Hello from .NET";
message.Body = new TextPart("html") { Text = "<p>Sent via SMTP.</p>" };
using var client = new SmtpClient();
await client.ConnectAsync("smtp.unitpost.com", 587, SecureSocketOptions.StartTls);
await client.AuthenticateAsync("unitpost", Environment.GetEnvironmentVariable("UNITPOST_API_KEY"));
await client.SendAsync(message);
await client.DisconnectAsync(true);