Guides

Learn Unitpost

A page-by-page tour of the dashboard, plus integration walkthroughs for SMTP and AI agents.

Getting started

What every part of the dashboard does, and how contacts, segments, and campaigns connect.

How it all fits together

The contacts → segments → campaigns flow and the rules that keep it safe.

Open this guide on its own page

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.

The core flow

Three resources form a pipeline, each building on the one before it: Contacts → Segments → Campaigns.

  1. 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.
  2. 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.
  3. 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.

Supporting pieces

  • Domains establish sending identity (DKIM/SPF/DMARC). Nothing sends from an address on an unverified domain.
  • Templates hold reusable content; the Library stores the images and assets templates reference.
  • Emails and Activity are the read side — every individual message and every account event, for debugging and auditing.
  • API Keys and Webhooks are the developer surface: keys authenticate the API, webhooks push event notifications back to you.

Archive, don't delete

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.

Dashboard

Your account at a glance — sending health, recent activity, and what needs attention.

Open this guide on its own page

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.

Domains

Verify sending domains so your mail authenticates and lands in the inbox.

Open this guide on its own page

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.

Inbound routing

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

Author reusable email content for campaigns and the API.

Open this guide on its own page

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.

Library

Store and manage images and assets your templates reference.

Open this guide on its own page

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

The people you can email — subscription state, custom fields, and imports.

Open this guide on its own page

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.

Importing

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.

Deleting a contact

Deleting a contact removes them from every segment. If segments are targeted by an active campaign, the confirmation shows the impact.

Contacts are the first link in the core flow: group them into Segments, then send to a segment from a Campaign.

Suppressions

Addresses blocked from receiving mail — protecting your sending reputation.

Open this guide on its own page

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.

Automatic suppression

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.

Adding your own

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.

Platform-wide blocks

Some blocks are read-only

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.

Removing an address

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.

Segments

Named groups of contacts that campaigns send to.

Open this guide on its own page

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.

Archiving & deletion

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.

Subscription topics

Let contacts opt out of one kind of mail without leaving your list.

Open this guide on its own page

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.

Opt-in vs. opt-out

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.

Scoping a campaign

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.

Managing topics & preferences

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.

Campaigns

Send a template to a segment — now or on a schedule — with server-enforced safety gates.

Open this guide on its own page

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.

Send-readiness gates

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.

Lifecycle & archiving

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.

Emails

Every individual message sent — delivery status, opens, clicks, and bounces.

Open this guide on its own page

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

A chronological audit log of account events across every resource.

Open this guide on its own page

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 the API with scoped, least-privilege keys.

Open this guide on its own page

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

Receive signed, real-time event notifications at your own endpoint.

Open this guide on its own page

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.

Migration Assistant

Move from Resend, SendGrid, Mailgun, Postmark, Brevo, or Loops with one read-only API key.

Open this guide on its own page

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.

How it works

  1. 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.
  2. 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.
  3. 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.

What key do I need?

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:

  • Resend — any API key (resend.com/api-keys); read access is enough.
  • 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.
  • Loops — any API key (Settings → API).

Safety model

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

What to expect

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

SMS Messages

Send transactional and marketing SMS, and follow each message's delivery live.

Open this guide on its own page

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 Senders

The phone numbers and identities your SMS messages originate from.

Open this guide on its own page

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.

SMS Dashboard

Your SMS sending at a glance — volume and delivery over the last 30 days.

Open this guide on its own page

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

The workspace activity stream, scoped to the SMS channel.

Open this guide on its own page

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

Reusable plain-text messages with {{variables}}, published before use.

Open this guide on its own page

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

Per-category subscription preferences, tracked separately from email.

Open this guide on its own page

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.

SMS Campaigns

Bulk SMS to a segment — consent-gated, quiet-hours aware, paced per sender.

Open this guide on its own page

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.

Connection settings

Hostsmtp.unitpost.com
Usernameunitpostalways this literal value
PasswordYour Unitpost API keymust carry emails:send
Port (STARTTLS)587or 2587 if blocked
Port (implicit TLS)465or 2465 if blocked
From addressAny address on a verified domain

The username is always unitpost; the password is a Unitpost API key that carries emails:send. TLS is mandatory.

Nodemailer

Configure a Nodemailer transport pointed at Unitpost and send your first message in a few lines.

Open the Nodemailer guide

  1. Install Nodemailer

    Add it to your Node.js project (Node 14+).

    Shell
    npm install nodemailer
  2. Create a transport

    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.

    JavaScript
    import nodemailer from "nodemailer";
    
    const transporter = nodemailer.createTransport({
      host: "smtp.unitpost.com",
      port: 587,
      secure: false, // STARTTLS
      auth: {
        user: "unitpost",
        pass: process.env.UNITPOST_API_KEY,
      },
    });
  3. Send an email

    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.

Supabase Auth

Point Supabase's custom SMTP at Unitpost so auth emails send from your own verified domain — and skip Supabase's built-in auth-email rate limits.

Open the Supabase Auth guide

  1. Open Supabase SMTP settings

    In the Supabase dashboard go to Project Settings → Authentication → SMTP Settings and enable Custom SMTP.

  2. Enter your Unitpost credentials

    Hostsmtp.unitpost.com
    Port587
    Usernameunitpost
    PasswordYour Unitpost API key
    Sender emailauth@yourdomain.com (verified)
    Sender nameYour app name

    The sender email must be on a domain you've verified in Unitpost, or sends are rejected with a 550.

  3. Save and send a test

    Save the settings, then trigger a magic link or signup confirmation. Supabase renders its own auth templates; Unitpost just delivers them.

  4. Confirm delivery

    The auth email shows up in your Unitpost Activity view with full tracking.

Because delivery now runs through Unitpost, your plan's sending limits apply instead of Supabase's default auth-email throttle.

WordPress

WordPress sends mail via PHP's `mail()` by default, which most hosts deliver poorly. Route it through Unitpost SMTP with a plugin like WP Mail SMTP.

Open the WordPress guide

  1. Install an SMTP plugin

    From Plugins → Add New, install and activate WP Mail SMTP (or a comparable SMTP plugin).

  2. Choose the "Other SMTP" mailer

    In the plugin settings, pick the generic Other SMTP option (not a named provider).

  3. Enter your Unitpost SMTP settings

    SMTP Hostsmtp.unitpost.com
    EncryptionTLS (STARTTLS)
    SMTP Port587
    AuthenticationOn
    SMTP Usernameunitpost
    SMTP PasswordYour Unitpost API key
    From Emailyou@yourdomain.com (verified)

    Store the API key in the plugin's password field. Set the From Email to an address on your verified domain.

  4. Send a test email

    Use the plugin's Email Test tab to send yourself a message, then confirm it in your Unitpost Activity view.

Auth0

Configure a custom SMTP email provider in Auth0 so your tenant's emails deliver from your verified domain through Unitpost.

Open the Auth0 guide

  1. Open the email provider settings

    In the Auth0 dashboard go to Branding → Email Provider (or Emails → Provider on older tenants) and choose SMTP.

  2. Enter your Unitpost credentials

    Fromyou@yourdomain.com (verified)
    Hostsmtp.unitpost.com
    Port587
    Usernameunitpost
    PasswordYour Unitpost API key
  3. Save and send a test

    Save the provider, then use Auth0's Send Test Email button to verify the connection.

  4. Enable the emails you want

    Under Branding → Email Templates, toggle on verification, welcome, password-reset, and other templates. Auth0 renders them; Unitpost delivers.

Ruby on Rails

Set Action Mailer's SMTP settings to route your Rails app's mail through Unitpost.

Open the Ruby on Rails guide

  1. Configure Action Mailer

    In your environment config (e.g. config/environments/production.rb), set the delivery method and SMTP settings.

    Ruby
    config.action_mailer.delivery_method = :smtp
    config.action_mailer.smtp_settings = {
      address:              "smtp.unitpost.com",
      port:                 587,
      user_name:            "unitpost",
      password:             ENV["UNITPOST_API_KEY"],
      authentication:       :plain,
      enable_starttls_auto: true,
    }
  2. Set your sender

    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.

Laravel

Laravel reads SMTP settings from your `.env`. Point the mailer at Unitpost and you're done.

Open the Laravel guide

  1. Set your mail environment variables

    Add the following to your .env file.

    Shell
    MAIL_MAILER=smtp
    MAIL_HOST=smtp.unitpost.com
    MAIL_PORT=587
    MAIL_USERNAME=unitpost
    MAIL_PASSWORD=${UNITPOST_API_KEY}
    MAIL_ENCRYPTION=tls
    MAIL_FROM_ADDRESS=you@yourdomain.com
    MAIL_FROM_NAME="${APP_NAME}"

    MAIL_FROM_ADDRESS must be on a domain you've verified in Unitpost.

  2. Clear the config cache

    If you cache config in production, refresh it so the new values load.

    Shell
    php artisan config:clear
  3. Send and verify

    Send a mailable (e.g. Mail::to($user)->send(new WelcomeMail())) and confirm it in your Unitpost Activity view.

Django

Configure Django's built-in SMTP backend to send through Unitpost.

Open the Django guide

  1. Configure your settings

    Add the SMTP settings to settings.py. Keep the API key in an environment variable.

    Python
    EMAIL_BACKEND = "django.core.mail.backends.smtp.EmailBackend"
    EMAIL_HOST = "smtp.unitpost.com"
    EMAIL_PORT = 587
    EMAIL_USE_TLS = True
    EMAIL_HOST_USER = "unitpost"
    EMAIL_HOST_PASSWORD = os.environ["UNITPOST_API_KEY"]
    DEFAULT_FROM_EMAIL = "you@yourdomain.com"
  2. Send an email

    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.

PHPMailer

Use PHPMailer's SMTP mode to deliver through Unitpost from plain PHP.

Open the PHPMailer guide

  1. Install PHPMailer

    Add it via Composer.

    Shell
    composer require phpmailer/phpmailer
  2. Configure and send

    Set PHPMailer to SMTP mode with your Unitpost credentials.

    php
    <?php
    use PHPMailer\PHPMailer\PHPMailer;
    
    $mail = new PHPMailer(true);
    $mail->isSMTP();
    $mail->Host       = "smtp.unitpost.com";
    $mail->SMTPAuth   = true;
    $mail->Username   = "unitpost";
    $mail->Password   = getenv("UNITPOST_API_KEY");
    $mail->SMTPSecure = PHPMailer::ENCRYPTION_STARTTLS;
    $mail->Port       = 587;
    
    $mail->setFrom("you@yourdomain.com", "Your App");
    $mail->addAddress("customer@example.com");
    $mail->isHTML(true);
    $mail->Subject = "Hello from Unitpost";
    $mail->Body    = "<h1>Welcome!</h1><p>Sent via SMTP.</p>";
    $mail->send();
  3. Verify delivery

    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.
Learn more about UnitAI

Agent Skills

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.

Connection settings

Server URLhttps://mcp.unitpost.com/mcp
TransportStreamable HTTP
Auth headerAuthorization: Bearer pk_live_YOUR_KEYproduction key from www.unitpost.com
Toolsone 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.

Cursor

Paste one JSON block into Cursor's MCP config, reload, and the agent gets Unitpost tools.

Open the Cursor guide

  1. Create a production API key

    Open https://www.unitpost.comSettings → 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.

    JSON
    {
      "mcpServers": {
        "unitpost": {
          "url": "https://mcp.unitpost.com/mcp",
          "headers": {
            "Authorization": "Bearer pk_live_YOUR_KEY"
          }
        }
      }
    }

    Grant only the scopes the agent needs (e.g. emails:send for email_send). Prefer a revocable key dedicated to the agent.

  4. Reload and verify

    Reload MCP servers in Cursor. unitpost should show green with tools like email_send, email_campaigns_send, email_domains_verify.

  5. Send a test

    Ask the agent to send a test email from a verified domain, then confirm it in Unitpost Activity.

Claude Code

Run one CLI command (replace the key), then verify with `claude mcp list`.

Open the Claude Code guide

  1. Create a production API key

    Open https://www.unitpost.comSettings → 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.

    Shell
    claude mcp add --transport http --header "Authorization: Bearer pk_live_YOUR_KEY" unitpost https://mcp.unitpost.com/mcp

    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.

Claude Desktop

Claude Desktop's **Add custom connector** dialog (URL + OAuth only) cannot send a Bearer API key. Use the local mcp-remote bridge instead.

Open the Claude Desktop guide

  1. Create a production API key

    Open https://www.unitpost.comSettings → 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.

    JSON
    {
      "mcpServers": {
        "unitpost": {
          "command": "npx",
          "args": [
            "-y",
            "mcp-remote",
            "https://mcp.unitpost.com/mcp",
            "--header",
            "Authorization:${AUTH_HEADER}"
          ],
          "env": {
            "AUTH_HEADER": "Bearer pk_live_YOUR_KEY"
          }
        }
      }
    }

    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 URLhttps://mcp.unitpost.com/mcp
    Request header valueBearer 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.

Codex

Paste a short TOML block into `~/.codex/config.toml`, restart Codex, done.

Open the Codex guide

  1. Create a production API key

    Open https://www.unitpost.comSettings → 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.

    TOML
    [mcp_servers.unitpost]
    url = "https://mcp.unitpost.com/mcp"
    
    [mcp_servers.unitpost.http_headers]
    Authorization = "Bearer pk_live_YOUR_KEY"

    Grant only the scopes the agent needs (e.g. emails:send for email_send). Prefer a revocable key dedicated to the agent.

  4. Restart and verify

    Restart Codex so it reloads the config. unitpost tools should be available to the agent.

  5. Send a test

    Ask Codex to send an email from a verified domain and confirm it in Unitpost Activity.

GitHub Copilot

Paste into `.vscode/mcp.json`. VS Code will prompt for the key at runtime (not stored in the file).

Open the GitHub Copilot guide

  1. Create a production API key

    Open https://www.unitpost.comSettings → 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).

    JSON
    {
      "inputs": [
        {
          "id": "unitpost-key",
          "type": "promptString",
          "description": "Unitpost API key (pk_live_…)",
          "password": true
        }
      ],
      "servers": {
        "unitpost": {
          "type": "http",
          "url": "https://mcp.unitpost.com/mcp",
          "headers": {
            "Authorization": "Bearer ${input:unitpost-key}"
          }
        }
      }
    }

    Grant only the scopes the agent needs (e.g. emails:send for email_send). Prefer a revocable key dedicated to the agent.

  4. Start the server

    Open the MCP view, start unitpost, enter your production key when prompted, and confirm tools are listed.

  5. Send a test

    In Copilot agent mode, ask it to send a test email from a verified domain and confirm in Unitpost Activity.

Gemini CLI

Paste one JSON block into Gemini settings, restart, run `/mcp`.

Open the Gemini CLI guide

  1. Create a production API key

    Open https://www.unitpost.comSettings → 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 .

    JSON
    {
      "mcpServers": {
        "unitpost": {
          "httpUrl": "https://mcp.unitpost.com/mcp",
          "headers": {
            "Authorization": "Bearer pk_live_YOUR_KEY"
          }
        }
      }
    }

    Grant only the scopes the agent needs (e.g. emails:send for email_send). Prefer a revocable key dedicated to the agent.

  4. Verify

    Restart the Gemini CLI and run /mcp. unitpost should list its tools.

  5. Send a test

    Ask Gemini to send an email from a verified domain and confirm it in Unitpost Activity.

Windsurf

Paste one JSON block into Windsurf's MCP config and refresh.

Open the Windsurf guide

  1. Create a production API key

    Open https://www.unitpost.comSettings → 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`.

  3. Paste this server block

    Replace pk_live_YOUR_KEY — keep Bearer .

    JSON
    {
      "mcpServers": {
        "unitpost": {
          "serverUrl": "https://mcp.unitpost.com/mcp",
          "headers": {
            "Authorization": "Bearer pk_live_YOUR_KEY"
          }
        }
      }
    }

    Grant only the scopes the agent needs (e.g. emails:send for email_send). Prefer a revocable key dedicated to the agent.

  4. Refresh and verify

    Refresh the MCP server list. Unitpost's tools should appear under unitpost.

  5. Send a test

    Ask Cascade to send a test email from a verified domain, then confirm in Unitpost Activity.

Generic / custom client

Any streamable-HTTP MCP client can connect with a Bearer production API key.

Open the Generic / custom client guide

  1. Create a production API key

    Open https://www.unitpost.comSettings → 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 URLhttps://mcp.unitpost.com/mcp
    TransportStreamable HTTP
    Auth headerAuthorization: Bearer pk_live_YOUR_KEY
    Toolsone 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.