TL;DR: Before any campaigns start, send one real test email through the same connector, domain and copy your campaign will use, and read it in a real inbox with the headers open. An assistant can prepare the test, but a person must send it and judge it, because only a human can say whether it reads right.
#The only check that shows the prospect's view
Every other pre-launch check looks at your setup from the inside. A test email is the one check that shows what a stranger will see.
You can verify DNS records, read the campaign workflow and confirm the list is clean, and still miss a broken link, an unresolved merge field or a subject line that reads oddly in a crowded inbox. The test catches those.
That is this post's single point of view: the test email is a human check wearing a technical costume. An assistant can prepare it, but the judgment belongs to a person reading the message in a real inbox.
The FirstSales command line interface (CLI) gives you two commands for it, campaigns prepare-test-email and campaigns send-test-email. Together with campaigns readiness, they form the pair of checks that must run before campaigns start.
The stakes are practical. Google's email sender guidelines tell senders to keep the user-reported spam rate below 0.10% and to prevent it from ever reaching 0.30%, and a first send with a broken message is a fast way to approach those lines.
Sending one test message costs almost nothing. Sending a thousand broken messages costs a domain.
I have not run these commands against a real account while writing this post, so every example uses placeholder IDs such as org_123, ws_123 and camp_123. Where the README is silent, I say so and point you to --help, --dry-run and the OpenAPI contract at developer.firstsales.io.
#The two commands
The package is @firstsales.io/cli and the binary is firstsales. As of October 2026 the published version is 0.1.11.
The campaigns group includes the two test commands.
| Command | What it is for | Sends a message? |
|---|---|---|
campaigns prepare-test-email | Prepares a test send for a campaign | ✗ |
campaigns send-test-email | Sends the test email | ✓ (one message) |
The README does not document either command. 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.
Check the local registry for the facts that matter.
bashfirstsales commands --json firstsales campaigns send-test-email --help
The package skill treats the command registry as the authority for what your installed version supports. Trust it over any article, including this one.
#Why there are two commands
A split between "prepare" and "send" is a familiar safety pattern. Preparation can be repeated and inspected, and sending is the step that reaches the world.
Use that shape. Run prepare-test-email, read what it gives you, and only then run send-test-email.
#Set up a safe session
Install the package and check it.
bashnpm i -g @firstsales.io/cli firstsales --help
Authenticate through the environment. The CLI resolves a key from --api-key, then FIRSTSALES_API_KEY, then a saved profile.
bashexport FIRSTSALES_API_KEY="<YOUR_FIRSTSALES_API_KEY>" firstsales whoami --json firstsales doctor --pretty
Never paste a real key into a Claude Code prompt. A key typed into a chat can appear in transcripts and logs.
#Use a key with the least access
A key that only prepares and sends test emails does not need to start campaigns or delete contacts.
bashfirstsales api-keys create \ --org org_123 \ --data '{"name":"Test email sender","scopes":["<TEST_EMAIL_SCOPES>"],"accessLevel":"workspace"}' \ --json
Read the scope names from the OpenAPI contract. The raw key is shown once, so store it in a secret manager at once.
The post on API keys for outbound agents explains why a narrow key limits the damage of a mistake.
#Confirm the workspace
whoami prints the organization and workspace context without exposing the key. Check that the workspace is the one you mean, because a test sent from the wrong workspace tests the wrong setup.
#Prepare the test
Start with the prepare command.
bashfirstsales campaigns prepare-test-email --org org_123 --workspace ws_123 --campaign camp_123 --json
Read the output carefully. I cannot describe its fields, but you should look for the sender, the step being tested, the recipient and anything that needs a decision.
If the output asks for a value you do not have, stop and find it. Do not guess.
#Preview with a dry run
Add --dry-run to see the request the CLI would send.
bashfirstsales campaigns prepare-test-email --org org_123 --workspace ws_123 --campaign camp_123 --dry-run
The README describes --dry-run as printing the method, URL, redacted headers and body without sending the request. It does not use auth or the network.
Check the org, the workspace and the campaign in the printed URL. A wrong value here means a wrong test.
#Read the campaign first
Before you prepare anything, read the campaign and its workflow.
bashfirstsales campaigns get --org org_123 --workspace ws_123 --campaign camp_123 --json firstsales campaigns workflow get --org org_123 --workspace ws_123 --campaign camp_123 --json > workflow.json
Save the workflow. It holds the exact steps and messages, and you will compare them with what lands in your inbox.
#Send the test
Sending is the step that reaches a mailbox. Treat it as a real send, even though it is one message.
bashfirstsales campaigns send-test-email \ --org org_123 --workspace ws_123 --campaign camp_123 \ --data-file test-email.json --dry-run
The recipient and any other fields belong in the request body. The CLI accepts a body through --data or --data-file, and the field names come from the OpenAPI contract.
Read the dry-run output, then run the command without --dry-run.
bashfirstsales campaigns send-test-email \ --org org_123 --workspace ws_123 --campaign camp_123 \ --data-file test-email.json --json
#Add an idempotency key if the contract asks for one
Some mutating commands in the CLI accept a global --idempotency-key flag, and the README says the CLI never makes one up. If the contract for the test email supports a key, supply one that names the test, such as test-camp_123-2026-10-08-1.
Check --help for your version. I will not claim the test email requires a key, because the README does not say so.
#One test per change
Send a new test every time you change the copy, the sender or the sequence. A test from last week proves nothing about today's edit.
Number your tests in a notes file. A line with the time, the step and what you saw is enough.
#Who should receive it
The test goes to a mailbox you control. Pick it with care.
Use an address on a different provider from your sending domain's mailbox. If you send from a Google Workspace mailbox, a test to an Outlook address shows how the other big provider treats you.
#Test more than one inbox
If your audience uses both Gmail and Outlook, test both. The two providers publish different rules, and they filter differently.
Google asks bulk senders for SPF, DKIM and DMARC and for one-click unsubscribe on marketing mail. Microsoft's requirements for high-volume senders began enforcement on May 5, 2025.
#Never send tests to real prospects
A test to a prospect is a real email to a real person, with all the risk that carries. Keep tests inside addresses you own.
If you want a wider check, use a seed list. The post on inbox placement seed lists explains the method and its limits.
#Check the block list first
If your test address shares a domain that you blocked, the test may be skipped. Read the list before you wonder why nothing arrived.
bashfirstsales blocked-domains list --org org_123 --workspace ws_123 --json
A blocked domain also blocks its subdomains, according to the README. The post on blocked domains and do-not-contact lists covers the details.
#The test as a flow
The diagram shows where the test sits between the gate and the start. The loop on the left is what makes it useful: a problem sends you back to fix and test again.
flowchart TD A[Gate passes] -- ready --> B[prepare-test-email] B -- request looks right --> C[send-test-email with dry-run] C -- reviewed --> D[send-test-email to your own address] D -- message arrives --> E[Human reads message and headers] E -- problem found --> F[Fix copy, domain or list] F -- changed one thing --> D E -- looks right --> G[Human decides on campaigns start]
Only the final arrow leads to a launch. Everything before it is reversible and costs one message at most.
#What to read in the inbox
When the message arrives, resist the urge to glance and move on. Work through a short list, in order.
#Did it arrive, and where
First, did it arrive at all? If not, check the spam folder, then check the blocked list and the connector.
Second, which folder did it land in? Primary, Promotions and spam each tell a story, though one message is a weak sample.
#The sender
Read the From name and address. Is it a name a stranger would recognise and trust?
A sender name that looks like a role, such as "Sales Team," reads like a campaign. A real person's name reads like a person.
#The subject and the first line
Look at the subject and the preview as an inbox shows them. The first line often decides whether a message gets opened.
The cold email skim test gives a quick method. Ask whether the message earns attention in five seconds.
The preview text deserves its own look. Email preheader optimization explains how it appears in the inbox list.
#The body
Read it aloud. Awkward phrases show up when you hear them.
Check every fact. A claim you cannot support is a liability, and a model-written line may contain one.
Check the ask. A good message makes one clear request.
#The signature
Check the name, the title, the company and any address your policy requires. The FTC's CAN-SPAM compliance guide says commercial email must include a valid physical postal address and a clear way to opt out.
#Read the headers
Headers show what receivers saw. Both big providers let you open them.
In Gmail, open the message, use the three-dot menu and choose "Show original." The page lists SPF, DKIM and DMARC results near the top.
#What to look for
You want three passes. SPF pass, DKIM pass and DMARC pass, with the domains aligned to your From address.
SPF is defined in RFC 7208, DKIM in RFC 6376 and DMARC in RFC 7489. A fail on any of them is a reason to stop.
#Match the headers to the CLI checks
You already ran email-auth verify as part of your gate.
bashfirstsales email-auth verify --domain acme.com --dkim-selector s1 --org org_123 --workspace ws_123 --json
The header view confirms the result with a real message. If the CLI said pass and the header says fail, something differs between the check and the send, such as a different sending domain or selector.
The domain walkthrough is in setting up sending and tracking domains from the CLI, and the record syntax is in SPF, DKIM and DMARC setup for 2026.
#Look for the list-unsubscribe header
For marketing and bulk mail, Google asks for one-click unsubscribe, specified in RFC 8058. It uses List-Unsubscribe and List-Unsubscribe-Post headers.
Search the original message for those header names. If they are missing, read one-click unsubscribe for cold email and check your setup.
Whether your campaign should carry them depends on your volume and your policy. Check the rule against your own situation and not against a rule of thumb.
#Check links and the unsubscribe path
Every link in the message is a promise. Click each one.
#Click every link
Check that each link goes where it says, loads quickly and does not trigger a browser warning. A tracking link that fails a certificate check is a red flag.
The tracking domain matters here. Verify it with the CLI before you test.
bashfirstsales tracking-domains verify --org org_123 --workspace ws_123 --domain domain_123 --json
The get command shows certificateEligibility, and a value like lookup_failed points at a lookup problem. Read the domain post for how to fix it.
#Test the opt-out
Use the unsubscribe path as a prospect would. Does it work, and does it confirm?
The FTC guide says opt-outs must be honored within 10 business days, and the law makes no exception for business-to-business email. It also sets penalties of up to $53,088 per violating email.
If you opt out with your test address, add its domain to your own test exceptions list, or use a throwaway address. Do not leave your test address blocked by accident.
#Plain text and tracking
Plain text messages with few links tend to look more personal. The post on plain text versus HTML in cold email explains the trade-offs.
If you track opens, check that the tracking does not break the message. Fewer moving parts means fewer ways to fail.
#Check variables and variants
Personalization is where most visible errors live.
#Merge fields
Look for blanks, raw placeholders and awkward fallbacks. A message that starts "Hi ," or "Hi {{first_name}}" is obviously mass mail.
Test with a contact record that has missing data. The edge case is the one that breaks in production.
#Variants
If your copy uses variations, send a test that shows more than one. The post on spintax in cold email explains how too many variants make messages read strangely.
Read each variant on its own. A variant that makes no sense alone will make no sense to a prospect.
#Naming other companies
If your copy mentions another company, read it twice. Naming competitors in cold email covers when it helps and when it backfires.
#Claims and numbers
Every number in the copy needs a source you can name. If you cannot point to one, remove the number.
#Check rendering
A message can look fine in one client and broken in another.
#Dark mode
Many people read mail in dark mode. Logos with white backgrounds and low-contrast text can break.
Dark mode email rendering explains common problems. If your messages are plain text, you avoid most of them.
#Mobile
Open the test on a phone. Long lines, tiny links and wide images show up fast on a small screen.
#A second client
Send the test to a second provider if you can. A message that looks right in one client and wrong in another needs simplifying.
#What a test email cannot prove
A test email is a sample of one. It cannot tell you how a thousand messages will fare.
It cannot prove inbox placement for your audience, because reputation builds across many sends. It cannot prove that the list is clean, because it does not touch the list.
#Placement needs more than one message
Use a seed list for broader signals, and watch Postmaster data as you scale. Google Postmaster Tools for cold email explains what the dashboards show.
#Copy needs real replies
Only prospects can tell you whether the copy works. A test checks that it is correct, not that it is persuasive.
#The first batch still needs watching
After the start, watch progress, events and alerts. Be ready to pause.
bashfirstsales campaigns progress --org org_123 --workspace ws_123 --campaign camp_123 --json firstsales alerts list --org org_123 --workspace ws_123 --json
The post on starting, pausing and resuming campaigns covers the lifecycle, and the pre-launch checklist from the terminal covers the rest of the gate.
#A scorecard for the received message
A scorecard keeps the review honest. Read the message once for feel, then again with the card in hand.
| Item | Pass looks like | Fail looks like |
|---|---|---|
| Arrival | In the inbox, or Promotions with a clear reason | Missing or in spam |
| Sender | A real name and the right address | Role name or wrong domain |
| Subject | Short, specific, no tricks | Vague or misleading |
| First line | About the reader | About you |
| Body | One idea, one ask | Several asks, long blocks |
| Variables | All resolved | Blanks or raw placeholders |
| Links | All load and match their labels | Broken or mismatched |
| Opt-out | Clear and working | Hidden or failing |
| Authentication | SPF, DKIM and DMARC pass | Any fail |
| Rendering | Clean on phone and dark mode | Broken layout |
Score each row as pass or fail, and fix every fail before you send another test. Do not average the rows, because one broken link is a fail regardless of the rest.
#Turn findings into changes
Sort the findings into three piles. Copy problems go back to the workflow, setup problems go to the domain checks, and list problems go to the block list and verification steps.
Use the workflow tools to edit the text, then send a new test. The edit might happen in the app, with campaigns workflow update in the CLI, or through MCP if your tool list includes the workflow tools, and in every case the new version needs a fresh test.
#Do not fix two things at once
If you change the copy and the sender together, a new problem has two possible causes. Change one thing, test, and move on.
It feels slow and it is faster in total. Debugging a double change costs more than two clean tests.
#A worked scenario
Here is an illustrative scenario, not a real case study. Suppose you build a three-step campaign and prepare to launch it.
You run the gate and everything passes. You prepare the test email, preview the send with --dry-run and send it to your own address on a different provider.
The message arrives in the inbox. You read it, and the first line begins "Hi ," with no name, because your test contact had no first name.
That is the failure the test exists to catch. You fix the fallback in the workflow, send a second test, and the greeting reads correctly.
Then you open the headers. SPF and DKIM pass, but DMARC shows a fail because the From domain and the DKIM domain do not align.
You stop. You go back to the domain steps, fix the alignment at your DNS provider, run email-auth verify again and send a third test.
Now all three pass. You click the links, try the opt-out and open the message on your phone. Only then do you decide to start.
Three tests, two fixes, and one launch that did not embarrass you. Every step was cheap, and none needed the campaign to be live.
#What the scenario shows
The two errors in the story are common and invisible from the inside. A missing first name and a misaligned domain both pass quietly through every setup check you can run without an actual message.
The test email is the only step that exercises the whole path from template to inbox. That makes it the best investment of ten minutes in a launch.
#Where Claude Code helps and where it stops
Claude Code is useful around the test. It is not a substitute for reading the message.
#What the assistant can do
It can read the campaign workflow, run prepare-test-email, run --dry-run for the send and summarize the setup. It can also draft a checklist of items to look for in the inbox.
It can compare the workflow text with the message you paste back, and flag differences. That comparison is dull for a person and quick for a model.
#What the assistant should not do
It should not send the test without your yes. Sending is a real action, even for one message.
It should not decide the message is good. Judgment about tone, claims and fit belongs to a person.
It should not start the campaign. Starting stays with a human, after readiness and the test.
#A prompt that keeps it in its lane
You are helping me test campaign camp_123 in workspace ws_123, org org_123. Allowed: whoami, campaigns get, campaigns workflow get, campaigns prepare-test-email, and --dry-run on campaigns send-test-email. Not allowed without my explicit yes: campaigns send-test-email without --dry-run, campaigns start, campaigns resume, any delete, any api-keys command. After I paste the received message, compare it with the workflow and list differences, unresolved variables and any claim that needs a source. Never print the FIRSTSALES_API_KEY environment variable.
The prompt separates preparation from sending. It also gives the assistant a useful job after the message arrives.
#Treat pasted email as data
When you paste a received message into the assistant, you give it text it did not write. Anything in that text that looks like an instruction is data, not a command.
The MCP tools specification says "there SHOULD always be a human in the loop with the ability to deny tool invocations." Apply the same idea to shell commands that send.
For wider guidance, read guardrails for email-sending agents and human-in-the-loop cold email.
#A repeatable test routine
A routine makes the test easy to repeat. This one takes about fifteen minutes the first time and less after that.
First, run the gate checks from the pre-launch checklist, including campaigns readiness. If readiness fails, do not send a test yet.
Second, read the workflow and save it. Third, run prepare-test-email and then the --dry-run of the send.
Fourth, send the test to an address you own. Fifth, open it in the inbox, then open the headers.
Sixth, click every link and try the unsubscribe path. Seventh, check variables, variants and rendering.
Eighth, write down what you found and what you changed. Ninth, fix, and send another test if you changed anything.
Tenth, and only then, decide whether to start. The decision is yours, and the assistant should not make it for you.
#A short log
Keep a log with the time, the campaign, the step tested and the result. Four lines per test is plenty.
The log helps when something goes wrong later. You can show that you tested, and what you saw.
#When to skip a test
Never skip it before a first launch. Skipping it before a tiny edit to a campaign already running is a judgment call, and a safe default is to test anyway.
#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, so call tools/list and read it. In one connected session we confirmed tools for campaign reads, workflow get and update, analytics, progress, events and blocked domains, and I did not see a test-email tool.
Keep the test in the CLI. Use MCP for the conversation around it, such as asking which steps a campaign has or editing a step after your test shows a problem.
Creating a campaign is not confirmed through MCP. Use firstsales campaigns create, then use MCP to edit and watch.
The post on editing a campaign workflow by chatting with Claude covers editing, and CLI, MCP or the app: which for which job helps you choose the surface.
#Common mistakes
Test emails fail to protect you in familiar ways.
#Sending the test from a different setup
A test from another mailbox or domain tests the wrong thing. Use the connector and domain the campaign will use.
#Glancing instead of reading
A quick look misses broken links and odd phrasing. Work through the list.
#Skipping the headers
The headers show authentication results. Without them, you are guessing.
#Testing once and editing after
A test of version one says nothing about version two. Test after every change.
#Sending the test to a real prospect
Keep tests inside addresses you own.
#Letting the assistant send
The assistant can prepare. A person sends and judges.
#Pasting the API key into a prompt
Keep it in the environment, and use a key with the smallest scopes.
#Forgetting the rest of the gate
The test is one of several checks. Run campaigns readiness and campaigns send-test-email before campaigns start, and remember that delete commands cannot be undone.
For the cost of removing the human from the loop, read unsupervised AI outbound.
#FAQs
#What does campaigns send-test-email do?
It sends a test email for a campaign. The README does not document its flags, so read --help and the OpenAPI contract.
#What does campaigns prepare-test-email do?
It prepares a test send so you can inspect it before sending. Run it first and read the output.
#Does the test email reach real prospects?
It should not. Send it to an address you own, and check the request with --dry-run first.
#Which commands must run before campaigns start?
campaigns readiness and campaigns send-test-email. Run both every time.
#Can I preview the send without sending?
Yes. Add --dry-run. It prints the method, URL, redacted headers and body, and it uses no auth or network.
#Does the test email require an idempotency key?
The README does not say so. Check --help for your version, and supply a key that names the test if the contract supports one.
#How do I read the headers in Gmail?
Open the message, use the three-dot menu and choose "Show original." Look for SPF, DKIM and DMARC results.
#What should the headers show?
SPF pass, DKIM pass and DMARC pass, with the domains aligned to your From address.
#What is one-click unsubscribe?
It is a method specified in RFC 8058 that lets a recipient unsubscribe with one click, using List-Unsubscribe and List-Unsubscribe-Post headers. Google asks bulk senders to support it.
#How many tests should I send?
Send one after every change to the copy, sender or sequence. A test of an old version proves nothing about a new one.
#Which inbox should I use?
An address you own, ideally on a different provider from your sending mailbox. Test Gmail and Outlook if your audience uses both.
#Can Claude Code send the test for me?
It can run the command if you allow it, but a person should say yes, because sending is a real action. Keep send-test-email on a prompt-for-approval rule.
#Can MCP send a test email?
Not as far as we confirmed. The tool list is per tenant and per grant, so check tools/list and use the CLI otherwise.
#What should I check in the body?
Facts, claims, the ask, merge fields, variants and tone. Read it aloud.
#What must a commercial email include?
The FTC's CAN-SPAM guide requires a valid physical postal address and a clear way to opt out, and opt-outs must be honored within 10 business days.
#Does a good test guarantee inbox placement?
No. It is a sample of one. Placement depends on reputation, content and list quality across many sends.
#What if the test lands in spam?
Check the headers for failed authentication, check the content and the domain, and run the domain checks again before you send more.
#What spam rate should I stay under?
Google says to keep the user-reported rate below 0.10% and never let it reach 0.30%.
#Do the CLI exit codes help here?
Yes. 0 is ok, 1 runtime, 2 usage, 3 auth, 4 not found and 5 rate limited. A script can stop on any non-zero code.
#What key should I use?
A dedicated key with the smallest scopes that cover the test, created with api-keys create. Never paste it into a prompt.
#Conclusion
A test email takes minutes and shows you something no dashboard can: the message as a stranger will read it. Make it a fixed step, and make a person own the verdict.
These are the takeaways.
- Run
campaigns readinessandcampaigns send-test-emailbefore everycampaigns start. - Prepare first, preview with
--dry-run, then send to an address you own. - Read the message, open the headers and check for SPF, DKIM and DMARC passes.
- Click every link, try the unsubscribe path and look for unresolved merge fields.
- Let Claude Code prepare and compare, but keep the send and the judgment with a person.
- Keep API keys in the environment, and remember that delete commands cannot be undone.
To try it, install the CLI, run firstsales whoami --json and read campaigns send-test-email --help. Then read the pre-launch checklist from the terminal and how to start, pause and resume campaigns to finish the launch path.