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.
#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 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).
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.
bashnpm i -g @firstsales.io/cli firstsales --help
Put the key in the environment, never in a prompt.
bashexport 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.
bashfirstsales 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.
#Check 1: identity and workspace
A wrong workspace invalidates every other check. Start there.
bashfirstsales 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.
bashfirstsales 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.
bashfirstsales 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.
bashfirstsales 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 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 covers each command in detail. For the record syntax itself, see SPF, DKIM and DMARC setup for 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 and Google Postmaster Tools.
#Check 4: connectors and warm-up
A campaign sends through connected accounts. Those accounts must work and must be warm.
bashfirstsales 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 and email sending limits. For why warming never ends, read ongoing email warm-up.
#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
bashfirstsales 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
bashfirstsales blocked-domains list --org org_123 --workspace ws_123 --json firstsales suppression check [email protected] 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.
#Verify the addresses
Verification catches addresses that will bounce. The post on email verification before sending explains the process, and catch-all email addresses covers the tricky cases.
High bounce rates damage reputation quickly. 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 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 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.
bashfirstsales 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 gives a quick method. Plain text versus HTML 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 explains the risks of too many variants.
#Check rendering
Look at the message in a dark-mode client too. Dark mode email rendering and 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, and one-click unsubscribe for 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.
bashfirstsales 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
bashfirstsales 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.
bashfirstsales 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, 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 |
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.
bashfirstsales 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.
#After the start
Watch the first hour. Read progress, events and alerts, and be ready to pause.
bashfirstsales 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, and for what to do when it goes wrong, 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 and automated pre-send checks for cold email cover checks beyond configuration. The broader 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
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 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.
#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.
#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.
#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 readinesswith domain, connector, list, credit and alert checks, and make any failure exit non-zero. - Always run
campaigns readinessandcampaigns send-test-emailbeforecampaigns 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 and starting, pausing and resuming campaigns to complete the launch path.