TL;DR: To run a cold email campaign from Claude Code, create it with firstsales campaigns create, edit and watch it through the FirstSales MCP server, and pass campaigns readiness and campaigns send-test-email before campaigns start. The agent proposes each step and you approve each gate. The order of operations is what keeps the launch safe.
#The angle: the order of operations is the product
Most guides about running outbound from an AI agent show a single impressive prompt. This one argues the opposite: the value sits in the sequence of small, checkable steps, and the prompt matters least.
Claude Code can talk to FirstSales through two surfaces, and each is good at a different job. The CLI creates, checks and launches. The MCP server edits and watches.
Keep that split and the agent can move fast on the reversible parts and stop at the parts that cost you reputation.
This post walks one campaign from idea to first send, with a human gate between every stage.
FirstSales is an AI SDR and cold email product. The commands below come from the @firstsales.io/cli README (version 0.1.11 as of October 2026), and nothing here was run against a live account while this was written. Where the README does not spell out a flag or a request body, the post says so and points you to firstsales commands --json, --dry-run and the OpenAPI document.
If you have not connected the server yet, do that first with the five-minute MCP setup. You only need the connection and a saved tool list.
One more framing point comes from the Model Context Protocol specification.
"For trust & safety and security, there SHOULD always be a human in the loop with the ability to deny tool invocations." (Model Context Protocol specification, 2025-11-25, Tools)
This guide turns that sentence into six concrete gates. Claude Code documents how its own approval prompts and server scopes work in its MCP guide.
#Before you begin
Collect four things before you open Claude Code.
- A working CLI session. Run
firstsales whoami --jsonandfirstsales doctor --prettyand confirm the organization and workspace match what you expect. - The MCP server connected and authenticated, with your tool list saved from the setup post.
- A connected sending mailbox with SPF, DKIM and DMARC in place. If you are unsure, read the SPF, DKIM and DMARC setup guide first, because no workflow fixes a bad domain.
- A written idea of who you are writing to and why now. A campaign without that is a mailing, not outbound.
Keep the safety rules visible. Never paste an API key into a prompt, and create keys with firstsales api-keys create using the narrowest access that covers the work.
Treat contacts delete and every other delete command as permanent, because they cannot be undone.
Note that the README adds a rule of its own for agents: inspect first, mutate deliberately, verify after every action. Each gate below follows it.
#The six gates at a glance
| Gate | Surface | What happens | Who decides |
|---|---|---|---|
| 1. Frame | Paper or a markdown file | Audience, offer, proof, stop rules | You ✓, agent ✗ |
| 2. Create | CLI | firstsales campaigns create | You approve the command ✓ |
| 3. Shape | MCP | Read and edit the workflow | You approve each write ✓ |
| 4. Check | CLI | firstsales campaigns readiness | You read the result ✓ |
| 5. Test | CLI | firstsales campaigns send-test-email | You read the inbox ✓ |
| 6. Start | CLI | firstsales campaigns start with a reviewed body | You, in person ✓, agent ✗ |
The agent never decides a gate. It prepares the evidence for each one and then waits.
flowchart TD A[Gate 1: frame on paper] --> B[Gate 2: CLI campaigns create] B --> C[Gate 3: MCP workflow get and update] C --> D[Gate 4: CLI campaigns readiness] D -->|issues found| C D -->|clean| E[Gate 5: CLI send-test-email] E -->|looks wrong| C E -->|looks right| F[Gate 6: CLI campaigns start] F --> G[MCP progress, analytics, events]
Notice the loops back to gate 3. A failed check should send you back to editing, never forward to launch.
#Gate 1: frame the campaign on paper
Open a markdown file in the repo and write the brief before any tool runs. Ask Claude Code to interview you if you prefer, but make it produce a file you can read.
A useful brief has seven short fields:
- the audience, in one sentence a stranger could apply to a list of companies
- the trigger, meaning why this person hears from you today
- the single offer, stated so plainly that a reader can say yes or no
- the proof you can show, with a source you can name
- the number of steps and the gap between them
- the stop rules, such as pausing on any bounce spike
- the human who approves launch
The audience and the offer are the two fields that sink campaigns when they are vague. The post on defining an ideal customer profile goes through the audience question, and offer clarity in cold email covers the second.
For the trigger, look at buying signals for cold email. A signal that you can verify beats a flattering opener every time.
Ask Claude Code to draft the brief and to mark every sentence it could not source from you as an assumption. Then read it. If you cannot defend a line, delete it.
Here is a prompt that works well for this stage:
Read brief.md. List every claim that I have not given you evidence for. Do not call any FirstSales tool. Do not write email copy yet.
This gate costs no credits and no sends. It is also where most of the quality gets decided, so give it the time.
#Gate 2: create the campaign with the CLI
Creation is a CLI job. The CLI's command list (firstsales commands --json) includes campaigns create, and campaign creation did not appear among the MCP tools in the connected session we checked. So the rule for this guide is simple: create with the CLI, then use MCP to edit and watch.
Start by checking the exact shape of the command on your installed version.
bashfirstsales commands --json firstsales campaigns create --help
firstsales commands --json prints the local command registry, which the CLI skill calls the authority for what exists on your version. The README states that the full set of global flags includes --data, --data-file, --idempotency-key, --dry-run and --output.
Do not guess the body of campaigns create. The README does not give a request body for it, so read the OpenAPI document at https://developer.firstsales.io/openapi/firstsales-public-v1.yaml or the command reference.
Once you know the body, rehearse with --dry-run. The README describes --dry-run as a way to preview a request without sending it.
bashfirstsales campaigns create \ --org org_123 --workspace ws_123 \ --data-file campaign.json \ --idempotency-key spring-pilot-create-001 \ --dry-run --json
Read the dry-run output with Claude Code in the room, and ask it to check the body against the OpenAPI schema. When you agree, run the same command without --dry-run.
The idempotency key does real work here. If the terminal drops and you run the command again, the same key tells the API this is a retry and not a second campaign.
After creation, read the campaign back.
bashfirstsales campaigns list --org org_123 --workspace ws_123 --json firstsales campaigns get --campaign <campaign-id> --org org_123 --workspace ws_123 --json
Check the status. A new campaign should be a draft that has not started. If it already shows as running, stop and investigate before doing anything else.
Save the get output in your repo next to the brief. You will compare against it at gate 3.
#Gate 3: shape the workflow through MCP
Now the conversation tool earns its place. Editing a workflow is iterative, wordy work, and plain-language editing suits it.
First, read. Ask for the workflow through the MCP server and demand raw output.
Use the firstsales-product campaign workflow get tool for campaign <campaign-id>. Show me the full result untouched, then list the steps in order with their delays. Do not change anything.
Check your tool list from the setup post. The confirmed session had both campaign workflow get and campaign workflow update, but the product skill describes the released catalog as read-only today. If your list has no update tool, make the same edit with the CLI instead.
The CLI route is firstsales campaigns workflow get --campaign <campaign> and firstsales campaigns workflow update. The README's example for the update sends only two fields, the included list ids and the sender connector ids, as one PATCH.
bashfirstsales campaigns workflow get --campaign <campaign-id> --org org_123 --workspace ws_123 --json firstsales campaigns workflow update --campaign <campaign-id> \ --org org_123 --workspace ws_123 \ --include-list list_123 --sender sender_123 --dry-run
There is also a separate campaigns workflow-update command, which the README says is a PUT. The README does not document its body, so treat it as unverified until you have read the OpenAPI document.
For deep edits to step copy and timing, the follow-up guide on rewriting campaign steps by chatting with Claude goes into the read, diff, write and re-read loop.
A few rules make this gate safe:
- Ask the model to state the one change it wants to make before it calls a write tool.
- Approve the tool call only if the arguments match that statement.
- Re-read the workflow after every write and compare it with the saved copy.
Attach your audience at this gate too. The list that feeds the campaign comes from the import workflow in importing leads from the terminal, and the include-list flag above is how the README attaches it.
On copy, give Claude Code the same standards you would give a junior writer. The guide to writing cold emails and the post on cold email templates are good references to load into the session. For subject lines, see cold email subject line and for the gap between steps, follow-up email strategy.
Keep the copy short, specific and honest. An agent will happily produce fluent filler, and filler at scale is what the post on AI slop in cold email warns about.
#Gate 4: run the readiness check
Before any test email, run the readiness command. It exists to catch what you forgot: no sender attached, no list, a domain problem, missing steps.
bashfirstsales campaigns readiness --campaign <campaign-id> --org org_123 --workspace ws_123 --json
The CLI's command list includes campaigns readiness, and the README example for start shows that a start request carries a readinessVersion. The exact fields of the readiness response are not documented in the README, so read your own output rather than relying on this post.
Ask Claude Code to do two things with the result. First, quote every blocker verbatim. Second, propose a fix for each and say which surface the fix needs.
Here is the readiness output. Quote each blocker exactly. For each one, say whether a fix needs the CLI, MCP or the web app. Do not fix anything yet.
Fix blockers one at a time, and run readiness again after each fix. A clean result is a green light for gate 5, not for launch.
If the readiness output mentions domain authentication, run firstsales email-auth status to see the state yourself. The command list includes email-auth with status and verify, and warmup status for the sending mailbox.
Deliverability checks deserve a human read. The cold email deliverability checklist and the guide to automated pre-send checks show what a good pre-flight looks like.
As of October 2026, Google's email sender guidelines set the bar for anyone writing to Gmail. They describe a bulk sender as one that sends close to 5,000 or more messages to Gmail accounts in a day, and they ask all senders to keep the spam rate shown in Postmaster Tools below 0.10% and never reach 0.30%.
#Gate 5: send a test email to yourself
The test email is the cheapest insurance in the whole process. It shows you what a real recipient sees, including the sender name, the rendering, the links and the unsubscribe line.
bashfirstsales campaigns send-test-email --campaign <campaign-id> --org org_123 --workspace ws_123 --json
The README does not document the body of send-test-email, and the command list also includes prepare-test-email. Run firstsales campaigns send-test-email --help, read the command reference, and use --dry-run first so you can see exactly what would be sent and to whom.
When the test arrives, read it like a stranger would.
- Does the first line make sense without context?
- Is the sender name one a person would recognize?
- Does every link go where it should?
- Is there a clear way to opt out?
- Does anything look templated, such as a broken merge field?
If you find a problem, go back to gate 3 and fix the workflow. Do not edit around it in your head.
Do not skip this gate because the copy "looks fine" in the terminal. Rendering, merge fields and sender profiles only show up in a real inbox.
Add the test to your notes with a date. If you later change the copy, run it again, since a small edit can break a merge field.
#Gate 6: start, then watch
Launching is the one step the agent should only prepare.
The README example for campaigns start shows a JSON body with savedVersionId, readinessVersion, idempotencyKey and confirmation. The same README advises using --dry-run with the same body to inspect the request.
bashfirstsales campaigns start --campaign <campaign-id> \ --org org_123 --workspace ws_123 \ --data-file start.json --dry-run --json
The command list also includes prepare-launch, which prepares the launch request. Its flags are not in the README, so check firstsales campaigns prepare-launch --help and read the output yourself.
Read the dry-run with these questions:
- Is the version id the one you tested?
- Does the readiness version match the readiness run you just read?
- Is the confirmation field filled by you, not by the model?
Then run the real command yourself, in your own terminal, so the decision is plainly yours.
After launch, switch to MCP for watching. Ask for progress and the latest events, and keep the questions simple.
Get progress for campaign <campaign-id>. Report sends, bounces and replies as counts. Then list the last 20 events. Do not interpret yet.
The follow-up guide on asking Claude how your campaign is doing covers how to read the numbers without fooling yourself. Early on, the number to watch is the bounce count, since a spike means you should pause.
The CLI has campaigns pause and campaigns resume for that, and the post on starting and pausing campaigns from the CLI covers them in detail.
Plan the first day's volume conservatively. The guides on how many cold emails per day and emails per inbox per day give ranges to start from.
#Rules to give Claude Code
Put your standing rules in CLAUDE.md so every session starts with them. The text below is a starting point you can edit.
Cold email rules for this repo - The CLI creates, checks and launches. MCP edits and watches. - Never run campaigns start, pause or resume. Print the command and wait. - Before any workflow write, state the one change in a sentence. - After any write, re-read and show the difference. - Run campaigns readiness and campaigns send-test-email before any start. - Never ask for or print an API key. Never run a delete command. - Quote tool output verbatim before you interpret it.
These are instructions, not guarantees. The permission prompt in Claude Code is the real gate, so keep write tools on "ask every time" and let the written rules guide the conversation.
For a wider treatment of agent limits, read guardrails for email-sending agents, and for the case for keeping a person in the loop, human-in-the-loop cold email.
#What the agent should never decide
Some decisions belong to people no matter how capable the model is.
Who to email. A model can suggest segments, but the final list reflects your judgment about fit. See segment validation tests for outbound for a way to check a segment before you commit.
What claims to make. The model will phrase anything fluently, true or not. Every claim in the copy needs a source you can name.
When to send. Launch timing affects your mailboxes. Treat it as a business decision.
What counts as success. Define it before launch. Reply rate is a better starting point than opens, since the guide on email open rates as dead metrics explains why tracking pixels mislead.
Whether to continue. If bounces or complaints rise, a person decides to pause. The post on unsupervised AI outbound describes what happens when nobody does.
#When to stop and ask a person
Some signals mean the session should pause until a human looks. Write them into your notes so no one has to remember them under pressure.
Stop when the readiness output contains a blocker you do not understand. Stop when the test email shows a sender name or address that you did not choose. Stop when a tool call returns data from an organization or workspace that differs from your brief.
Stop when the model proposes a command that is not in your registry. The CLI skill treats firstsales commands --json as the authority, so a command that does not appear there is a guess. Never let a guess near a live account.
Stop, finally, whenever you feel rushed. Launch pressure is the moment an extra gate pays for itself.
#Compliance and deliverability notes
Cold email is legal in many places when you follow the rules, and the rules differ by country. The post on whether cold email is legal in 2026 covers the main regimes.
For the United States, the Federal Trade Commission's CAN-SPAM compliance guide lists the requirements: no false or misleading header information, no deceptive subject lines, a clear opt-out method, and your valid physical postal address. It also says you must honor opt-out requests promptly.
Bulk senders also need authentication that passes, and RFC 7489 defines DMARC. For Gmail recipients, RFC 8058 defines one-click unsubscribe, and Google's guidelines require it for bulk senders. The guide to one-click unsubscribe in cold email covers how to add it.
Check your numbers against spam complaint rate thresholds and the Google bulk sender rules for 2026. Before you send, run the addresses through email verification, and remember that catch-all domains need special care, as the post on catch-all email addresses explains.
If you are new to outbound at this scale, pre-launch outbound validation is a useful drill.
For a broader view of where this workflow sits, see where FirstSales fits in an outbound stack and the five layers of an AI outbound agent stack.
#A worked pass through the gates
A walk-through with invented names shows how the gates feel in practice. Every name and number in this section is hypothetical, and none of it came from a real account.
Imagine a two-person team that sells scheduling software to dental clinics. They write the brief in brief.md: the audience is independent clinics with one to three locations, the trigger is a new online booking page, and the offer is a free audit of missed-call volume.
Claude Code reads the brief and flags two assumptions. One says "most clinics miss calls at lunch", and the other says "our audit takes ten minutes". The team has evidence for the second and none for the first, so the first line goes.
At gate 2 the team runs the dry run for campaigns create, reads the body together with the model, and creates the campaign under an idempotency key. They save the get output in the repo.
At gate 3 the model reads the workflow through MCP and proposes a three-step sequence. The team shortens the first email to five sentences, removes a claim about "industry averages" that has no source, and asks for a plain unsubscribe line.
At gate 4 the readiness check reports a missing sender attachment. The team attaches a sender, reruns readiness, and reads a clean result.
At gate 5 the test email arrives with a broken first-name merge field. The team goes back to gate 3, fixes the field, and sends a second test. The second test looks right.
At gate 6 one person runs the start dry run, checks that the version id matches the tested version, and launches. The model then watches progress and reports counts only.
Nothing in that story needed a clever prompt. It needed a sequence and a person who owned each gate.
#Prompts you can reuse at each gate
Good prompts for this work share a shape: a verb, a boundary and a proof requirement. The set below covers the whole sequence, and you can paste it into a notes file.
Frame. "Read brief.md. List every claim that lacks evidence. Do not call any tool."
Create. "Show me the exact firstsales campaigns create command with --dry-run. Wait for my yes before you drop the flag."
Read. "Call the campaign workflow get tool. Print the raw result. Then list steps and delays."
Propose. "Propose one change to step two. State the before and after text. Do not call a write tool."
Write. "Apply that change only. Then re-read the workflow and show me what differs from the saved copy."
Check. "Run firstsales campaigns readiness and quote every blocker."
Test. "Prepare the send-test-email command for my address. Show the dry run first."
Launch. "Prepare the start request body and show it. Do not run it."
Watch. "Report progress as counts with the field names you read. Then list the last 20 events."
Each prompt keeps the model in the proposing role. The word "wait" appears often on purpose.
You will also want one prompt for a bad day. If bounces climb, use this: "Show bounce events from the last 24 hours, grouped by domain. Tell me which fields you used. Do not pause anything."
The model finds the pattern and you decide whether to run firstsales campaigns pause. Pausing is a quick command and a poor one to automate while you are still learning how your list behaves.
#Roles on a small team
Even a two-person team benefits from naming roles at each gate, since "everyone approved it" often means no one did.
The author writes the brief and the copy with the model. The operator runs the CLI commands and owns the idempotency keys. The approver reads the readiness result and the test email and says yes to launch. On a solo project, you play all three, so write the three names in the notes file and review in three separate passes.
The split also helps with credentials. The operator holds a CLI key with the access that the job needs, and the author and approver never need one. That fits the advice in the guide to MCP tool permissions for sales: give each role only the access it uses.
Rotate nothing in a hurry, and never paste a key into a chat to share it. Create a new key with firstsales api-keys create for each person or job, and revoke it when the job ends.
#Mistakes that break the sequence
Editing during a run. Changing a live campaign's workflow while sends are in flight can create confusion about which version a recipient saw. This post has not verified how live edits apply to a running campaign, so pause first, edit, and test again.
Reusing last month's start body. The start body carries a saved version id and a readiness version. A stale one points at old work.
Letting the model summarize its own work. Ask for raw tool output and read it. A summary that sounds right can hide a field that changed.
Skipping the brief. Without the brief, the model fills gaps with plausible filler, and you ship the filler.
Scaling before the first read. Look at the first twenty sends before you raise volume. The guide to AI email quality at scale explains why quality checks must run before volume does.
#Measuring the first week
After launch, define the questions you will ask each day, and ask the same ones. Consistent questions make trends visible.
On day one, ask how many emails went out, how many bounced and whether any failed. On day two, add replies, and sort them into interested, not now, wrong person and opt-out.
By day five you have a small sample. A small sample cannot support big conclusions, so treat early reply counts as direction and not as proof. Our cold email benchmarks and reply rate benchmarks for 2026 show what ranges to expect, with sources.
Handle replies with a plan. The reply handling playbook describes categories and response times, and the CLI has inbox commands for threads and drafts that you can use with the same approval habits.
Write down what you learned in the brief file. The next campaign starts from that note, not from memory.
#FAQs
#Can Claude Code create a cold email campaign through MCP?
Campaign creation through MCP is not confirmed. Create the campaign with firstsales campaigns create, then use MCP to edit and watch it.
#What is the right order of operations?
Frame on paper, create with the CLI, shape the workflow, run campaigns readiness, run campaigns send-test-email, and only then run campaigns start. Each step ends with a human check.
#Do I need a test email every time?
Yes. Rendering, merge fields and sender profiles only show up in a real inbox. Run it again after any copy change.
#What does --dry-run do?
The README describes it as a way to preview a request without sending it. Use it before create, workflow update and start.
#What is the idempotency key for?
It marks a request as a retry of the same action. If a command runs twice with the same key, you do not get two campaigns.
#Where do I find the body for campaigns create?
Check firstsales campaigns create --help, the command reference at developer.firstsales.io, or the OpenAPI document. The README does not spell it out.
#Can the agent start the campaign for me?
It can prepare the command, but you should run it yourself. The start body carries a confirmation field that should come from you.
#What if readiness reports a blocker?
Fix it, then run readiness again. Do not move to the test email until the result is clean.
#How do I edit step copy without breaking the workflow?
Read the workflow first, make one change at a time, and re-read afterward. The guide on editing workflows describes a diff-based loop.
#What if my tool list has no workflow update tool?
Make the same edit with firstsales campaigns workflow update. The README example sends only the include-list and sender fields.
#Is campaigns workflow-update the same as workflow update?
No. The README describes workflow-update as a PUT and workflow update as a PATCH. The PUT body is not documented, so read the OpenAPI document first.
#Where should the leads come from?
Import them with the CLI, verify the emails, and attach the list at the workflow stage. The terminal import guide has the steps.
#How many emails should I send on day one?
Start low and raise volume as bounces and complaints stay low. Our posts on daily volume and per-inbox limits give starting ranges.
#What numbers should I watch first?
Bounces, then replies. Opens are unreliable because mail clients can load tracking pixels automatically.
#When should I pause?
Pause when bounces spike or complaints approach Google's 0.30% line. Use firstsales campaigns pause.
#Are cold emails legal?
In many places, yes, with conditions. In the United States the CAN-SPAM Act requires an opt-out, a postal address and honest headers and subjects.
#Can I use the same flow for follow-ups?
Yes. Follow-ups are steps in the workflow, so they pass through the same gates.
#How do I stop the agent from running destructive commands?
Keep delete commands out of your allow rules, write the rule in CLAUDE.md, and keep permission prompts on. Deletes cannot be undone.
#Do I need the web app at all?
For account setup, billing and anything the CLI or MCP does not expose, yes. This guide covers the campaign loop only.
#What should I read next?
Read Import leads and build lists from the terminal, then the guide to editing your workflow with Claude.
#Conclusion
The campaign you build this way is slower to start than a one-shot prompt and far safer to run. Six gates, each with a named owner, keep the model in the role it does well.
Remember the three rules. Create with the CLI and edit with MCP. Run campaigns readiness and campaigns send-test-email before campaigns start. Never put a key in a prompt, and never run a delete you cannot undo.
Write the brief first, rehearse every mutating command with --dry-run, and keep the launch in your own hands.
Ready to try it? Open a Claude Code session, run firstsales whoami --json, and write your campaign brief. Then follow the MCP setup guide if you have not connected the server.