# Custom payload configuration

> Choose exactly which fields your webhook receives — reduce overhead, limit PII exposure, and match endpoint requirements

Source: https://emailconnect.eu/help/custom-payload/

You can configure exactly which fields are included in your webhook payload, on a per-alias basis. Strip what you don't need, keep what you do.

There are two separate mechanisms, and they are gated differently:

| | What it does | Plan |
|---|---|---|
| **Field toggles** | Switch whole blocks of the standard payload on or off — HTML body, text body, envelope, attachments, Markdown, reply parsing | **Every plan, including Free** |
| **Payload templates** | Define your *own* JSON shape with `{{path}}` placeholders, so the payload matches your endpoint's schema exactly | **Business+** |

Field toggles reshape our payload. Templates replace it.

## Why customise your payload

### Minimise PII exposure

Every field you forward is a field that could end up in logs, error reports, or third-party systems. If your pipeline only needs the sender address and subject line to route a support ticket, there is no reason to forward the full email body.

Stripping unnecessary fields reduces your data footprint and simplifies GDPR compliance — you can't leak data you never received.

### Reduce overhead

Smaller payloads mean faster webhook delivery, lower bandwidth, and less processing on your end. At high volumes the difference adds up: a metadata-only payload can be an order of magnitude smaller than one carrying the full HTML body and inline attachments.

### Match rigid endpoints

Some webhook receivers — Zapier, Make, or internal microservices — expect a specific schema. If the endpoint has no flexibility for extra fields, a trimmed payload avoids parsing errors and simplifies your integration.

### Compliance and audit

Regulated industries often require data minimisation by design. Custom payloads let you enforce this at the source: forward only the metadata you need, and leave everything else behind.

## Field toggles — available on every plan

In your alias settings, switch these blocks on or off. **All of them work on every plan, Free included.**

| Toggle | What it controls | Default |
|---|---|---|
| `includeText` | `message.content.text` — the plain-text body | **On** |
| `includeHtml` | `message.content.html` — the raw HTML body | **On** |
| `allowAttachments` | `message.attachments[]` — file metadata plus inline content or a `downloadUrl` | **On** |
| `includeEnvelope` | The whole `envelope` block — headers, `allRecipients`, `returnPath`, `processed` | **Off** |
| `includeMarkdown` | `message.content.markdown` — HTML converted to clean Markdown | **Off** (and Maker+) |
| `includeReplyParsing` | `message.content.reply` — the new text of a reply, with the quoted thread stripped | **Off** (and Maker+) |

Two things to know about the defaults:

- **`includeEnvelope` is off by default, and turning it off does not remove the envelope entirely** — it is reduced to `{ messageId }`. `messageId` always survives, so the sender's Message-ID is there whatever the setting. To deduplicate, or to name an email in an API call such as `DELETE /api/v1/emails/{messageId}`, the `webhook-id` header (`msg_…`) is the better key: we assign it, and it is unique per email. A template can put the same value in the body with `{{id}}`.
- **`includeMarkdown` and `includeReplyParsing` are off by default *and* gated at Maker+.** Turning the toggle on is necessary but not sufficient — on Free the field is stripped regardless.

Blocks the toggles do **not** control: `message.sender`, `message.recipient` (including `tag`), `message.subject`, `message.date`, `message.content.links`, `classification`, `spam`, `integrity`, `security`, `domainId` and `aliasId`. These are governed by your plan, not by a per-alias switch. To drop them from the payload, use a template.

## Payload templates — Business+

A template is a JSON object you define, in which any string of the form `{{path}}` is replaced with a value from the email. The rendered object *becomes* the webhook body — nothing else is sent.

```json
{
  "ticket": {
    "from": "{{message.sender.email}}",
    "queue": "{{message.recipient.tag}}",
    "title": "{{message.subject}}",
    "body": "{{message.content.text}}"
  },
  "received_at": "{{envelope.processed.timestamp}}",
  "source": "email"
}
```

### Two kinds of reference

- **Bare reference** — the string is *exactly* `"{{path}}"` and nothing else. The value keeps its original type: `"{{spam.score}}"` renders as the number `-0.99`, not the string `"-0.99"`, and `"{{message.attachments}}"` renders as a real JSON array.
- **Interpolation** — a `{{path}}` embedded in a longer string, e.g. `"[{{classification.type}}] {{message.subject}}"`. The result is always a string.

Values that contain no `{{` at all are passed through as literals, so you can hard-code constants like `"source": "email"` above.

### Missing values

- A **bare** reference that resolves to nothing has its key **omitted** from the output entirely — you get no key, rather than a `null`.
- An **interpolated** reference that resolves to nothing becomes an **empty string**.

Design your endpoint to tolerate a missing key: a plan-gated field (`spam.score` on Free) or an unset one (`message.recipient.tag` on untagged mail) will simply not appear.

### The `system.*` context

Beyond the email itself, templates can address a `system` object describing the delivery. These fields exist **only inside templates** — they are not part of the standard payload.

| Path | Type | What it is |
|---|---|---|
| `system.event` | string | The event that triggered the delivery |
| `system.timestamp` | string | Delivery time, ISO 8601 |
| `system.timestampUnix` | number | The same instant as a Unix timestamp |
| `system.alias.email` | string | The alias address that matched |
| `system.alias.name` | string | The alias's display name |
| `system.domain.name` | string | The domain that received the email |
| `system.webhook.name` | string | The webhook's display name |
| `system.webhook.id` | string | The webhook's ID |

These are what you reach for when your endpoint needs to know *which* of your aliases or webhooks fired, without inferring it from the recipient address:

```json
{
  "event": "{{system.event}}",
  "at": "{{system.timestampUnix}}",
  "routed_by": "{{system.alias.email}}",
  "webhook": "{{system.webhook.id}}",
  "subject": "{{message.subject}}"
}
```

### Addressable paths

Templates are validated when you save them: an unknown path is a hard error, and a plan-gated path is a warning. The addressable paths are:

**`id`** — the identifier of the email on EmailConnect (`msg_…`), the same value as the `webhook-id` header. A templated body only contains it if your template names it.

**`message`** — `message.sender.email`, `message.sender.name`, `message.recipient.email`, `message.recipient.name`, `message.recipient.tag`, `message.subject`, `message.date`, `message.content.text`, `message.content.html`, `message.content.links`, `message.attachments`
· *Maker+*: `message.content.markdown`, `message.content.reply`, `message.content.links.anchorText`, `message.content.links.source`, `message.attachments.metadata`
· *Business+*: `message.attachments.virusScan`

**`envelope`** — `envelope.messageId`, `envelope.xMailer`, `envelope.xOriginalTo`, `envelope.returnPath`, `envelope.allRecipients`, `envelope.headers`, `envelope.processed.timestamp`, `envelope.processed.domain`, `envelope.processed.alias`, `envelope.processed.originalSize`

**`spam`** *(Maker+)* — `spam.score`, `spam.engine`, `spam.symbols`, `spam.authentication`, `spam.report`. There is deliberately **no `spam.isSpam`** — the cut-off is your policy, not ours, so compare `spam.score` against your own threshold.

**`classification`** — `classification.type`, `classification.confidence`, `classification.signals`. All three are available on **every plan**.

**`integrity`** *(Maker+)* — `integrity.contentHash`, `integrity.rawEmailHash`

**`security`** *(Business+)* — `security.virusScan`

**`routing`** — `domainId`, `aliasId`

**`system`** — as listed above.

You can address a parent path to pull the whole object: `"{{spam.authentication}}"` returns the full DKIM/SPF/DMARC object as a bare reference.

## Example: metadata-only payload (template)

A support ticketing system that only needs routing information. The template:

```json
{
  "message": {
    "sender": {
      "name": "{{message.sender.name}}",
      "email": "{{message.sender.email}}"
    },
    "recipient": {
      "email": "{{message.recipient.email}}",
      "tag": "{{message.recipient.tag}}"
    },
    "subject": "{{message.subject}}"
  }
}
```

…and what your endpoint receives:

```json
{
  "message": {
    "sender": {
      "name": "Alice Martin",
      "email": "alice@acme.com"
    },
    "recipient": {
      "email": "support+billing@yourcompany.com",
      "tag": "billing"
    },
    "subject": "Order #1042 — delivery question"
  }
}
```

No email body, no attachments, no HTML. Just enough to create a ticket and notify the right team.

Because this email was addressed to `support+billing@`, `recipient.tag` arrives as `"billing"` — enough to drop the ticket straight into the right queue without parsing the address or reading the body. Plus-addressed mail is still delivered to the webhook on your `support@` alias, so a tag costs you nothing to set up.

## Example: attachments only (template)

A document processing pipeline that extracts invoices from emails. `"{{message.attachments}}"` is a bare reference, so the array arrives as a real JSON array rather than a stringified one:

```json
{
  "message": {
    "sender": { "email": "{{message.sender.email}}" },
    "subject": "{{message.subject}}",
    "attachments": "{{message.attachments}}"
  }
}
```

Delivered as:

```json
{
  "message": {
    "sender": {
      "email": "billing@vendor.com"
    },
    "subject": "Invoice #4821",
    "attachments": [
      {
        "filename": "invoice-4821.pdf",
        "contentType": "application/pdf",
        "size": 48210,
        "downloadUrl": "https://app.emailconnect.eu/attachments/.../download"
      }
    ]
  }
}
```

The email body is irrelevant — only the PDF matters.

## Availability

| | Plan |
|---|---|
| **Field toggles** (`includeText`, `includeHtml`, `allowAttachments`, `includeEnvelope`, `includeMarkdown`, `includeReplyParsing`) | **All plans**, including Free |
| **Payload templates** (`{{path}}` syntax and the visual payload builder) | **Business** and **Platform** |

Two caveats on the toggles, since they trip people up:

- Turning a toggle **on** does not defeat a plan gate. `includeMarkdown` and `includeReplyParsing` still require **Maker+**; on Free the field is stripped even with the toggle enabled.
- The defaults are not "everything on". `includeEnvelope`, `includeMarkdown` and `includeReplyParsing` all start **off** — if you expected `envelope.headers` or `message.content.markdown` and they are not in your payload, that is why.

## Related topics

- [Alias options explained](https://emailconnect.eu/help/alias-options-explained/) — all alias configuration options
- [Webhook payload reference](https://emailconnect.eu/help/webhook-payload-reference/) — full payload field documentation
- [Data retention](https://emailconnect.eu/help/data-retention/) — how long data is stored
