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
bashnpm install -g @firstsales.io/cli firstsales --help
Check the version after install, since the CLI moves quickly.
bashfirstsales --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.
bashfirstsales 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.
bashexport 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
bashfirstsales 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
bashfirstsales 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.
bashfirstsales 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.
bashfirstsales 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.
bashalias 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.
bashmkdir -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.
bashfirstsales 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 code | Meaning | What a script should do |
|---|---|---|
| 0 | ok | continue |
| 1 | runtime error | log and decide per task |
| 2 | usage error | stop, the command is wrong |
| 3 | auth error | stop and alert: key bad or revoked |
| 4 | not found | log, an id may be wrong |
| 5 | rate limited | wait, 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.
bashfs() { 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.
bashfirstsales 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.
bashfirstsales 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.
bashfirstsales 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.
bashfirstsales 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.
bashfirstsales 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.
bashfirstsales 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
bashfirstsales 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.
bashfirstsales 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.
bashfirstsales 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.
bashguard() { 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.
bashsplit -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 working | Repeatable | Logged | Scales to many campaigns | Safe 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 readinessandcampaigns send-test-emailbeforecampaigns 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.