---
title: "Send your first campaign"
description: "Connect an agent, add a domain, import a list, write, test, schedule and watch a real send."
sidebar:
  order: 1
---

In this tutorial, we send a product update to a list of people who signed up for it. We connect an agent, add a
sending domain, import the list, write the email, send a test, schedule the send, and watch the results come in.

It takes about 30 minutes with a real domain, most of it waiting for DNS. To try every step first with no domain
and no real mail, create a sandbox workspace with `pneumail workspaces create --sandbox`. A sandbox approves sends
automatically and sends everything to the Amazon SES mailbox simulator.

You need three things:

- A Pneumail workspace. Sign in at `app.pneumail.com` with your work email.
- A domain where you can add DNS records. We use `news.example.com`.
- A CSV of people who opted in, with `email` and `consented_at` columns. We use `residents.csv`.

## 1. Connect your agent

Paste this into Claude Code, Codex, Cursor, or any agent with a terminal:

```
Set up Pneumail for me. Follow https://pneumail.com/install
```

The agent installs the CLI and starts a login. You approve it in your browser, or on your phone with the code the
agent shows you. Then the agent adds the MCP server to its config and runs `pneumail whoami`:

```
$ pneumail whoami
workspace  Front Door
you        david@backpackops.com (admin)
key        cli-macbook (created just now)
```

The key can draft, import, and schedule. It cannot approve a send, and it cannot remove anyone from a suppression
list. Every command takes `--json` and returns a stable error `code` with a `next` step, so an agent can act on the
result without parsing text.

## 2. Add a sending domain

```
$ pneumail domains add news.example.com
```

Pneumail prints the DNS records to add:

```
Add these records at your DNS provider:

  CNAME  abc123._domainkey.news.example.com   abc123.dkim.amazonses.com
  CNAME  def456._domainkey.news.example.com   def456.dkim.amazonses.com
  CNAME  ghi789._domainkey.news.example.com   ghi789.dkim.amazonses.com
  MX     bounce.news.example.com              10 feedback-smtp.us-east-2.amazonses.com
  TXT    bounce.news.example.com              "v=spf1 include:amazonses.com ~all"
  TXT    news.example.com                     "google-site-verification=Xy7..."   (Google Postmaster Tools)

DMARC  example.com already publishes p=reject. news.example.com inherits it. No record needed.
```

If your DNS is on Cloudflare, add `--apply-dns` and Pneumail adds the records with your Cloudflare token.

Now wait for the records to work. DKIM usually verifies within an hour and can take up to 72 hours:

```
$ pneumail domains verify news.example.com --wait
DKIM    pass
SPF     pass, aligned through bounce.news.example.com
DMARC   pass (inherited p=reject from example.com)
ready   news.example.com can send. Warmup allows 2,000 emails today.
```

A new domain starts at 2,000 emails a day. The limit doubles after each day the domain sends with few bounces.

## 3. Create a publication

A publication is one stream of mail with its own sender, its own subscribers, and its own unsubscribe list. We
create one for product updates:

```
$ pneumail publications create "Front Door updates" \
    --from "Front Door <updates@news.example.com>" \
    --reply-to david@backpackops.com \
    --address "1234 Example St, Seattle, WA 98101"
created  front-door-updates
```

CAN-SPAM requires a postal address in commercial email, so it goes in every footer.

## 4. Import your contacts

```
$ pneumail contacts import residents.csv \
    --publication front-door-updates \
    --consent form --consent-url https://localcarehub.org/updates
```

Pneumail checks every address before it adds anything:

```
rows          4,812
added         4,630
duplicates    0
to review     141   (112 likely typos like gmial.com, 29 role addresses like info@)
rejected      41    (disposable domains)
suppressed    0
```

Each contact keeps its consent record: how the person agreed, the page, the time from `consented_at`, and a
fingerprint of the file. A row without `consented_at` waits for review. Pneumail never changes an address on its
own. Open the review link to accept or reject each suggested fix.

## 5. Write the email

Ask your agent to draft it, or open the composer:

```
$ pneumail campaigns create --publication front-door-updates --from-markdown update.md \
    --subject "You can now renew SNAP from your phone" \
    --preview "Two minutes, no office visit."
created  cmp_7Hk2  https://app.pneumail.com/c/cmp_7Hk2
```

In the composer, type `/` to add a block: image, button, columns, divider, or quote. The right side shows the email
on a phone, filled in for a sample contact. The size gauge under it reads `38 KB of 102 KB`. Gmail cuts off emails
over 102 KB.

## 6. Send yourself a test

```
$ pneumail campaigns test cmp_7Hk2 --to david@backpackops.com
sent  1 test email
```

Tests go only to workspace members and your seed list. Open the test on your phone. Check that the images load,
the button works, and the unsubscribe link opens a one-click page.

## 7. Schedule the send

```
$ pneumail campaigns schedule cmp_7Hk2 --at "2026-11-03 09:00 America/Los_Angeles"
checks    subject, preview, unsubscribe, postal address, size, links: pass
audience  everyone in front-door-updates, 4,630 today
status    waiting for approval  https://app.pneumail.com/c/cmp_7Hk2/approve
```

Pneumail emails every admin that an approval is waiting. An admin opens the link, sees the email, the audience and
where those contacts came from, and clicks **Approve**. The agent waits for it:

```
$ pneumail campaigns wait cmp_7Hk2 --until scheduled
scheduled  2026-11-03 09:00 PST
```

The audience is counted again when the send starts. If it has grown or shrunk by more than 10%, the send waits for
a new approval.

## 8. Watch the results

```
$ pneumail campaigns stats cmp_7Hk2 --watch
first batch   463 random new contacts, held 30 minutes: 0.4% bounced, sending the rest
day 1         2,000 sent (warmup limit)
day 2         2,630 sent, done
sent          4,630   delivered 4,588   bounced 21   pending 21   unsubscribed 6   complained 1
clicked       612 people   (88 bot clicks removed)
opened        838 people   (1,102 automatic opens from Apple Mail and scanners not counted)
gmail         not enough Gmail volume yet for Google Postmaster to report
```

Contacts this workspace has never emailed get a test first: a random 10% goes out, and the rest waits 30 minutes.
The warmup limit spreads this send over two days. If bounces pass 2% or complaints pass 0.1%, the send pauses, and
every admin gets an email saying which limit it hit. Nothing more goes out until an admin resumes it.

You sent your first campaign.

## Next steps

The full docs are at docs.pneumail.com. Agents can read them through `docs.pneumail.com/llms.txt`.

- To add people from your own signup form with double opt-in, read "How to collect signups". Done when a form
  post creates a pending contact, the confirmation email arrives, and the click adds the contact with a
  `double_opt_in` consent record.
- To send a welcome sequence to everyone who signs up, read "How to send a welcome sequence". Done when a new
  contact receives three emails on the schedule and the journey view shows each step.
- To turn each changelog post into a draft campaign, read "How to draft campaigns from a changelog". Done when
  publishing a post produces a draft with the post's title, body and link.
- To learn what Pneumail does to keep your mail out of spam folders, read "About deliverability".
- To look up a command, an endpoint, or an MCP tool, use the reference section.
