---
title: "Campaign Readiness Check CLI: A Pre-Launch Gate Guide"
description: "Campaign readiness check CLI guide: chain FirstSales readiness, domain, list and credit checks into one gate before start."
date: "2026-10-07"
tags: "cli, campaign-readiness, pre-launch, checklist, deliverability"
readTime: "25 min read"
author: "FirstSales Team"
slug: "campaign-readiness-check-cli"
canonical: "https://firstsales.io/blog/campaign-readiness-check-cli/"
---

# Pre-launch checklist from the terminal

**TL;DR:** `firstsales campaigns readiness` is one check, and a launch needs several. Chain it with domain, list, credit and alert checks into one script that exits non-zero on any failure, then make `campaigns start` depend on that script passing.

---

## Table of contents

- [A checklist is only useful if it blocks the launch](#a-checklist-is-only-useful-if-it-blocks-the-launch)
- [What the evidence says about checklists](#what-the-evidence-says-about-checklists)
- [The commands behind the gate](#the-commands-behind-the-gate)
- [Set up a safe session](#set-up-a-safe-session)
- [Check 1: identity and workspace](#check-1-identity-and-workspace)
- [Check 2: campaigns readiness](#check-2-campaigns-readiness)
- [Check 3: domains and authentication](#check-3-domains-and-authentication)
- [Check 4: connectors and warm-up](#check-4-connectors-and-warm-up)
- [Check 5: the list and the block list](#check-5-the-list-and-the-block-list)
- [Check 6: the copy and the workflow](#check-6-the-copy-and-the-workflow)
- [Check 7: credits and alerts](#check-7-credits-and-alerts)
- [Check 8: the test email](#check-8-the-test-email)
- [Assemble the gate](#assemble-the-gate)
- [Connect the gate to start](#connect-the-gate-to-start)
- [What a checklist cannot catch](#what-a-checklist-cannot-catch)
- [Tune the gate for your team](#tune-the-gate-for-your-team)
- [Let Claude Code run the gate](#let-claude-code-run-the-gate)
- [Where MCP fits](#where-mcp-fits)
- [Common mistakes](#common-mistakes)
- [FAQs](#faqs)
- [Conclusion](#conclusion)

---

## A checklist is only useful if it blocks the launch

Most teams have a pre-launch checklist somewhere. Few have one that stops a launch when an item fails.

A checklist in a document depends on someone reading it at the right moment, and the right moment is usually the one with the most time pressure. A checklist in a script runs the same way every time, and it can refuse to continue.

This post makes one claim: build the checklist as a gate, with exit codes, and let `campaigns start` wait for it. A gate you can skip is a suggestion.

The FirstSales command line interface (CLI) gives you the pieces. It has a readiness command for the campaign, verification commands for domains and authentication, a block-list check, credit and alert reads and a test-email command.

Together they cover what goes wrong most often in a first send: broken authentication, a dirty list, copy errors and a mailbox that is not ready.

The cost of a miss is high. Google's [email sender guidelines](https://support.google.com/a/answer/81126) tell senders to keep the spam rate below 0.10% and to prevent it from ever reaching 0.30%, and one careless first send can push a young domain past those lines.

This post walks through each check, the command that performs it, what the command proves and what it cannot. IDs such as `org_123`, `ws_123` and `camp_123` are placeholders, and I have not run any of these commands against a real account.

## What the evidence says about checklists

Checklists have a record outside email. The best-known study came from surgery.

In 2009, Haynes and colleagues published a study in the *New England Journal of Medicine* of a 19-item surgical safety checklist introduced across eight hospitals worldwide. The inpatient death rate after major surgery fell from 1.5% to 0.8%, and complications fell from 11.0% to 7.0% ([NEJM, 2009](https://www.nejm.org/doi/full/10.1056/NEJMsa0810119)).

Sending cold email is not surgery, and no one should claim the same effect size for outbound. The lesson is about method: short, specific, written checks catch errors that experienced people still make under pressure.

Two features made the surgical checklist work. It was short enough to use, and it was tied to a moment, the pause before the operation, when someone had to say the items out loud.

Your gate copies both. It is a handful of checks, and it sits at a fixed moment, the second before `campaigns start`.

### Why experts skip checklists

The people most likely to skip a checklist are the people who know the most. They have done it a hundred times, and they trust their memory.

A script removes that choice. It does not care how experienced you are, and it does not get tired on a Friday afternoon.

### What a good checklist item looks like

Each item should be a question with a yes or no answer that a command can give. "Is the sending domain authenticated" is good. "Is the copy good" is not.

Some items need a human, such as reading the test email. Those items still belong on the list, but the script should state that a person must answer them.

## The commands behind the gate

The package is `@firstsales.io/cli` and the binary is `firstsales`. As of October 2026 the published version is 0.1.11.

The table maps each check to its command. All of them are reads except the test email, which sends one message.

| Check | Command | Changes state? |
| --- | --- | --- |
| Identity | `whoami`, `doctor` | No |
| Campaign readiness | `campaigns readiness` | No |
| Launch preparation | `campaigns prepare-launch` | No |
| Authentication | `email-auth verify` | No |
| Tracking domain | `tracking-domains verify` | No |
| Connector health | `connectors test` | No |
| Warm-up | `warmup status` | No |
| Block list | `blocked-domains list` | No |
| Suppression | `suppression check` | No |
| Copy and steps | `campaigns workflow get` | No |
| Audience | `campaigns leads`, `campaigns sources` | No |
| Credits | `billing credits` | No |
| Alerts | `alerts list` | No |
| Test email | `campaigns prepare-test-email`, `campaigns send-test-email` | Sends one email |

The README does not document `campaigns readiness`, `prepare-launch`, `prepare-test-email` or `send-test-email`. They appear in the CLI's own command list (`firstsales commands --json`), so I name them and do not describe their flags or response fields.

Run `firstsales commands --json` to see the registry for your installed version, and `firstsales campaigns readiness --help` for its flags. Use `--dry-run` to see the request a command would send.

## Set up a safe session

Install the package and check it.

```bash
npm i -g @firstsales.io/cli
firstsales --help
```

Put the key in the environment, never in a prompt.

```bash
export FIRSTSALES_API_KEY="<YOUR_FIRSTSALES_API_KEY>"
```

A gate script needs read access and, for the test email, the ability to send one message. Create a dedicated key with the smallest scopes that cover both.

```bash
firstsales api-keys create \
  --org org_123 \
  --data '{"name":"Launch gate","scopes":["<READ_AND_TEST_SCOPES>"],"accessLevel":"workspace"}' \
  --json
```

Take scope names from the OpenAPI contract at `developer.firstsales.io`. The raw key is shown once, so store it in a secret manager.

The key for the gate should not be able to start campaigns. Starting stays with a person and a separate credential.

For key design, read [API keys for outbound agents](/blog/api-keys-for-outbound-agents).

## Check 1: identity and workspace

A wrong workspace invalidates every other check. Start there.

```bash
firstsales whoami --json
firstsales doctor --pretty
```

`whoami` validates the key and prints the user, key, organization and workspace context without exposing the raw or hashed key. `doctor` checks your setup.

Print the workspace name back to the human and ask for a yes. It sounds small, and it prevents a launch in the wrong place.

### Why the gate starts here

Every later command takes `--org` and `--workspace`. If those values are wrong, every check passes against the wrong data.

Pin the values at the top of your script and print them. A script that prints `ORG=org_123 WS=ws_123` before anything else gives you a chance to catch a typo.

## Check 2: campaigns readiness

The central check is the readiness command.

```bash
firstsales campaigns readiness --org org_123 --workspace ws_123 --campaign camp_123 --json
```

Run this before `campaigns start`, every time. It is the first of the two commands that the safety rules require before a launch.

I cannot show you its output, because I have not seen a live response and the README does not document it. Read the whole response, note what it checks, and decide which parts your script will treat as blocking.

### Readiness and the start body

The start command takes a body with `savedVersionId`, `readinessVersion`, `idempotencyKey` and `confirmation`. The `readinessVersion` field suggests that a launch is tied to a specific readiness result.

I cannot confirm where each value comes from, so treat that link as an inference. Run `campaigns prepare-launch` and read its output to see which values it gives you.

```bash
firstsales campaigns prepare-launch --org org_123 --workspace ws_123 --campaign camp_123 --json
```

If the evidence in the start body must match a recent readiness result, then a gate that runs minutes before the start is more valuable than one that ran yesterday. Keep the gap short.

### Do not weaken the check

If readiness reports a problem you do not understand, stop. Do not edit the script to make it pass.

A readiness failure is a message from the system that something is wrong. Fix the cause, or ask someone who can.

## Check 3: domains and authentication

The sending domain must authenticate, and the tracking domain must verify.

```bash
firstsales email-auth verify --domain acme.com --dkim-selector s1 --org org_123 --workspace ws_123 --json
firstsales tracking-domains verify --org org_123 --workspace ws_123 --domain domain_123 --json
```

These are the README forms. Replace `acme.com`, `s1` and `domain_123` with your own values.

Google requires SPF, DKIM and DMARC for senders of more than 5,000 messages a day to Gmail, and Microsoft's [requirements for high-volume senders](https://techcommunity.microsoft.com/blog/microsoftdefenderforoffice365blog/strengthening-email-ecosystem-outlook%E2%80%99s-new-requirements-for-high%E2%80%90volume-senders/4399730) took effect on May 5, 2025.

Even if you send far less than that, the records protect your reputation. A domain that lacks them looks like a spoofed domain.

### The full domain walkthrough

The post on [setting up sending and tracking domains from the CLI](/blog/sending-domains-from-cli) covers each command in detail. For the record syntax itself, see [SPF, DKIM and DMARC setup for 2026](/blog/spf-dkim-dmarc-setup-2026).

### What a pass proves

A passing `email-auth verify` proves the records validate for that domain and selector now. It does not prove inbox placement.

For placement, use [an inbox placement seed list](/blog/inbox-placement-seed-list) and [Google Postmaster Tools](/blog/google-postmaster-tools-cold-email).

## Check 4: connectors and warm-up

A campaign sends through connected accounts. Those accounts must work and must be warm.

```bash
firstsales connectors list --org org_123 --workspace ws_123 --json
firstsales connectors test --org org_123 --workspace ws_123 --connector connector_123 --json
firstsales warmup status --org org_123 --workspace ws_123 --connector connector_123 --json
```

`connectors list` shows what is connected. `connectors test` checks that a connector works, and connector secrets are never returned.

`warmup status` takes a connector. Check `--help` for the exact flag name, because the README lists the command without a worked example.

### Warm-up gates volume

A new mailbox at full volume looks like a spammer. The checks above tell you whether a mailbox is ready for the load your campaign will place on it.

For caps, read [emails per inbox per day](/blog/emails-per-inbox-per-day) and [email sending limits](/blog/email-sending-limits). For why warming never ends, read [ongoing email warm-up](/blog/ongoing-email-warmup).

## Check 5: the list and the block list

A bad list does more damage than bad copy. Check it before anything else goes out.

### Read the audience

```bash
firstsales campaigns leads --org org_123 --workspace ws_123 --campaign camp_123 --json
firstsales campaigns sources --org org_123 --workspace ws_123 --campaign camp_123 --json
```

`campaigns leads` shows who is in the campaign and `campaigns sources` shows where they came from. Both are reads.

Look at the size, the sources and a sample. Does the list match the audience the copy was written for?

### Check the block list

```bash
firstsales blocked-domains list --org org_123 --workspace ws_123 --json
firstsales suppression check jane@acme.com acme.com --org org_123 --workspace ws_123
```

`suppression check` accepts up to 100 named addresses or domains and returns `value`, `suppressed` and `reason`. There is no list or export.

Run a sample through it, and run every opt-out you received this week. The workflow is explained in [blocked domains and do-not-contact lists through MCP](/blog/do-not-contact-list-mcp).

### Verify the addresses

Verification catches addresses that will bounce. The post on [email verification before sending](/blog/email-verification-before-sending) explains the process, and [catch-all email addresses](/blog/catch-all-email-addresses) covers the tricky cases.

High bounce rates damage reputation quickly. [Cold email bounce rate](/blog/cold-email-bounce-rate) lists thresholds worth knowing.

### The legal check

Before you send, confirm the list meets your obligations. The FTC's [CAN-SPAM compliance guide](https://www.ftc.gov/business-guidance/resources/can-spam-act-compliance-guide-business) says the law makes no exception for business-to-business email, requires a working opt-out honored within 10 business days, and sets penalties of up to $53,088 per violating email.

I am not giving legal advice. See [is cold email legal in 2026](/blog/is-cold-email-legal-2026) and consult your lawyer for your situation.

## Check 6: the copy and the workflow

The campaign workflow holds the steps and messages. Read it before you send it.

```bash
firstsales campaigns workflow get --org org_123 --workspace ws_123 --campaign camp_123 --json > workflow.json
```

Save the output. It is the exact content that will go out, and a saved copy lets you compare it later.

### Read it as a prospect

Read the first message as if you received it. Is the first line about the prospect or about you?

The [cold email skim test](/blog/cold-email-skim-test) gives a quick method. [Plain text versus HTML](/blog/plain-text-vs-html-cold-email) explains why simple formats usually deliver better.

### Check variables and spintax

A broken merge field is the most common copy error. Look for placeholders that did not resolve, and for fallback text that reads strangely.

If you use variations, read each one. The post on [spintax in cold email](/blog/spintax-cold-email-2026) explains the risks of too many variants.

### Check rendering

Look at the message in a dark-mode client too. [Dark mode email rendering](/blog/dark-mode-email-rendering) and [email preheader optimization](/blog/email-preheader-optimization) cover the details.

### Check the unsubscribe mechanism

Every message needs a clear way to opt out. Google asks bulk senders for one-click unsubscribe, specified in [RFC 8058](https://www.rfc-editor.org/rfc/rfc8058), and [one-click unsubscribe for cold email](/blog/one-click-unsubscribe-cold-email) explains how to meet it.

## Check 7: credits and alerts

A campaign that runs out of credits halfway through is a quiet failure. Check the balance before you start.

```bash
firstsales billing credits --org org_123 --json
```

The README lists `billing credits` as a read. Compare the balance with the campaign size, and leave a margin.

### Read open alerts

```bash
firstsales alerts list --org org_123 --workspace ws_123 --json
```

An open alert is a warning the system has already raised. Starting a campaign with an unresolved alert is like launching with a warning light on.

The `alerts` group also has `ack` and `resolve`. Acknowledge an alert when someone has seen it, and resolve it only when the cause is fixed.

### A read that gates a write

Notice the pattern. Every check so far is a read, and each one gates a write that comes later.

That is the shape of a safe automation. Reads are free, and writes wait for a human.

## Check 8: the test email

The last check sends one real message. Run `campaigns send-test-email` before `campaigns start`, always.

```bash
firstsales campaigns prepare-test-email --org org_123 --workspace ws_123 --campaign camp_123 --json
firstsales campaigns send-test-email --org org_123 --workspace ws_123 --campaign camp_123 --data-file test-email.json --json
```

I cannot describe the body, because the README does not document it. Use `--dry-run` and the OpenAPI contract.

Open the received message in a real inbox. Check the sender, the subject, the first line, the links and the unsubscribe mechanism.

The next post in this series, [sending a test email from Claude Code first](/blog/send-test-email-claude-code), covers this step in depth.

### Why the test is last

The test uses the same domain, connector and copy as the real send. If the earlier checks passed and the test looks right, you have decent evidence.

If the test fails, you have found an error at the cost of one message. That is the best price you will ever get.

### The human item

Reading the test email is a human item. The script can send it, and a person must look at it.

Make the script print a clear prompt, such as "Open the test email and confirm it looks right," and wait for a typed answer. That short pause is the surgical "time out" of your launch.

## The gate as a decision table

The table shows who answers each check, a script or a person. A check mark means a script can answer it alone, and a cross means a person must.

| Check | Script can answer | Person must answer |
| --- | --- | --- |
| Right org and workspace | ✗ | Confirms the printed values |
| Readiness | ✓ | Reads anything unclear |
| Authentication | ✓ | Fixes DNS when it fails |
| Connector and warm-up | ✓ | Decides on volume |
| Block list and suppression | ✓ | Verifies new opt-outs |
| Copy and variables | ✗ | Reads the workflow |
| Credits and alerts | ✓ | Decides on open alerts |
| Test email | ✗ | Reads it in an inbox |
| Start | ✗ | Approves the launch |

```mermaid
flowchart TD
    A[Confirm workspace] -- confirmed --> B[campaigns readiness]
    B -- pass --> C[Domain and connector checks]
    B -- fail --> X[Fix and rerun]
    C -- pass --> D[List, block list, credits, alerts]
    C -- fail --> X
    D -- pass --> E[send-test-email]
    D -- fail --> X
    E -- human reads it, looks right --> F[Human runs campaigns start]
    E -- problem found --> X
```

Every failing path loops back to a fix, and only one path reaches the start. That is the whole design.

## Assemble the gate

Now combine the checks. The script below uses only commands from the README and the CLI's command list, and it relies on exit codes and not on guessed JSON fields.

```bash
#!/usr/bin/env bash
set -u

ORG="org_123"; WS="ws_123"; CAMP="camp_123"
SENDING_DOMAIN="acme.com"; DKIM_SELECTOR="s1"
TRACKING_DOMAIN_ID="domain_123"; CONNECTOR="connector_123"

echo "Target: ORG=$ORG WS=$WS CAMP=$CAMP"
read -r -p "Is this the right workspace and campaign? (yes/no) " ok
[ "$ok" = "yes" ] || { echo "Stopped."; exit 1; }

fail=0
run() {
  echo "== $*"
  "$@"
  code=$?
  if [ "$code" -ne 0 ]; then
    echo "FAILED (exit $code): $*" >&2
    fail=1
  fi
}

run firstsales whoami --json
run firstsales campaigns readiness --org "$ORG" --workspace "$WS" --campaign "$CAMP" --json
run firstsales email-auth verify --domain "$SENDING_DOMAIN" --dkim-selector "$DKIM_SELECTOR" --org "$ORG" --workspace "$WS" --json
run firstsales tracking-domains verify --org "$ORG" --workspace "$WS" --domain "$TRACKING_DOMAIN_ID" --json
run firstsales connectors test --org "$ORG" --workspace "$WS" --connector "$CONNECTOR" --json
run firstsales warmup status --org "$ORG" --workspace "$WS" --connector "$CONNECTOR" --json
run firstsales blocked-domains list --org "$ORG" --workspace "$WS" --json
run firstsales campaigns workflow get --org "$ORG" --workspace "$WS" --campaign "$CAMP" --json
run firstsales billing credits --org "$ORG" --json
run firstsales alerts list --org "$ORG" --workspace "$WS" --json

if [ "$fail" -ne 0 ]; then
  echo "Gate FAILED. Do not start the campaign." >&2
  exit 1
fi

echo "Automated checks passed. Next: send the test email and read it yourself."
touch "gate-passed-$CAMP.txt"
exit 0
```

The script stops short of the test email and the start on purpose. Both need a person.

### The exit-code caveat

A command can exit `0` and still report a failed check inside its JSON. I cannot tell how readiness reports failures without running it.

Before you trust the gate, test it against a campaign you know is not ready. If the script exits `0` there, add a check on the JSON body for the blocking fields you find.

The CLI exit codes are `0` ok, `1` runtime, `2` usage, `3` auth, `4` not found and `5` rate limited, so a rate-limited call fails the gate loudly and you can simply retry.

### Keep the output

Redirect each command's output to a dated folder. When something goes wrong later, you will want to see what the gate saw.

Keep that folder out of version control if it holds lead data. Treat it as personal data.

## Connect the gate to start

A gate matters only if the start depends on it. Make the dependency explicit.

The simplest version is a human rule: nobody runs `campaigns start` until the gate has passed in the last hour. A better version records the pass in a file and checks its age.

```bash
# gate.sh writes gate-passed-camp_123.txt on success
if [ ! -f gate-passed-camp_123.txt ] || [ -n "$(find gate-passed-camp_123.txt -mmin +60)" ]; then
  echo "Run the gate first. The last pass is missing or older than 60 minutes." >&2
  exit 1
fi
```

This snippet is a sketch. Adapt the file name and the time window to your own process.

### The start command itself

When the gate has passed and a person has read the test email, preview the start.

```bash
firstsales campaigns start --org org_123 --workspace ws_123 --campaign camp_123 --data-file launch.json --dry-run
```

The README says the start body needs `savedVersionId`, `readinessVersion`, `idempotencyKey` and `confirmation`. The full lifecycle is in [starting, pausing and resuming campaigns from the CLI](/blog/start-pause-campaigns-cli).

### After the start

Watch the first hour. Read progress, events and alerts, and be ready to pause.

```bash
firstsales campaigns progress --org org_123 --workspace ws_123 --campaign camp_123 --json
firstsales campaigns events --org org_123 --workspace ws_123 --campaign camp_123 --json
```

For a deeper view of what to watch, see [email deliverability monitoring](/blog/email-deliverability-monitoring), and for what to do when it goes wrong, [deliverability incident response](/blog/deliverability-incident-response).

## What a checklist cannot catch

A gate catches configuration and hygiene problems. It cannot catch strategy problems.

It will not tell you that your offer is weak, that your audience is wrong or that your message does not match your product. Those problems need thinking and not scripting.

The posts on [pre-launch outbound validation](/blog/pre-launch-outbound-validation) and [automated pre-send checks for cold email](/blog/automated-pre-send-checks-cold-email) cover checks beyond configuration. The broader [cold email deliverability checklist](/blog/cold-email-deliverability-checklist) is a good companion.

### Keep the gate honest

A gate that never fails is either perfect or broken. Test it with a known-bad campaign every quarter to be sure it still catches problems.

Add a check each time you learn a lesson the hard way. A good gate grows from your own incidents.

### Remove checks that never matter

A gate that takes ten minutes will get skipped. Keep it quick, and move slow checks to a weekly job.

## Tune the gate for your team

One gate does not fit every team. A solo founder sending 50 emails a day needs fewer checks than a team that runs ten campaigns at once.

Start with the checks in this post and adjust after your first month. Remove a check only if you can say why it can never fail for you, and add one every time a launch teaches you something.

### Split the gate into two speeds

Fast checks run before every launch: identity, readiness, authentication, block list, credits and alerts. Slow checks run weekly: placement tests, list verification audits and a review of the copy library.

Splitting the gate keeps the fast one under a few minutes, which is the difference between a gate people run and a gate people skip.

### Name an owner

Every gate needs a person who owns it. That person decides when to add a check, reads the failures and keeps the script in step with the CLI.

The CLI changes as it grows. The README notes that exit codes changed in version 0.1.1, so a script written for an older release needs a review. Re-run `firstsales commands --json` after each upgrade and compare it with what your script calls.

### Write down what a failure looks like

For each check, write one line describing what failing means and who fixes it. "Authentication failed: DNS owner adds the missing record" is enough.

When a check fails at 9 a.m., nobody should have to work out whose problem it is. The line tells them.

### A failing run, described

Imagine the gate runs and the authentication check fails. The script prints the failing command, the exit code and a message that the gate failed, and it stops.

You publish the missing record at your DNS provider, wait for it to spread and run the gate again. This time everything passes, you send the test email, read it and decide to launch.

Nothing in that story is dramatic, and that is the point. A good gate turns a possible incident into a boring Tuesday.

## Let Claude Code run the gate

Claude Code can run the script and summarize the result. It should not run `campaigns start`.

The package ships three agent skills: `firstsales-cli`, `firstsales-product-mcp` and `firstsales-developer-api`. The CLI skill tells the agent to verify identity first, read before mutating and use `--dry-run` before risky writes.

### A prompt for the gate

```text
Run ./gate.sh for workspace ws_123, campaign camp_123, and summarize the result.
For each failing check, give the command, the exit code and the single next step.
Allowed: the commands in gate.sh and campaigns prepare-test-email.
Not allowed without my explicit yes: campaigns send-test-email, campaigns start, campaigns resume,
any delete, any api-keys command.
Never print the FIRSTSALES_API_KEY environment variable.
```

The prompt lets the agent read and summarize, and it blocks the send and the start. The test email needs a yes because it sends a real message.

### Human in the loop

The [MCP tools specification](https://modelcontextprotocol.io/specification/2025-11-25/server/tools) says "there SHOULD always be a human in the loop with the ability to deny tool invocations." The same idea applies to shell commands that change the world.

For guardrails across the whole agent, see [guardrails for email-sending agents](/blog/email-agent-guardrails).

## Where MCP fits

The FirstSales Product MCP server is `firstsales-product`, a streamable HTTP server at `https://api.app.firstsales.io/mcp`. It uses OAuth, and it does not accept Developer API keys.

The tool list depends on your tenant and your grant. In one connected session we confirmed campaigns list and get, workflow get and update, analytics, progress, events, blocked domains list and add, and billing reads.

Check `tools/list` for your own account. The readiness and test-email commands are CLI commands, so keep the gate in the CLI.

MCP is good for the conversation around the launch, such as asking which steps a campaign has or how it is progressing. Creating a campaign is not confirmed through MCP, so use `firstsales campaigns create` and then use MCP to edit and watch.

For the wider picture, read [CLI, MCP or the app: which for which job](/blog/cli-vs-mcp-vs-app-outbound).

## Common mistakes

Gates fail in familiar ways.

### Treating a pass as permission

A passing gate means the checks you wrote passed. It does not mean the launch is wise.

### Running the gate once and launching a day later

State changes. Keep the time between the gate and the start short.

### Editing the check to make it pass

A failing check is information. Fix the cause.

### Skipping the test email

The test is the only check that shows what a prospect sees. Run it every time.

### Letting the agent start the campaign

The agent can run the gate and report. A person starts the campaign.

### Putting the API key in a prompt

Keep it in the environment, and create a key with the smallest scopes the gate needs.

### Forgetting that deletes cannot be undone

`contacts delete` and other delete commands cannot be undone. Never let a gate script call them.

For more on what goes wrong without supervision, read [unsupervised AI outbound](/blog/unsupervised-ai-outbound).

## FAQs

### What does `firstsales campaigns readiness` do?

It runs the readiness check for a campaign. The README does not document its flags or output, so read `--help` and the OpenAPI contract.

### Which two commands must run before `campaigns start`?

`campaigns readiness` and `campaigns send-test-email`. Run both every time.

### Does the gate start the campaign?

No. The gate runs reads and prepares the launch. A person starts the campaign after reading the test email.

### Why chain several checks?

Readiness covers the campaign, and a launch also depends on domains, connectors, the list, credits and alerts. Each fails in its own way.

### What does `email-auth verify` check?

It checks authentication for a domain and DKIM selector, as in `firstsales email-auth verify --domain acme.com --dkim-selector s1`.

### Does a passing authentication check guarantee the inbox?

No. It proves the records validate. Placement depends on reputation, content and list quality.

### How do I check my balance?

Run `firstsales billing credits --org <org>`. Compare it with the campaign size before you start.

### How do I see open alerts?

Run `firstsales alerts list --org <org> --workspace <workspace>`. The group also has `ack` and `resolve`.

### How do I check whether people on my list are suppressed?

Use `firstsales suppression check` with up to 100 addresses or domains. It returns `value`, `suppressed` and `reason`, and there is no export.

### How do I read the copy that will be sent?

Run `firstsales campaigns workflow get` and save the output. Read it as a prospect would.

### How do I preview the start request?

Add `--dry-run` to `campaigns start`. It prints the request without sending it and uses no auth or network.

### What does the start body need?

The README says `savedVersionId`, `readinessVersion`, `idempotencyKey` and `confirmation`.

### Can a command exit 0 and still fail the check?

It is possible, depending on how the API reports a failed check. Test the gate against a campaign you know is not ready.

### What do the CLI exit codes mean?

`0` ok, `1` runtime, `2` usage, `3` auth, `4` not found, `5` rate limited.

### How old can a gate pass be?

Keep it short, such as under an hour. State changes, and the start may depend on a recent readiness result.

### Why use a checklist at all?

Evidence from surgery shows it works. A 2009 NEJM study found a 19-item checklist cut the inpatient death rate from 1.5% to 0.8%.

### Can MCP run the readiness check?

Do not assume it. Readiness is a CLI command, and the MCP tool list is per tenant, so check `tools/list`.

### Can the gate run in CI or cron?

You can run the read checks on a schedule. Keep `campaigns start` out of cron, because it needs a human decision.

### What key should the gate use?

A dedicated key with the smallest scopes it needs, created with `api-keys create`. Never paste it into a prompt.

### What should I do if a check fails?

Stop, fix the cause and run the gate again. Do not edit the check to make it pass.

---

## Conclusion

A pre-launch checklist earns its place when it can stop a launch. Build it as a script with exit codes, tie it to the start, and leave the last look to a person.

These are the takeaways.

- Chain `campaigns readiness` with domain, connector, list, credit and alert checks, and make any failure exit non-zero.
- Always run `campaigns readiness` and `campaigns send-test-email` before `campaigns start`.
- Read the test email yourself, because it is the only check that shows what a prospect sees.
- Keep the gap between the gate and the start short, and test the gate against a campaign you know is not ready.
- Keep keys in the environment, give the gate a key with the smallest scopes, and never let it call delete commands, which cannot be undone.

To try it, install the CLI, run `firstsales whoami --json` and read `campaigns readiness --help`. Then read [sending a test email from Claude Code first](/blog/send-test-email-claude-code) and [starting, pausing and resuming campaigns](/blog/start-pause-campaigns-cli) to complete the launch path.