# Activity on a legacy email campaign

Source: https://docs.emelia.io/docs/webhooks/legacy-email-activity

Guide: [Webhooks](https://docs.emelia.io/docs/webhooks.md)

Webhook event `emailCampaignActivity`: Emelia sends an HTTP POST request to your URL.

Sent to URLs registered with `type: email` (or without `type`).

- `SENT`: right after a sequence email is sent (test sends excluded). Network errors retried up to 5 times; the send waits for your endpoint.
- `OPENED`: tracking pixel loaded, at most once per email and contact, delivered 30 to 60 seconds later. Needs open tracking.
- `CLICKED`: tracked link clicked, at most once per link and email, delivered 30 to 60 seconds later; two clicks of the same contact on the same step queued within that delay can be merged into the last one. With open tracking on, a click is ignored until an open was recorded.
- `REPLIED`: first reply of the contact detected in the mailbox (later replies are not sent), with reply text, AI classification, sending mailbox and Message-ID. Network errors retried up to 5 times.
- `BOUNCED`: bounce detected (non delivery report or refusal at send time). No `campaign` field and `step` always 0. Network errors retried up to 5 times.
- `UNSUBSCRIBED`: the contact confirms on the unsubscribe page (or a one click unsubscribe POST is received); once per contact.
- `INTERESTED`: contact marked as interested by a user in the legacy web app (not through the REST API).
- `CAMPAIGN_ENDING`: accepted at registration, never sent for email campaigns.

**Delivery.** One `POST` per event and per registered URL, header `Content-Type: application/json`, JSON body, no signature or secret header, default User-Agent of the HTTP library. No timeout is set. Your response (status and body) is ignored: an HTTP error is never retried. Network errors are retried only for some events (see each list). No event ID, no ordering guarantee, no replay; a URL registered twice receives everything twice. Absent fields are omitted, never null.

## Payload

| Field | Type | Required | Description |
|---|---|---|---|
| `event` | enum: `SENT`, `OPENED`, `CLICKED`, `REPLIED`, `BOUNCED`, `UNSUBSCRIBED`, `INTERESTED`, `CAMPAIGN_ENDING` | yes | Events delivered for legacy email campaigns (`type: email`). `CAMPAIGN_ENDING` is accepted but never delivered for email campaigns in the current code. |
| `campaign` | string | no | Campaign name. Missing on BOUNCED (bug). |
| `campaignId` | string | no | REPLIED only. |
| `sender` | string | no | Address of the Emelia sending mailbox (the one that received the reply). REPLIED, OPENED and CLICKED only. |
| `senderName` | string | no | REPLIED only. |
| `contact` | object | yes | Contact of a legacy email campaign. Starts with every custom field of the contact (any key), then the standard fields, which win on name clashes. `company` is the value of the first custom field whose name contains `company` (case insensitive), absent otherwise. No contact ID. |
| `contact.firstName` | string | no |  |
| `contact.lastName` | string | no |  |
| `contact.phoneNumber` | string | no |  |
| `contact.email` | string (email) | no |  |
| `contact.company` | string | no |  |
| `date` | string (date-time) | yes | ISO 8601 UTC. Time at which Emelia processed the event: send time, detection time for replies (not the reply's own date for email), processing time 30 to 60 seconds after the open or click for OPENED and CLICKED. |
| `step` | integer | yes | 0 based index of the sequence step. Always 0 for BOUNCED and INTERESTED. Can be -1 when the step cannot be resolved. |
| `reply` | string | no | REPLIED only. Plain text of the reply (HTML when no text part), usually including the quoted thread. |
| `sentiment` | object | no | AI classification of a reply. Present only when reply classification is enabled on the account and AI credits remain. Classified by Jev, with Gemini as fallback; only the Gemini fallback can add `additionalData`. |
| `sentiment.classification` | enum: `INTERESTED`, `NOT_INTERESTED`, `NEUTRAL`, `OUT_OF_OFFICE`, `HOSTILE`, `DELEGATION`, `INCOMPREHENSION`, `QUESTION_ASK` | no |  |
| `sentiment.score` | number | no | Confidence of the classification. Range: 0 to 1. |
| `sentiment.additionalData` | object | no | Gemini fallback only: `delegationEmail` for DELEGATION, `returnDate` (DD/MM/YYYY) for OUT_OF_OFFICE. |
| `sentiment.additionalData.delegationEmail` | string | no |  |
| `sentiment.additionalData.returnDate` | string | no |  |
| `messageId` | string | no | REPLIED only. Message-ID of the reply, usable to answer in the same thread. |
| `link` | string | no | CLICKED only. Original URL of the clicked link. |

**SENT**

```json
{
  "event": "SENT",
  "campaign": "Acme Q4 outbound",
  "contact": {
    "companyName": "Acme",
    "industry": "Software",
    "firstName": "John",
    "lastName": "Doe",
    "phoneNumber": "+33612345678",
    "email": "john.doe@acme.com",
    "company": "Acme"
  },
  "date": "2026-10-12T08:15:02.123Z",
  "step": 0
}
```

**OPENED (sender = sending mailbox)**

```json
{
  "event": "OPENED",
  "campaign": "Acme Q4 outbound",
  "sender": "jane@acme-mail.com",
  "contact": {
    "companyName": "Acme",
    "industry": "Software",
    "firstName": "John",
    "lastName": "Doe",
    "phoneNumber": "+33612345678",
    "email": "john.doe@acme.com",
    "company": "Acme"
  },
  "date": "2026-10-12T09:01:45.000Z",
  "step": 0
}
```

**CLICKED**

```json
{
  "event": "CLICKED",
  "campaign": "Acme Q4 outbound",
  "sender": "jane@acme-mail.com",
  "contact": {
    "companyName": "Acme",
    "industry": "Software",
    "firstName": "John",
    "lastName": "Doe",
    "phoneNumber": "+33612345678",
    "email": "john.doe@acme.com",
    "company": "Acme"
  },
  "date": "2026-10-12T09:03:10.000Z",
  "step": 0,
  "link": "https://www.acme.com/pricing"
}
```

**REPLIED**

```json
{
  "event": "REPLIED",
  "campaign": "Acme Q4 outbound",
  "campaignId": "6650aa10b2c3d4e5f6071829",
  "sender": "jane@acme-mail.com",
  "senderName": "Jane Smith",
  "contact": {
    "companyName": "Acme",
    "industry": "Software",
    "firstName": "John",
    "lastName": "Doe",
    "phoneNumber": "+33612345678",
    "email": "john.doe@acme.com",
    "company": "Acme"
  },
  "date": "2026-10-13T10:20:00.000Z",
  "step": 1,
  "reply": "Hi Jane, sounds interesting. Can we talk on Tuesday?",
  "sentiment": {
    "classification": "INTERESTED",
    "score": 0.92
  },
  "messageId": "<CAF7x2y3z@mail.acme.com>"
}
```

**BOUNCED (no campaign name, step always 0)**

```json
{
  "event": "BOUNCED",
  "contact": {
    "companyName": "Acme",
    "industry": "Software",
    "firstName": "John",
    "lastName": "Doe",
    "phoneNumber": "+33612345678",
    "email": "john.doe@acme.com",
    "company": "Acme"
  },
  "date": "2026-10-12T08:16:30.000Z",
  "step": 0
}
```

**UNSUBSCRIBED**

```json
{
  "event": "UNSUBSCRIBED",
  "campaign": "Acme Q4 outbound",
  "contact": {
    "companyName": "Acme",
    "industry": "Software",
    "firstName": "John",
    "lastName": "Doe",
    "phoneNumber": "+33612345678",
    "email": "john.doe@acme.com",
    "company": "Acme"
  },
  "date": "2026-10-12T11:00:00.000Z",
  "step": 0
}
```

**INTERESTED (marked by a user)**

```json
{
  "event": "INTERESTED",
  "campaign": "Acme Q4 outbound",
  "contact": {
    "companyName": "Acme",
    "industry": "Software",
    "firstName": "John",
    "lastName": "Doe",
    "phoneNumber": "+33612345678",
    "email": "john.doe@acme.com",
    "company": "Acme"
  },
  "date": "2026-10-13T14:00:00.000Z",
  "step": 0
}
```
