# Running outbound from Claude Code

> Run cold email outreach from Claude Code with the AY Inbox MCP server or CLI: import leads, draft and check a campaign, triage replies and draft answers.

Source: https://docs.ayinbox.com/learn/outbound-from-claude-code/

Claude Code can run your outbound through AY Inbox in two ways: as an MCP server added with one command, or through the `ayinbox` CLI with the repository's `ayinbox-cli` skill. Claude can then import a list, draft a campaign, run preflight, read replies and draft answers, while starting a campaign or sending a reply stays a step you approve.

**Claude Code** is Anthropic's coding agent for the terminal. It runs shell commands and calls MCP tools, which is all AY Inbox needs: the MCP server gives it typed tools, and the CLI reaches every REST endpoint through its commands or `ayinbox api`.

Connect [#connect]

MCP with OAuth [#mcp-with-oauth]

```bash
claude mcp add --transport http ayinbox https://api.ayinbox.com/v1/mcp
```

Run `/mcp` inside Claude Code and pick **Authenticate**. A browser opens, you sign in, approve the scopes, and Claude holds a token it refreshes itself. The scopes it gets are the ones it asked for that your role covers. `SEND_CAMPAIGNS` is never one of them, so this connection can build and read but never starts mail.

To keep a smaller model focused, narrow the tool list: `https://api.ayinbox.com/v1/mcp?toolsets=inbox,campaigns,contacts`. The filter is tested with API keys; an OAuth client that ties its token to the exact server URL may not accept it. Prefer a static key? Add `--header "Authorization: Bearer aybox_..."` and see [MCP server](/api/mcp/) for scoping it.

The CLI [#the-cli]

```bash
curl -fsSL https://github.com/walidboulanouar/ayinbox/releases/latest/download/cli.sh | sh
ayinbox auth login
```

The installer needs no root and verifies the download against the published checksum. `ayinbox auth login` shows a code and a URL; you approve in the browser, and the approval creates one API key named for your machine. That step needs a person at a browser, so run it yourself, or set `AYINBOX_TOKEN` to a key in the environment.

Narrow what the key can do at sign-in. For example, a key that can build campaigns and draft replies but not start or send:

```bash
ayinbox auth login --scopes read_campaigns,write_campaigns,read_contacts,write_contacts,read_unibox,read_emails,read_analytics,ai_draft
```

Give it the playbook [#give-it-the-playbook]

The repository ships an `ayinbox-cli` skill under `skills/`: the command map, how to authenticate, which commands send, and the sending limits. Install it the way your agent expects, for example `cp -r skills/ayinbox-cli ~/.claude/skills/`.

The skill tells Claude to always pass `--json`. The CLI prints tables on a terminal and JSON when piped, and asking explicitly means the shape never depends on how Claude invoked it. Lists come back as `{"data": [...], "pagination": {...}}`, and `--all` walks every page. Failures print a machine-readable `code` and `request_id`, and exit code `4` always means the credential: missing, rejected or short a scope.

A first session, step by step [#a-first-session-step-by-step]

Ask Claude for the outcome ("set up a three-step campaign for the leads in `leads.csv`") and it will work through roughly this:

1. **Look around.** `ayinbox status --json` shows mailboxes needing attention, what is sending and what is unread. `ayinbox mailbox list --json` lists the senders.
2. **Create the campaign with its steps.** `ayinbox campaign create --input @campaign.json --idempotency-key q4-outbound-1`. A `steps` array in the body is connected in order at creation, and each follow-up waits its own `wait_after` days after the previous step (the first step defaults to `0`, follow-ups to `3`). A follow-up with no subject of its own is sent as a reply in the same thread.
3. **Pick the senders.** `ayinbox campaign set-senders CAMPAIGN_ID --input '{"senders":[{"email_account_id":"...","weight":1}]}'`.
4. **Import the contacts.** Claude turns the CSV into a JSON array and runs `ayinbox contact create --input @contacts.json --idempotency-key leads-q4-1`. `POST /contacts` takes an array, and each contact's `campaigns` field adds it to the new campaign. A large file with column mapping is better as a dashboard import.
5. **Check it.** `ayinbox campaign preflight CAMPAIGN_ID --json` runs every pre-send check without sending: missing senders, an empty audience, broken tracking.
6. **Read the plan.** `ayinbox campaign plan CAMPAIGN_ID --json` shows how many emails go out today and every limit that lowered the number.
7. **You start it.** `ayinbox campaign start CAMPAIGN_ID` in your own terminal, where it asks before sending, or **Start** in the dashboard.

Over MCP the same flow uses `create_campaign_draft`, `add_campaign_step`, `set_campaign_senders`, `add_contact`, a segment linked with `set_campaign_segments` while the campaign is still a draft, and `get_campaign` to check the result. The start is yours in the dashboard.

Working replies [#working-replies]

1. `ayinbox inbox list --unseen --json` lists new mail; `ayinbox inbox thread --thread-id THREAD_ID --json` reads one conversation.
2. `ayinbox inbox agent-drafts --json` lists the replies the [inbox agent](/guides/inbox-agent/) drafted and is holding for review. Each carries `grounding.flags`: links, figures or phrases the [campaign brain](/guides/campaign-brain/) does not back.
3. Over MCP, `generate_reply_draft` has the AI write a reply for one thread (it needs `AI_DRAFT`, charges [AI credits](/guides/ai-credits/) and never sends), and `draft_reply` checks text Claude wrote against the brain without calling a model.
4. Approving sends. `ayinbox inbox approve-draft DRAFT_ID` asks first, and a flagged draft answers `draft_flagged` until you read the flags and pass `--acknowledge-flags`. A flag is a reason to edit, not noise. `ayinbox inbox discard-draft DRAFT_ID` drops one.

Booking from replies [#booking-from-replies]

When a prospect asks for a call, [booking](/guides/booking/) offers times from your Cal.com or Calendly:

1. `ayinbox booking availability --thread THREAD_ID --json` reads open times in the prospect's timezone.
2. `ayinbox booking offer-create --thread THREAD_ID` prepares a block of times and sends nothing.
3. The block goes into a reply with `ayinbox inbox reply`, which asks before sending. The offer starts only when that reply is sent.
4. When the prospect picks, `ayinbox booking book OFFER_ID --slot SLOT_ID` books it. The provider invites the contact, so it asks like any send. `409 booking_new_reply` means a reply on the offer has not been read yet.

Over MCP, `get_availability` and `offer_meeting_times` work the same way. Booking itself is not an MCP tool.

Guardrails that stay on [#guardrails-that-stay-on]

* **`--yes` only for a send you asked for.** `campaign start`, `campaign test`, `placement test`, `placement batch-start`, `mailbox send`, `inbox reply`, `inbox compose`, `inbox approve-draft` and `booking book` ask first and refuse with no terminal. `--yes` is the only way past, which makes it the flag to look for when you review what Claude ran.
* **Caps stay where they are.** The default is 50 campaign emails per mailbox per day with 600 seconds between sends, and a fresh mailbox is better started around 10 to 20. Read `campaign plan` before touching a cap.
* **No `SEND_CAMPAIGNS` over OAuth.** An MCP client that registered itself never gets it, so it cannot start a campaign, link an audience to a sending one, or turn on an automation.
* **Text is data.** Replies, form answers and contact fields are written by people outside your workspace, and tool results arrive fenced as data. A model that reads mail belongs on a key from the **Agent: read and draft** preset.

Watching live [#watching-live]

```bash
ayinbox events tail --json --intent EMAIL --count 20
```

This streams the same live events the dashboard runs on, as JSON, and stops after 20. `--intent` matches the event type as a substring, so `EMAIL` takes `EMAIL_SENT`, `EMAIL_OPENED` and the rest. It needs a key with `REALTIME_SUBSCRIBE`. It is the quickest way to confirm a send went out or a reply arrived.

For the scope rules behind all of this, see [Cold email for AI agents](/learn/cold-email-for-ai-agents/). For a credential-by-credential walkthrough, see the [Agent quickstart](/api/agent-quickstart/), and for every command, the [CLI reference](/api/cli/).

## Frequently asked questions

### Can Claude Code send my cold emails on its own?

Not over MCP with an OAuth sign-in: no MCP tool sends mail, and that connection is never granted SEND_CAMPAIGNS, so it cannot start a campaign. Through the CLI, every command that sends asks first and refuses when there is no terminal to ask on, unless --yes is passed.

### Should I connect Claude Code through MCP or the ayinbox CLI?

MCP is one command and an OAuth sign-in, and gives Claude typed tools, none of which sends a message. The CLI reaches every endpoint, including starting a campaign and approving a draft, so it suits a person and Claude working in the same terminal.

### How do I sign Claude Code in to AY Inbox without a browser?

Set AYINBOX_TOKEN to an API key in the environment. It overrides the CLI's stored sign-in and is never written to disk. For MCP, add the server with an Authorization: Bearer header instead of the OAuth flow.

### Does this work with a self-hosted AY Inbox instance?

Yes. Sign the CLI in with ayinbox auth login --hostname and your instance's host, and point MCP at /v1/mcp on your own API host. The hosted service and a self-host run the same code.
