> ## Documentation Index
> Fetch the complete documentation index at: https://docs.agent-loadout.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Run Email Outreach Campaigns with Smart Follow-ups

> Send personal first contacts from the agent's own address, follow up only on real replies, and track bounces and complaints automatically.

A marketing agent sends personal first contacts from its own dedicated address, waits for real replies, and follows up only when the answer calls for it. Because the agent has its own inbox, an out-of-office reply or a bounce changes the plan rather than disappearing into a void. The CRM API key it uses to log results lives in the vault, not in a prompt or environment variable.

## What you need

An agent equipped with an inbox and a token that has the following capabilities:

* **email:read** — to watch for replies, bounces, and complaints
* **email:send** — to send first contacts and follow-ups
* **vault:use** — to read the CRM API key without exposing it

The workspace overview shows deliveries against your plan's recipient limits, so a campaign never runs blind.

## Steps

<Steps>
  ### Send with a label and an idempotency key

  Call `send_message` for each first contact. Include a `labels` array (e.g. `["campaign:launch"]`) to group the campaign's conversations, and set an `idempotency_key` (e.g. `launch-{contact.email}`) so a retry never sends twice.

  ### Watch the thread and check the screening verdict

  Subscribe to `thread.updated` and `message.received` events to see which conversations received a reply. Bounces and complaints arrive as events too, so the agent can remove those contacts from follow-up without a human having to notice. When reading any inbound reply, inspect `screening.verdict` before acting on its content — a suspicious verdict means the message should be surfaced to a human rather than used as a follow-up signal.

  ### Follow up in the same conversation

  Call `reply_to_message` to keep the correct threading headers intact and the conversation in one place. Only follow up when there is a positive signal from the recipient — a timer alone is not sufficient justification for a second message.

  ### Update the CRM

  Store the CRM API key as a vault credential. The agent calls `get_credential` (requires `vault:use`) to retrieve it when needed and never pastes it into a chat or log. Every credential read is recorded for your organization.
</Steps>

## Chat prompt example

Use this prompt in any chat client with Agent Loadout connected:

```text Chat prompt theme={null}
Send a short personal note to the five contacts in the attached CSV from your own
address, label the conversations campaign:launch, and tell me tomorrow who replied
and what they said.
```

## TypeScript example

```typescript outreach.ts theme={null}
import { AgentLoadout } from "@agent-loadout/sdk";

const agent = new AgentLoadout({ apiKey: process.env.AGENT_LOADOUT_TOKEN! });
const [inbox] = await agent.inboxes.list();

for (const contact of contacts) {
  await agent.messages.send(inbox.id, {
    to: [`${contact.name} <${contact.email}>`],
    subject: `Quick question about ${contact.company}`,
    text: draft(contact),
    labels: ["campaign:launch"],
    idempotency_key: `launch-${contact.email}`,
  });
}

// Later: who answered?
const { threads } = await agent.threads.list(inbox.id, { label: "campaign:launch", unread_only: true });
for (const thread of threads) {
  const { messages } = await agent.threads.get(thread.id, { limit: 1 });
  console.log(thread.subject, messages[0]?.snippet);
}
```

## Required permissions

| Capability   | Why it's needed                            |
| ------------ | ------------------------------------------ |
| `email:read` | Watch for replies, bounces, and complaints |
| `email:send` | Send first contacts and follow-up replies  |
| `vault:use`  | Read the CRM API key without exposing it   |

<Note>
  Keep the same `idempotency_key` when retrying a send. Choose a new key only when you intend a genuinely new message. Sending follows the plan's recipient limits and sender trust settings; the dashboard shows deliveries, bounces, and complaints per conversation.
</Note>
