# Replies

> Get the replies of your Emelia campaigns as they come, read them in batches or as a CSV, see what was sent, and answer from the right mailbox.

Source: https://docs.emelia.io/docs/replies

Replies reach you in several ways. Pick by need:

| Need                                                    | Use                                                                                     |
| ------------------------------------------------------- | --------------------------------------------------------------------------------------- |
| React the moment someone answers (CRM, Slack, routing)  | [Webhooks](https://docs.emelia.io/docs/webhooks.md) on `MAIL_REPLIED`, `LINKEDIN_REPLIED` and `WHATSAPP_REPLIED` |
| Process replies in batches, every hour or every morning | The activity feed of each campaign, below                                               |
| Every reply of a campaign in one file                   | [Export as CSV](https://docs.emelia.io/docs/campaigns/export-as-csv.md) with `type=replies`                      |

The Python examples use one session:

```python title="Python setup"
import os
import requests

session = requests.Session()
session.headers["Authorization"] = os.environ["EMELIA_API_KEY"]
```

## Read the replies of a campaign

[`GET /advanced/campaigns/{campaignId}/activities`](https://docs.emelia.io/docs/replies/list-campaign-activities.md)

The feed returns 30 activities per page, newest first, with `page` starting at `0`. An empty `activities` array means you reached the end. Pass `types=MAIL_REPLIED,LINKEDIN_REPLIED,WHATSAPP_REPLIED` (comma separated) to get the replies of every channel in one pass.

**curl**

```bash
curl "https://api.emelia.io/advanced/campaigns/6650f1a2b839d80011c5a0f1/activities?types=MAIL_REPLIED,LINKEDIN_REPLIED&page=0" \
  -H "Authorization: $EMELIA_API_KEY"
```

**JavaScript**

```js
async function replies(campaignId) {
  const all = [];
  for (let page = 0; ; page++) {
    const url = `https://api.emelia.io/advanced/campaigns/${campaignId}/activities?types=MAIL_REPLIED,LINKEDIN_REPLIED&page=${page}`;
    const res = await fetch(url, { headers: { Authorization: process.env.EMELIA_API_KEY } });
    const { activities } = await res.json();
    if (!activities?.length) return all;
    all.push(...activities);
  }
}
```

**Python**

```python
def replies(campaign_id):
    found, page = [], 0
    while True:
        res = session.get(
            f"https://api.emelia.io/advanced/campaigns/{campaign_id}/activities",
            params={"types": "MAIL_REPLIED,LINKEDIN_REPLIED", "page": page},
        ).json()
        if not res.get("activities"):
            return found
        found += res["activities"]
        page += 1
```

A reply looks like this:

```json
{
  "_id": "6650f1a2b839d80011c5e001",
  "event": "MAIL_REPLIED",
  "date": "2026-10-08T09:12:00.000Z",
  "step": 2,
  "version": 0,
  "contact": {
    "_id": "6650f1a2b839d80011c5d001",
    "email": "claire.fontaine@nexora.io",
    "firstName": "Claire",
    "lastName": "Fontaine"
  },
  "customData": {},
  "reply": {
    "subject": "Re: Quick question about Nexora",
    "text": "Sounds good, call me Tuesday.",
    "content": "<p>Sounds good, call me Tuesday.</p>",
    "senderName": "Claire Fontaine"
  }
}
```

* `reply.text` is the plain text, `reply.content` the HTML.
* Filter one contact with `contactId`, or search by name, email or LinkedIn URL with `search`.
* The feed is cached for up to 10 minutes: a reply that just arrived can take a few minutes to show here. [Webhooks](https://docs.emelia.io/docs/webhooks.md) are immediate.

## See what was sent

To show a reply next to the message that triggered it, rebuild the content of a send activity (`SENT`, `MESSAGE_SENT`, `INMAIL_SENT`, `INVITED` or `WHATSAPP_MESSAGE_SENT`):

[`GET /advanced/campaigns/{campaignId}/activities/{activityId}/message`](https://docs.emelia.io/docs/replies/get-sent-message.md)

**curl**

```bash
curl https://api.emelia.io/advanced/campaigns/6650f1a2b839d80011c5a0f1/activities/6650f1a2b839d80011c5e002/message \
  -H "Authorization: $EMELIA_API_KEY"
```

**JavaScript**

```js
const res = await fetch(
  'https://api.emelia.io/advanced/campaigns/6650f1a2b839d80011c5a0f1/activities/6650f1a2b839d80011c5e002/message',
  { headers: { Authorization: process.env.EMELIA_API_KEY } },
);
const { channel, subject, message } = await res.json();
```

**Python**

```python
res = session.get(
    "https://api.emelia.io/advanced/campaigns/6650f1a2b839d80011c5a0f1/activities/6650f1a2b839d80011c5e002/message"
)
print(res.json())  # {"channel": "EMAIL", "subject": "...", "message": "<p>...</p>", "attachments": []}
```

The content is not stored: it is rebuilt from the step and the contact, so it can differ if either changed since. This route answers `404 {"error": "NOT_FOUND"}` or `422 {"error": "NO_MESSAGE"}` instead of the usual format.

## Answer an email reply

[`POST /emails/reply`](https://docs.emelia.io/docs/replies/send-reply.md)

This route sends an email from one of your connected mailboxes, whatever the campaign. It comes from the previous system and is marked deprecated in the reference, but it is the route to answer a reply by API today.

| Field                         | Description                                                                                                                                                                    |
| ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `content`                     | Required. The HTML of your answer. Use `<br>` or `<p>` for line breaks.                                                                                                        |
| `providerId` or `senderEmail` | Required, one of the two: the mailbox that sends. `providerId` is the `_id` from [List email providers](https://docs.emelia.io/docs/email-providers/list-email-providers.md).                           |
| `messageId`                   | The Message-ID of the email you answer. With it, your answer is threaded, with the quoted history. If Emelia cannot find it, the call fails with "Original message not found". |
| `to`, `subject`               | Required without `messageId`.                                                                                                                                                  |
| `cc`, `bcc`, `attachments`    | Optional. `attachments` is a list of `{ "name", "url" }`.                                                                                                                      |
| `scheduledAt`                 | Optional ISO date: schedules the send instead of sending now.                                                                                                                  |

**curl**

```bash
curl -X POST https://api.emelia.io/emails/reply \
  -H "Authorization: $EMELIA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "senderEmail": "marie.martin@yourcompany.com",
    "to": ["claire.fontaine@nexora.io"],
    "subject": "Re: Quick question about Nexora",
    "content": "Hi Claire,<br><br>Tuesday 10am works. I just sent the invite.<br><br>Marie"
  }'
```

**JavaScript**

```js
await fetch('https://api.emelia.io/emails/reply', {
  method: 'POST',
  headers: { Authorization: process.env.EMELIA_API_KEY, 'Content-Type': 'application/json' },
  body: JSON.stringify({
    senderEmail: 'marie.martin@yourcompany.com',
    to: ['claire.fontaine@nexora.io'],
    subject: 'Re: Quick question about Nexora',
    content: 'Hi Claire,<br><br>Tuesday 10am works. I just sent the invite.<br><br>Marie',
  }),
});
```

**Python**

```python
session.post("https://api.emelia.io/emails/reply", json={
    "senderEmail": "marie.martin@yourcompany.com",
    "to": ["claire.fontaine@nexora.io"],
    "subject": "Re: Quick question about Nexora",
    "content": "Hi Claire,<br><br>Tuesday 10am works. I just sent the invite.<br><br>Marie",
})
```

Answer from the mailbox that received the reply. The `MAIL_REPLIED` [webhook](https://docs.emelia.io/docs/webhooks.md) gives it (`sender`) along with the `messageId` of the reply.

## Stop a sequence after a reply

By default, a reply by email, LinkedIn or WhatsApp ends the sequence for that contact (`eventToStop` in the [settings](https://docs.emelia.io/docs/campaigns.md#3-set-the-schedule)). Someone who asks not to be contacted again belongs in the [blacklist](https://docs.emelia.io/docs/blacklist.md): it protects all your future campaigns too.
