Skip to content
NewSee how
cli

Outbound From the Terminal: A Practical CLI Runbook

Run outbound from the terminal with the FirstSales CLI: setup, profiles, a safe loop, scripts, exit codes and what stays in the app.

TL;DR: The terminal suits the repeatable half of outbound: checks, reports, imports and previews. Set up the FirstSales CLI with a narrow key, follow the inspect, mutate, verify loop, rehearse every write with --dry-run, and leave judgment calls to a person in the app.


#Why run outbound from a terminal at all

Most outbound work is not creative. It is checking, copying, comparing and clicking the same buttons in the same order.

A person is good at the creative part and poor at the rest. After the fortieth check of the week, attention slips, and a wrong workspace or a skipped step creeps in.

The command line is where repeatable work lives. A command can be written down, reviewed, scheduled, logged and repeated by someone else next week.

FirstSales ships a CLI for this. The package @firstsales.io/cli installs a binary named firstsales, and its README describes it as a thin, JSON-first layer over the public Developer API.

This post is a runbook. It takes you from a clean machine to a weekly rhythm, with the safety steps in place.

It does not tell you to move everything to the terminal. Part of the skill is knowing what to leave in the app, and a section below covers that.

If you are still deciding between surfaces, read CLI, MCP or the app first. If you already know you want the terminal, carry on.

#The point of view: the terminal is for the boring half

Here is the stance this post argues for. Run the boring half of outbound from the terminal, and keep the thinking half in front of a person.

Boring does not mean unimportant. A report that checks bounce movement every Monday is boring and valuable. A suppression check before every import is boring and protects your domains.

Thinking work is different. Choosing an audience, judging a reply and deciding what to say are tasks where context on a screen helps and a script does not.

The Unix tradition behind the command line states a similar split. Doug McIlroy's often-cited summary, recorded on the Unix philosophy page, says to make each program do one thing well, to make programs work together, and to use text as the common interface. Paraphrased: small tools, joined by plain text.

The FirstSales CLI follows that shape. Each command does one small thing and prints text that the next tool can read.

That is why the terminal suits outbound hygiene. You compose small, checkable steps instead of trusting one large clever one.

#Day one: install, authenticate, verify

Start with a machine you control and Node 20 or later, which the README lists as the requirement.

#Install

bash
npm install -g @firstsales.io/cli firstsales --help

Check the version after install, since the CLI moves quickly.

bash
firstsales --version

#Create a key for yourself

Open the app or use an existing owner key to create a personal key with the access you need. Prefer workspace level and only the scopes your tasks require.

bash
firstsales api-keys create \ --org org_123 \ --data '{"name":"terminal-udit-ws123-202610","scopes":["campaigns:read"],"accessLevel":"workspace"}' \ --json

Replace the name with your own. The raw key prints once, so copy it into your secret manager at that moment.

Do not add --idempotency-key to this command, because the README says it is unsupported for api-keys create. And never paste a key into a chat with an AI tool.

The post on API keys for outbound agents explains how to size keys. For your own terminal, start read-only and widen one scope at a time as real tasks need it.

#Authenticate

The CLI looks for a key in this order: the --api-key flag, the FIRSTSALES_API_KEY environment variable, then a saved profile.

For a quick session, export the variable in your shell.

bash
export FIRSTSALES_API_KEY="<YOUR_FIRSTSALES_API_KEY>"

Type the real value from your secret manager. Do not put it in a script file or a command that lands in shared history.

#Verify identity

bash
firstsales whoami --json

The README compares whoami to npm whoami. It validates the token and prints the user, key, organization and workspace context, and does not mutate anything.

Read the output. Check the organization and workspace are the ones you meant.

#Check setup

bash
firstsales doctor --pretty

doctor checks your setup. Run it whenever something behaves oddly, before you start debugging anything else.

#First reads

Find your organization and workspace ids, and list your campaigns.

bash
firstsales orgs list --json firstsales workspaces list --org org_123 --json firstsales campaigns list --org org_123 --workspace ws_123 --json

Use --pretty the first time, to see the real output shape. This post avoids naming response fields, because the live output is the authority.

#Profiles, environment variables and several workspaces

If you manage more than one workspace or organization, you need a way to switch without mistakes.

#Named profiles for a laptop

The CLI can store named profiles locally.

bash
firstsales auth login --profile prod --base-url "https://api.app.firstsales.io" firstsales auth status --profile prod --json firstsales auth logout --profile prod

The README shows --api-key on the login command. Prefer entering the key through a prompt or a secrets tool if your version supports it, and check firstsales auth login --help for the options on your installed version.

Select a profile on any command with --profile <name>, or set FIRSTSALES_PROFILE. auth status never prints the raw key.

#Environment variables for automation

For scripts and CI, prefer environment variables. The README says the CLI can read local profiles, but automation should prefer variables, and you should not commit profile files or keys.

Other variables are FIRSTSALES_BASE_URL, which defaults to https://api.app.firstsales.io, and FIRSTSALES_PROFILE.

#Make the target visible

The most common terminal mistake is acting on the wrong workspace. Make the target visible in every command by passing --org and --workspace explicitly, and consider a prompt that shows the active profile.

bash
alias fsq='firstsales whoami --json'

Run a check like that at the start of a session and before any write. A second of looking beats an hour of cleanup.

#One key per task, not one per person

A person who runs three kinds of task should hold three keys, not one wide one. Switch with profiles.

A read-only profile for daily checks stays loaded. A write profile is used only for the write, and logged out afterwards.

#The loop: inspect, mutate, verify

The README distills a loop for agents and scripts, and it works for humans too.

1. firstsales whoami --json 2. Read org/workspace/resource state. 3. Plan the smallest mutation. 4. Use --dry-run when useful. 5. Use --idempotency-key only for supported retryable mutations; never for api-keys create. 6. Use --confirm only for intentional destructive commands. 7. Re-read state and verify the result.

Its motto is "Inspect first. Mutate deliberately. Verify after every action."

#Why each step exists

Identity first prevents wrong-workspace errors. Reading state gives you a before picture to compare with.

The smallest mutation limits the blast radius. Change one list or one campaign, not twenty.

A dry run shows exactly what would be sent. An idempotency key makes a retry safe when a network call times out.

Confirm only for deletes keeps a pause before the irreversible. Re-reading proves the change happened, instead of trusting a success message.

#The loop in practice

Say you want to add a list to a campaign's workflow. Here is the loop with real commands.

bash
# 1 and 2: identity and before state firstsales whoami --json firstsales campaigns workflow get --campaign camp_123 --org org_123 --workspace ws_123 --pretty # 3 and 4: smallest change, previewed firstsales campaigns workflow update --campaign camp_123 --org org_123 --workspace ws_123 \ --include-list list_123 --dry-run # apply only after the preview looks right firstsales campaigns workflow update --campaign camp_123 --org org_123 --workspace ws_123 \ --include-list list_123 --json # 7: after state firstsales campaigns workflow get --campaign camp_123 --org org_123 --workspace ws_123 --pretty

The README says workflow update sends one PATCH with only the ids you give, and that you can repeat --include-list for several lists. It also takes --sender for sender connector ids.

Save the before and after output to files. A plain diff of the two is a record of what changed.

#Keep the loop in your head

The loop is short enough to memorize. When something feels rushed, say the steps in order. Most expensive mistakes skip step 1, 4 or 7.

#Reading output: JSON, tables and pipes

The CLI is built to be read by machines, and that is a feature for you too.

#Output formats

The README lists --output json|table|tsv. It defaults to a table on a terminal and JSON when piped.

Pass --json for compact JSON and --pretty for indented JSON. Use --output tsv when you want columns you can feed to other tools.

#jq for filtering

The jq manual covers the filter language. A few patterns carry most of the work.

bash
# count items in a list response firstsales campaigns list --org org_123 --workspace ws_123 --json | jq 'length' # see the top-level keys so you know the shape firstsales campaigns list --org org_123 --workspace ws_123 --json | jq 'keys'

The first example assumes the response is an array, and the second works on an object. Check the shape with --pretty once, and write the filter that matches it.

#Pagination

List commands take --page and --limit, and --all fetches every page. Use --all for reports and a limit for quick looks.

#Save before you transform

Write raw output to a dated file first, then transform the file. If a filter is wrong, you still have the original.

bash
mkdir -p ~/outbound/raw firstsales campaigns list --org org_123 --workspace ws_123 --all --json \ > ~/outbound/raw/campaigns-$(date +%F).json

This habit is the base of the weekly report approach in weekly campaign reports with cron and the CLI.

#Do not parse what you have not looked at

Never write a script against a field you have not seen in real output. Run the command, read it, then write the filter.

Fields can change between CLI releases. A field-agnostic comparison, as in the weekly report post, survives changes better than a hard-coded path.

#Dry runs and idempotency keys

Two flags do most of the safety work, and they deserve their own section.

#What --dry-run does

The README says --dry-run previews the HTTP request without auth or network calls. It prints the method, URL, redacted headers and body.

That makes it safe to use anywhere, including on a machine with no key at all. It is also a way to learn the shape of a request.

bash
firstsales contacts create --org org_123 --workspace ws_123 \ --data '{"email":"[email protected]","firstName":"Alex"}' \ --idempotency-key demo-001 --dry-run

Read the printed request. Check the path, the workspace and the body before you run it for real.

#What --idempotency-key does

An idempotency key tells the API that repeated requests with the same key are the same request. A retry after a timeout then does not create a duplicate.

Use it on retryable writes. The README example builds keys from the data, such as [email protected].

Never use it on api-keys create. And remember that blocked-domains add requires an idempotency key, and the CLI will not make one up for you.

#Build keys that mean something

Make keys from the thing being done, not random strings. import-2026-10-08-row-0042 tells you what the key protected.

A meaningful key helps in a log and prevents accidental reuse across different operations.

#--confirm for destructive commands

Destructive commands stop unless you pass --confirm. If you see destructive_confirmation_required, the guard worked, so read the command again.

contacts delete and other delete commands cannot be undone. Rehearse them with --dry-run, and keep them out of scripts that run unattended.

#Exit codes in scripts

A terminal job is only as good as its failure handling. The CLI gives you stable exit codes to branch on.

Exit codeMeaningWhat a script should do
0okcontinue
1runtime errorlog and decide per task
2usage errorstop, the command is wrong
3auth errorstop and alert: key bad or revoked
4not foundlog, an id may be wrong
5rate limitedwait, retry once, then log

The README notes that exit codes were redefined in version 0.1.1, so scripts written for the older scheme need migrating.

#A small wrapper

Wrap calls in a function that returns the code and writes failures to a log.

bash
fs() { firstsales "$@" --org "$ORG_ID" --workspace "$WORKSPACE_ID" --json } out=$(fs campaigns progress --campaign camp_123 2>>"$LOG") && rc=0 || rc=$? if [ "$rc" -ne 0 ]; then case $rc in 3) echo "auth failed" >> "$LOG"; exit 3 ;; 5) sleep 30; out=$(fs campaigns progress --campaign camp_123) ;; *) echo "failed rc=$rc" >> "$LOG" ;; esac fi

Errors come back as JSON that scripts can parse. The README lists stable error names such as insufficient_scope, missing_api_key, invalid_api_key and unsupported_operation.

#Treat unsupported as a stop

If the CLI returns unsupported_operation, stop and report that the capability is not public. Signals and webhooks fall in this group today.

Do not work around it by calling app-private routes. The README says only /api/v1 endpoints are supported.

#The escape hatch

For a public endpoint with no dedicated command, use firstsales api GET /api/v1/.... It stays on the public surface.

Check the local command registry first with firstsales commands --json, since the command may already exist.

#A weekly operating rhythm

Tools help most when they sit inside a rhythm. Here is a weekly cadence that splits the work between terminal and app.

graph TD
  M[Monday 07:00 cron: weekly report] --> M2[Manager reads diff]
  D[Daily 08:30: read-only standup] --> D2[Decide today's actions in the app]
  W[Wednesday: import and suppression scripts] --> W2[Review list, then attach to campaign]
  T[Thursday: launch window] --> T2[Readiness, test email, dry run, start]
  F[Friday: key and alert review] --> F2[Rotate or revoke, ack alerts]
  M2 --> D
  D2 --> W
  W2 --> T
  T2 --> F

#Daily: a read-only check

Each morning, run a short read-only check. The daily outbound standup prompt shows a version that ends in a decision list.

Without an agent, a few commands do the same job.

bash
firstsales whoami --json firstsales connectors list --org org_123 --workspace ws_123 --pretty firstsales alerts list --org org_123 --workspace ws_123 --pretty firstsales inbox threads --org org_123 --workspace ws_123 --pretty

Read connector status, alerts and new threads. Decide the day's actions, then act in the app.

#Weekly: a reproducible report

Once a week, a cron job saves snapshots and diffs them against the week before. Numbers come from the script, and a person reads the diff.

The full build is in weekly campaign reports with cron and the CLI.

#As needed: imports with a gate

When new leads arrive, check suppression before you import.

bash
firstsales suppression check [email protected] [email protected] \ --org org_123 --workspace ws_123 --json

The command accepts up to 100 values at a time. For imports, see import leads from the terminal, and verify addresses first, as covered in email verification before sending.

#Weekly: a key and alert review

Once a week, list keys, read usage and clear alerts.

bash
firstsales api-keys list --org org_123 --pretty firstsales usage get --org org_123 --pretty firstsales alerts list --org org_123 --workspace ws_123 --pretty

Revoke any key nobody can explain. Acknowledge or resolve alerts that are handled, using alerts ack and alerts resolve.

#Sender health

Check the sender side whenever bounces rise. connectors test tests a connector, and warmup status reads warmup state for a connector.

bash
firstsales connectors test --org org_123 --workspace ws_123 --connector conn_123 --pretty firstsales warmup status --org org_123 --workspace ws_123 --connector conn_123 --pretty

Connector responses never return provider secrets. For a bounce playbook, see cold email bounce rate and deliverability incident response.

Google's sender guidelines say to keep the spam rate reported in Postmaster Tools below 0.10% and avoid reaching 0.30%. The terminal cannot read that number, so check Postmaster Tools alongside your weekly diff.

#Launching a campaign from the terminal, safely

Launching is the step where the terminal's guards matter most. Follow the order every time.

#Step 1: create

Creating a campaign is a CLI command.

bash
firstsales campaigns create --org org_123 --workspace ws_123 --data-file campaign.json --dry-run

The README does not document the body of campaigns create, so check firstsales campaigns create --help and the Developer API docs for the fields. Preview with --dry-run, then create.

MCP campaign creation is not confirmed, so do not plan to create a campaign through an agent over MCP.

#Step 2: prepare

Attach lists and senders with campaigns workflow update. Preview first.

#Step 3: readiness

Run the readiness check.

bash
firstsales campaigns readiness --org org_123 --workspace ws_123 --campaign camp_123 --pretty

Read every line. The README does not document the extra flags, so use --help on your installed version. See the campaign readiness check from the CLI.

#Step 4: send a test email

bash
firstsales campaigns send-test-email --org org_123 --workspace ws_123 --campaign camp_123 --pretty

Open the test email as the recipient would. Check the subject, the first line, links and the unsubscribe path. The post on sending a test email with Claude Code covers what to look for.

#Step 5: preview the start

campaigns start needs a JSON body with savedVersionId, readinessVersion, idempotencyKey and confirmation.

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

Read the preview. Confirm the campaign id, the workspace and the body.

#Step 6: start, then verify

Run the start without --dry-run only when the preview is right. Then re-read progress.

bash
firstsales campaigns progress --org org_123 --workspace ws_123 --campaign camp_123 --pretty

The full sequence, with pause and resume, is in start, pause and resume campaigns from the CLI.

#Keep a stop within reach

Know how to pause before you start. campaigns pause and campaigns resume exist for this.

If bounces jump in the first hour, pause first and investigate after. A paused campaign costs a few hours, and a runaway one costs reputation.

#Working with a coding agent

A coding agent with a shell, such as Claude Code, can run the CLI for you. This is where the safety design earns its place.

#The package ships skills

The package includes skills for Claude Code named firstsales-cli, firstsales-product-mcp and firstsales-developer-api. The CLI skill gives the agent a short rulebook.

It says to verify identity first, read state before mutation, prefer JSON, use dry runs before risky writes, use idempotency keys only for retryable mutations, use --confirm only for intentional destructive commands, and re-read state afterwards.

It also says to stop on unsupported_operation, and not to invent commands. firstsales commands --json is the authority.

#Limit what the agent can do

The agent inherits your key. So give it a key sized to the task, and do not hand it your owner key.

Run it with a read-only key for investigation. Widen only for a specific write, then switch back.

The Claude Code headless mode runs a prompt non-interactively with claude -p, and lets you limit tools with --allowedTools. Use that to keep an unattended run narrow.

#Keep the key out of the prompt

Never paste a key into a prompt. Export the variable in the shell where the agent runs, or let a wrapper load it, and the agent will use the CLI without ever seeing the value.

#Untrusted text

Reply bodies and prospect data are written by strangers. An agent reading them can be steered by instructions hidden inside.

Keep write power away from an agent that reads replies. The MCP security for outbound and email agent guardrails posts explain the pattern.

#Good tasks for an agent in the terminal

Agents do well at reading and explaining. Ask for summaries of a report diff, a list of campaigns with rising bounces, or a draft of a script you then review.

They do less well at unsupervised writes. Ask for a plan and a dry-run preview, then run the real command yourself.

#Use the Copilot command for narration

firstsales copilot ask "<prompt>" runs the product's assistant non-interactively and prints only the reply text on standard output. It is safe to pipe.

Use it for a quick read of live state. For numbers that must match an archive, produce them with the CLI and narrate afterwards.

#Scripts worth writing first

If you write only a few scripts, write these, in this order.

#1. The identity guard

A tiny function that runs whoami and compares the organization and workspace to expected values. Source it at the top of every other script.

bash
guard() { out=$(firstsales whoami --json) || exit 3 echo "$out" | grep -q "$WORKSPACE_ID" || { echo "wrong workspace" >&2; exit 2; } }

The grep is a blunt check. Replace it with a jq comparison after you have seen the real output shape.

#2. The morning check

The read-only daily commands, saved to a dated file. It takes ten minutes to write and saves ten minutes each day.

#3. The weekly snapshot and diff

The cron job from the weekly report post. This is the highest-value script for most teams.

#4. The suppression gate

A script that takes a file of addresses, checks them in batches of 100, and writes the clear ones to a new file. Import only from the clear file.

bash
split -l 100 addresses.txt batch_ for f in batch_*; do firstsales suppression check $(cat "$f") --org "$ORG_ID" --workspace "$WORKSPACE_ID" --json done

Parse the output after checking its real shape. Never import an address whose check failed.

#5. The import with idempotency keys

A loop that creates contacts with keys built from the row. A rerun after a crash then does not duplicate anyone.

For incoming events, see start a campaign when a buying signal fires, which shows a receiver built from the same parts.

#Keep scripts boring

A script should do one thing, print what it did and exit with a meaningful code. Resist clever features, since you will read these scripts at the worst moments.

#Put scripts under review

Treat scripts like code. Keep them in version control without keys, review changes, and note which key each one uses.

#What to leave in the app

The terminal is not a better app. Some work is better in the app, and knowing which saves trouble.

#Judgment with context

Reading a thread next to the lead's history, comparing two sequences side by side, or deciding who deserves a personal note is faster on a screen.

#Setup you do rarely

The first mailbox, the first sending domain, a new workspace and billing changes are rare tasks. Clicking through teaches you what the pieces are.

#People and permissions

Inviting teammates, changing roles and approving access belong where the team is. The app gives everyone the same view.

#Anything the CLI marks unsupported

Signals and webhooks return unsupported_operation today. Use the app for them, and do not reach for private routes.

#Final approval of risky actions

Even when a script prepares a launch, let a person click the final start or read the final preview. The human gate is cheap, and the cost of a bad send is not.

An email sent by an API key also waits for Send Approval, according to the CLI documentation on emails send, schedule and draft. That is a backstop, and it should not replace your own gate.

#Mistakes that cost the most

These are the errors that show up most often when teams move work to the terminal.

#Acting in the wrong workspace

Fix: whoami first, explicit --org and --workspace flags, and an identity guard in scripts.

#Pasting a key into a prompt or a ticket

Fix: environment variables and secret managers only. If a key was pasted, revoke it at once and create a new one.

#Running a write without a dry run

Fix: preview every write the first time. A --dry-run costs a second and has no side effects.

#Putting deletes in unattended scripts

contacts delete and other delete commands cannot be undone. Fix: keep deletes manual, with a person reading each command.

#Scheduling a job with a key that dies

Keys fail closed if their creator loses organization access or is suspended. Fix: record creators in a key register and re-issue before someone leaves.

#Parsing fields you never looked at

Fix: read the real output with --pretty and use field-agnostic comparisons where you can.

#Starting without readiness and a test email

Fix: run campaigns readiness and campaigns send-test-email before every campaigns start, with no exceptions.

#Giving an agent a wide key

Fix: one narrow key per job, read-only by default.

#Ignoring exit codes

Fix: branch on codes 3 and 5 on purpose, and alert on failure. A job that fails silently is worse than none.

#A thirty-day adoption plan

If your team is new to this, move in stages. Each stage should produce something useful.

#Week 1: read only

Install the CLI, create read-only keys, and run whoami and doctor. Build the morning check script and use it daily.

Goal: everyone on the team can read their workspace from the terminal and trusts the output.

#Week 2: reports

Build the weekly snapshot and diff, schedule it with cron and add the dead-man's check. Send the first report to the team.

Goal: one report that people read, produced without anyone opening the app.

#Week 3: gated writes

Add the suppression gate and the import script with idempotency keys. Use --dry-run on every write the first time.

Goal: a lead import that is safe to rerun.

#Week 4: launch discipline

Practice the launch sequence on a test campaign: create, prepare, readiness, test email, dry-run start. Write the sequence as a checklist.

Goal: a launch that any team member can run the same way.

#After day 30

Review keys, prune unused ones and rotate old ones. Add an agent for reading and explaining, with a read-only key.

Revisit the choice of surface every quarter. See CLI, MCP or the app for the matrix.

#Measure whether it helps

Track two numbers: the hours spent each week on routine checks, and the count of avoidable mistakes such as wrong-workspace actions or skipped steps. If both fall, the terminal is paying off.

If routine work does not shrink after a month, simplify. You may have automated the wrong things, or built scripts nobody reads.

Pair the plan with an audit habit too. Outbound process audit and monitoring outbound agents give you ways to review how the whole system behaves.

#Comparing ways of working

This table compares three ways a team might run outbound hygiene.

Way of workingRepeatableLoggedScales to many campaignsSafe against slips
Click through the app each time✗✗✗✗
Ad hoc commands typed by whoever is free✗✗✓✗
Reviewed scripts with narrow keys, dry runs and exit-code handling✓✓✓✓
Reviewed scripts plus a read-only agent for explanation✓✓✓✓

The second row is where many technical teams sit. Typed commands are fast and flexible, and they leave no record and no checks.

The third row costs a few days of setup. It pays back the first time a script catches a wrong workspace or a skipped readiness step.

#Where the agent adds value

The fourth row adds an agent for explaining and exploring. It does not add any new write power.

That is the right shape. Let scripts do the work, and let the agent help you understand the results.

#FAQs

#Can I run outbound entirely from the terminal?

You can run much of it, such as checks, reports, imports and launches. Signals and webhooks are not supported by the CLI today, and judgment calls are better in the app.

#How do I install the FirstSales CLI?

Run npm install -g @firstsales.io/cli with Node 20 or later. The binary is named firstsales.

#How do I authenticate?

Set FIRSTSALES_API_KEY in your shell, pass --api-key, or use a saved profile. The CLI checks them in that order.

#Where do I get an API key?

Create one with firstsales api-keys create, using a scope list and access level that fit your task. The raw key prints once.

#How do I check which workspace I am in?

Run firstsales whoami --json. It prints the user, key, organization and workspace context without changing anything.

#What does doctor do?

It checks your setup. Run firstsales doctor --pretty when something behaves oddly.

#What is the safe loop for any change?

Verify identity, read state, plan the smallest change, preview with --dry-run, apply with an idempotency key where supported, and re-read to verify.

#What does --dry-run do?

It previews the HTTP request with redacted headers and the body, and makes no auth or network calls. It is safe to run anywhere.

#When do I need --confirm?

For destructive commands such as delete and revoke. The CLI stops without it, and you add it only when the action is intentional.

#Are delete commands reversible?

No. contacts delete and other delete commands cannot be undone, so keep them out of unattended scripts.

#What exit codes should my scripts handle?

0 ok, 1 runtime error, 2 usage error, 3 auth error, 4 not found and 5 rate limited. Alert on 3 and retry once on 5.

#How do I filter output?

Use --pretty to see the shape, then filter with jq. Use --output tsv for columns, and --all to fetch every page.

#Which commands should I run every morning?

whoami, connectors list, alerts list and inbox threads, all read-only. They show sender health, open alerts and new replies.

#What must I do before campaigns start?

Run campaigns readiness and campaigns send-test-email, read the test email, and preview the start with --dry-run. Do this every time.

#Can I create a campaign through MCP?

No, that is not confirmed. Create with firstsales campaigns create, then use MCP to edit and watch if you wish.

#Can I let a coding agent run these commands?

Yes, with a narrow key and the CLI skill's rules. Keep write power away from any agent that reads replies, and never paste a key into the prompt.

#What do I do when the CLI says unsupported_operation?

Stop and report that the capability is not public. Signals and webhooks are in this group today, and the README says not to call app-private routes.

#How often should I rotate keys?

Every 90 days is a reasonable start, and sooner for keys that can write. Create the new key and test it with whoami before you revoke the old one.

#What should I leave in the app?

Judgment with context, rare setup, billing, people and permissions, anything unsupported, and the final approval of risky actions.

#How do I start if my team is new to this?

Follow the thirty-day plan: read-only checks, then reports, then gated writes, then launch discipline. Each week produces something useful on its own.

#Conclusion

The terminal does not make outbound smarter. It makes the routine part consistent, so people can spend their attention on the parts that need it.

Here are the takeaways.

  • Run the boring half from the terminal: checks, reports, imports with gates, and previewed launches. Keep judgment in the app.
  • Follow the loop every time: identity, read, smallest change, dry run, apply, verify.
  • Use narrow keys, one per job, and never paste a key into a prompt or a post.
  • Branch on exit codes, alert on auth failures, and stop on unsupported_operation.
  • Run campaigns readiness and campaigns send-test-email before campaigns start, and keep delete commands manual because they cannot be undone.
  • Create campaigns with firstsales campaigns create, not through MCP, and let any agent explain results without gaining write power.

Start tomorrow with whoami, doctor and the morning check. By the end of the month you can have a weekly report, a safe import and a launch checklist that anyone on the team can follow.

To go deeper, read the daily outbound standup prompt, weekly campaign reports with cron and API keys for outbound agents. To run outbound on a platform built for agent-safe automation, try FirstSales.

Keep reading

Outreach that keeps going after you log off.Leads, emails, follow-ups and replies, handled.

Connect unlimited mailboxes and let AI agents find leads, write each email and follow up. Or send your own templates in manual mode. From $29/mo.

$1 for 3 days · Cancel anytime · No contract

Install with AI

Your coding agent sets up the FirstSales CLI and skills for you.

Prompt for Claude Code

I'm setting up FirstSales (https://firstsales.io), an AI outbound sales platform: campaigns, contacts, a reply inbox, deliverability and lead signals. Help me get my workspace sending real, good emails end to end, from Claude Code.

Read these first so you know how FirstSales works:
- Developer docs and API: https://developer.firstsales.io
- CLI: https://www.npmjs.com/package/@firstsales.io/cli
- Skills for coding agents: https://github.com/firstsalesio/firstsales-skills (read README.md and GUIDE.md)

Setup
1. Install the CLI: npm install -g @firstsales.io/cli
2. Install the FirstSales skills:
   claude plugin marketplace add firstsalesio/firstsales-skills
   claude plugin install firstsales@firstsales
3. Ask me if I already have a FirstSales account. If not, send me to https://app.firstsales.io to sign up.
4. Ask me to create an API key under Settings → API → API Keys, with only the scopes we need.
   Never ask me to paste the key into this chat. Tell me to run this in my own terminal, so the key stays hidden:
   read -rs FS_KEY && firstsales auth login --api-key "$FS_KEY"; unset FS_KEY
5. Run `firstsales whoami` and tell me which workspace and organisation we are connected to.

Then ask me about my business: what I sell, who I sell to (roles, company size, countries), my best customers, and what I use today for outbound (tools, lists, mailboxes). Walk me through:
1. Teaching FirstSales my business: website, offer, proof points and case studies in the knowledge base, so the AI writes like someone who knows the product.
2. Connecting my sending mailboxes and checking my sending domain: SPF, DKIM, DMARC and warm-up. If anything is unhealthy, tell me the exact DNS record to add.
3. Bringing in contacts: import my CSV or CRM export into a clean list, remove duplicates and bad emails, and block domains I must never email (customers, competitors, my own).
4. Building my first campaign: the right list, senders, steps and timing. Write the sequence with me, show me the drafts, and do not launch until I say yes.
5. Setting up Signals if it fits: find people engaging with LinkedIn posts about topics my buyers care about, and turn them into leads.
6. Inviting my team and giving each person the right access.

Once campaigns run, show me how to work the reply inbox from here: which replies need an answer, draft responses in my voice, and move good conversations into deals. If I use Cal.com, connect it so follow-ups can offer my booking link (not the first email).

Throughout, as you learn about my business, proactively suggest and help create:
- Reply templates for the questions and objections my prospects are likely to raise, based on my product, site and codebase if you can see them.
- Campaign ideas and segments worth testing, with the reason for each.

Safety rules:
- Show every change as a dry run (--dry-run) first and wait for my yes. Never send emails, launch campaigns or delete anything without my yes.
- Tell me before anything that spends credits, and how many.

Be opinionated. If the docs give a sensible default, just use it. Don't make me choose between five options.