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.
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.
Point the transport 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.
The from address must be on a domain you've verified in Unitpost.
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.
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.
The message appears in your Unitpost Activity view with full tracking.
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.
Authorization: Bearer pk_live_YOUR_KEYproduction key from www.unitpost.com
Tools
one per API operation (email_send, email_campaigns_send, …)
Create the key on www.unitpost.com → Settings → API keys (not unitpost.dev — a .dev key returns 401 Invalid API key against this endpoint). 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.
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.
Must be production. Keys from unitpost.dev only work on the dev API. This MCP URL is production (mcp.unitpost.com) — a .dev key returns 401 Invalid API key on tool calls even if the server appears connected.
2
Open the MCP config file
Edit `~/.cursor/mcp.json` (all projects) or `.cursor/mcp.json` (this project only). Or: Cursor Settings → MCP → Add new MCP server.
3
Paste this server block
Put the unitpost entry under top-level mcpServers. Replace pk_live_YOUR_KEY with your key — keep the Bearer prefix and the space.
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.
Must be production. Keys from unitpost.dev only work on the dev API. This MCP URL is production (mcp.unitpost.com) — a .dev key returns 401 Invalid API key on tool calls even if the server appears connected.
2
Run this command
Paste as one line. Replace pk_live_YOUR_KEY with your key. Keep Bearer inside the quotes. Flags stay before the name/URL.
Use -s user to install for every project. Grant only the scopes the agent needs (e.g. emails:send for email_send). Prefer a revocable key dedicated to the agent.
3
Verify
Run claude mcp list (or /mcp in a session). unitpost should be connected with its tools listed.
4
Send a test
Ask Claude to send an email from a verified domain and confirm it in Unitpost Activity.
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.
Must be production. Keys from unitpost.dev only work on the dev API. This MCP URL is production (mcp.unitpost.com) — a .dev key returns 401 Invalid API key on tool calls even if the server appears connected.
2
Open Edit Config
In Claude Desktop: Settings → Desktop app → Developer → Edit Config. That opens claude_desktop_config.json.
3
Paste mcpServers at the top level
Merge the block below into the file. mcpServers must be a top-level key (sibling of preferences), not nested inside preferences. Replace pk_live_YOUR_KEY in AUTH_HEADER — keep Bearer and the space.
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). Grant only the scopes the agent needs (e.g. emails:send for email_send). Prefer a revocable key dedicated to the agent.
4
Fully quit and reopen
Save the file, then ⌘Q (Quit) Claude Desktop — closing the window is not enough. Reopen and check Settings → Developer: unitpost should appear under Local MCP servers.
5
Send a test
Ask Claude to send an email from a verified domain and confirm it in Unitpost Activity.
6
Skip: Add custom connector (unless you see Request headers)
Most plans only show Name + Remote MCP server URL + OAuth Client ID/Secret. That cannot authenticate Unitpost. Only if your dialog has Request headers: set URL to the value below, add header Authorization = Bearer pk_live_… (include Bearer ). Never put the API key in OAuth Client ID/Secret.
Remote MCP server URL
https://mcp.unitpost.com/mcp
Request header value
Bearer pk_live_YOUR_KEY
mcp-remote is local to Claude Desktop. Custom connectors (when header auth exists) are cloud-brokered and also work on claude.ai / mobile.
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.
Must be production. Keys from unitpost.dev only work on the dev API. This MCP URL is production (mcp.unitpost.com) — a .dev key returns 401 Invalid API key on tool calls even if the server appears connected.
2
Open the Codex config
Edit `~/.codex/config.toml` (create the file if it does not exist).
3
Paste this TOML
Replace pk_live_YOUR_KEY — keep Bearer inside the quotes.
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.
Must be production. Keys from unitpost.dev only work on the dev API. This MCP URL is production (mcp.unitpost.com) — a .dev key returns 401 Invalid API key on tool calls even if the server appears connected.
2
Open the MCP config
Create or edit `.vscode/mcp.json` in your workspace (or MCP: Add Server from the Command Palette for a user-level entry).
3
Paste this config
When VS Code prompts for the key later, paste only the pk_live_… value — not Bearer (the config already adds it).
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.
Must be production. Keys from unitpost.dev only work on the dev API. This MCP URL is production (mcp.unitpost.com) — a .dev key returns 401 Invalid API key on tool calls even if the server appears connected.
2
Open Gemini settings
Edit `~/.gemini/settings.json` (user-level) or `.gemini/settings.json` (project).
3
Paste this server block
Under mcpServers, add unitpost. Replace pk_live_YOUR_KEY — keep Bearer .
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.
Must be production. Keys from unitpost.dev only work on the dev API. This MCP URL is production (mcp.unitpost.com) — a .dev key returns 401 Invalid API key on tool calls even if the server appears connected.
2
Open MCP settings
In Windsurf: Settings → Cascade → MCP Servers → Add Server, or edit `~/.codeium/windsurf/mcp_config.json`.
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.
Must be production. Keys from unitpost.dev only work on the dev API. This MCP URL is production (mcp.unitpost.com) — a .dev key returns 401 Invalid API key on tool calls even if the server appears connected.
2
Point the client at Unitpost
Configure a streamable-HTTP MCP server with these values:
Server URL
https://mcp.unitpost.com/mcp
Transport
Streamable HTTP
Auth header
Authorization: Bearer pk_live_YOUR_KEY
Tools
one per API operation (email_send, email_campaigns_send, …)
3
Send the Authorization header
On every MCP request, send Authorization: Bearer pk_live_YOUR_KEY (replace the placeholder; keep Bearer ).
Grant only the scopes the agent needs (e.g. emails:send for email_send). Prefer a revocable key dedicated to the agent.
4
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.
5
Verify
Call a read tool (e.g. list domains). A 401 Invalid API key usually means a .dev key or a missing Bearer prefix — recreate the key on www.unitpost.com and retry.
Every MCP call uses the same pipeline as the REST API (scopes, rate limits, suppression, tracking, webhooks). Full tool list: /api/v1/openapi.json.