# Analytics

Source: https://molted.email/docs/analytics

Contact fatigue scores, send velocity, deliverability rates, segment membership, and outcome attribution.

The analytics API gives you real-time insight into contact engagement, sending patterns, and deliverability, plus outcome attribution to measure the impact of your campaigns.

With a mailbox-scoped key, send velocity, deliverability and policy decisions count only that key's mailboxes, and the suppression breakdown is refused; contact fatigue stays workspace-wide. On `api.molted.email`, pass `tenantId` in the query string (`mailbox.molted.email`, the CLI and the MCP server fill it in).

## Contact fatigue

Get a fatigue score (0–100) for a contact with a recommendation on whether to send.

```
GET https://mailbox.molted.email/v1/agent/analytics/contact-fatigue
```

```bash title="curl"
curl "https://mailbox.molted.email/v1/agent/analytics/contact-fatigue?contactEmail=user@example.com" \
  -H "Authorization: Bearer YOUR_API_KEY"
```

```bash title="CLI"
molted analytics fatigue --contact user@example.com
```

MCP: `fatigue_score` (`email`).

### Response

```json title="Response"
{
  "contactEmail": "user@example.com",
  "fatigueScore": 72,
  "factors": {
    "sendFrequency": 9,
    "bounceCount": 0,
    "complaintCount": 0,
    "replyRate": 0,
    "daysSinceLastEngagement": 41
  },
  "recommendation": "stop_sending"
}
```

`sendFrequency` is the number of sends in the last 7 days; the bounce, complaint and reply figures cover all time.

| Score range | Recommendation |
| - | - |
| 0–39 | `safe_to_send` -- contact is not fatigued. |
| 40–69 | `reduce_frequency` -- consider spacing out sends. |
| 70–100 | `stop_sending` -- contact is over-saturated. |

## Send velocity

Check your sending rate across different time windows, with an hourly trend breakdown for the last 24 hours.

```
GET https://mailbox.molted.email/v1/agent/analytics/send-velocity
```

```bash title="curl"
curl "https://mailbox.molted.email/v1/agent/analytics/send-velocity" \
  -H "Authorization: Bearer YOUR_API_KEY"
```

```bash title="CLI"
molted analytics velocity
```

MCP: `send_velocity` (no parameters).

### Response

```json title="Response"
{
  "tenantId": "tenant_abc123",
  "lastHour": 12,
  "last24Hours": 156,
  "last7Days": 892,
  "hourlyTrend": [
    { "hour": "2026-04-10 08:00:00+00", "count": 5 },
    { "hour": "2026-04-10 09:00:00+00", "count": 8 },
    { "hour": "2026-04-10 10:00:00+00", "count": 12 }
  ]
}
```

| Field | Type | Description |
| - | - | - |
| `tenantId` | `string` | Your tenant ID. |
| `lastHour` | `number` | Total sends accepted or sent in the last hour. |
| `last24Hours` | `number` | Total sends in the last 24 hours. |
| `last7Days` | `number` | Total sends in the last 7 days. |
| `hourlyTrend` | `array` | Hourly send counts for the last 24 hours. Each element has `hour` (timestamp string) and `count` (number). Empty array if no sends in the period. |

## Deliverability

Get delivery, bounce, and complaint rates for one time window.

```
GET https://mailbox.molted.email/v1/agent/analytics/deliverability
```

### Parameters

| Parameter | Type | Required | Description |
| - | - | - | - |
| `period` | `string` | No | `24h`, `7d` (default), or `30d`. |

```bash title="curl"
curl "https://mailbox.molted.email/v1/agent/analytics/deliverability?period=7d" \
  -H "Authorization: Bearer YOUR_API_KEY"
```

```bash title="CLI"
molted analytics deliverability --period 7d
```

MCP: `deliverability` (`period`: `24h`, `7d` or `30d`). The period isn't validated by the API: an unknown value returns zeros with `hasData: false`.

### Response

```json title="Response"
{
  "tenantId": "tenant_abc123",
  "period": "7d",
  "totalSent": 312,
  "delivered": 305,
  "bounced": 4,
  "complained": 1,
  "deliveryRate": 0.978,
  "bounceRate": 0.013,
  "complaintRate": 0.003,
  "hasData": true
}
```

`hasData` is `false` when `totalSent` is 0 for the period -- `deliveryRate`, `bounceRate`, and `complaintRate` are all 0 too, but that means nothing was sent, not a perfect score. The rates divide events that occurred in the window by sends created in the window, so a send from just before the window that is delivered inside it can push `deliveryRate` slightly above 1.

## Segment check

Check whether a contact is currently a member of a specific segment. This is a targeted, single-segment lookup -- useful as a pre-send gate (e.g. "only send if this contact is in the target segment").

To list all segments a contact belongs to instead, use [`segments contact`](https://molted.email/docs/segmentation.md#get-contact-segments).

```
GET https://mailbox.molted.email/v1/agent/analytics/segment-check
```

### Parameters

| Parameter | Type | Required | Description |
| - | - | - | - |
| `contactEmail` | `string` | Yes | The contact's email address. |
| `segmentId` | `string` | Yes | The segment ID to check membership against. |

```bash title="curl"
curl "https://mailbox.molted.email/v1/agent/analytics/segment-check?contactEmail=user@example.com&segmentId=SEGMENT_ID" \
  -H "Authorization: Bearer YOUR_API_KEY"
```

```bash title="CLI"
molted analytics segment-check --contact user@example.com --segment SEGMENT_ID
```

MCP: `check_membership` (`segment_id`, `contact_email`; in the opt-in `journeys` toolset).

### Response

```json title="Response"
{
  "isMember": true,
  "evaluatedLive": true
}
```

The filter always runs against live contact data for this request, so `evaluatedLive` is always `true`. A contact that doesn't exist returns `isMember: false`. If the segment's filter expression can't be evaluated (for example, because `filter_group` is malformed), the response is `isMember: false` with an `error` string. An unknown segment is `404 Segment not found: <id>`.

## Policy decision breakdown

See what the send policy allowed and blocked, and why, over a time window.

```
GET https://mailbox.molted.email/v1/agent/analytics/policy-decisions
```

```bash title="curl"
curl "https://mailbox.molted.email/v1/agent/analytics/policy-decisions?period=7d" \
  -H "Authorization: Bearer YOUR_API_KEY"
```

```bash title="CLI"
molted analytics policy-decisions --period 7d
```

### Response

```json title="Response"
{
  "period": "7d",
  "totalAllowed": 900,
  "totalBlocked": 42,
  "blockRate": 0.045,
  "hasData": true,
  "blocksByReason": [{ "reason": "hourly_limit_exceeded", "count": 20 }],
  "dailyTrend": [{ "day": "2026-04-10", "reason": "hourly_limit_exceeded", "count": 3 }]
}
```

`period` is `24h`, `7d` (default) or `30d`. There is no MCP tool for this breakdown (`check_send_health` reads the last 24 hours of it). `hasData` is `false` when there were no policy decisions in the period -- `blockRate` is 0 too, but that means no data, not "nothing was blocked".

## Suppression breakdown

Suppression growth and reason-code breakdown over a time window.

```
GET https://mailbox.molted.email/v1/agent/analytics/suppressions
```

```bash title="curl"
curl "https://mailbox.molted.email/v1/agent/analytics/suppressions?period=7d" \
  -H "Authorization: Bearer YOUR_API_KEY"
```

```bash title="CLI"
molted analytics suppressions --period 7d
```

### Response

```json title="Response"
{
  "period": "7d",
  "totalActive": 340,
  "suppressedDomains": 4,
  "byReasonCode": [{ "reasonCode": "hard_bounce", "count": 120 }],
  "dailyGrowth": [{ "day": "2026-04-10", "count": 5 }]
}
```

`period` is `24h`, `7d` (default) or `30d`. Refused for a mailbox-scoped key. No MCP tool. See [Suppressions](https://molted.email/docs/suppressions.md) to manage the underlying list.

---

## Attribution

Track outcomes and measure the impact of your email campaigns and journeys. The outcomes you ingest here also surface for the human in the portal, on `/app`'s Analytics page (an "Outcomes" section: revenue and outcome count for the selected range with the change from the previous range, by type, and by journey) -- account-wide, not scoped to one mailbox.

### Ingest outcomes

Record one conversion or outcome event tied to a contact per call.

```
POST https://mailbox.molted.email/v1/outcomes/ingest
```

```bash title="curl"
curl -X POST https://mailbox.molted.email/v1/outcomes/ingest \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "contactEmail": "user@example.com",
    "eventType": "deal_closed",
    "eventName": "Annual plan purchase",
    "revenue": 99.99,
    "occurredAt": "2026-03-01T15:30:00Z"
  }'
```

```bash title="CLI"
molted outcomes ingest --contact user@example.com --event-type deal_closed \
  --event-name "Annual plan purchase" --revenue 99.99
```

| Field | Type | Required | Description |
| - | - | - | - |
| `contactEmail` | `string` | Yes | The contact the outcome belongs to. |
| `eventType` | `string` | Yes | Any string. The MCP tool limits it to `activation`, `trial_conversion`, `meeting_booked`, `deal_closed`, `upsell` or `custom`. |
| `eventName` | `string` | Yes | A name for this outcome. |
| `revenue` | `number` | No | A decimal currency amount (e.g. `99.99`), not cents. |
| `metadata` | `object` | No | Anything you want to keep with it. |
| `occurredAt` | `string` | No | ISO timestamp, defaults to now. |

Returns `{ "outcomeEventId": "..." }`; attribution is computed asynchronously. Ingest needs write scope and refuses a mailbox-scoped key; every outcomes read refuses a mailbox-scoped key with `403 mailbox_scope_denied`. MCP: `record_outcome` (`contact_email`, `event_type`, `event_name`, `revenue`, `metadata`, `occurred_at`; in the opt-in `journeys` toolset).

### List outcomes

```
GET https://mailbox.molted.email/v1/outcomes
```

```bash title="curl"
curl "https://mailbox.molted.email/v1/outcomes?eventType=deal_closed" \
  -H "Authorization: Bearer YOUR_API_KEY"
```

```bash title="CLI"
molted outcomes list --event-type deal_closed --from 2026-03-01 --to 2026-03-31
```

Optional filters: `eventType`, `startDate` and `endDate` (ISO, matched on `occurred_at`). Returns at most 100 raw rows, newest `occurred_at` first.

### Get an outcome

```
GET https://mailbox.molted.email/v1/outcomes/:id
```

```bash title="CLI"
molted outcomes get OUTCOME_ID
```

Returns the outcome row plus `attributions`: one per model, each `{ id, outcome_event_id, model, touchpoints, created_at }`, where each touchpoint is `{ requestId, journeyId, journeyStepId, experimentId, variantId, sentAt, weight }`. An unknown id returns an empty `200`.

### Outcomes dashboard

```
GET https://mailbox.molted.email/v1/outcomes/dashboard
```

```bash title="CLI"
molted outcomes dashboard
```

Returns `{ totalOutcomes, totalRevenue, byType: [{ eventType, count, revenue }] }` for the last 30 days. MCP: `outcome_dashboard`.

### Journey impact

Measure the impact of your journeys on outcomes.

```
GET https://mailbox.molted.email/v1/outcomes/journey-impact
```

```bash title="curl"
curl "https://mailbox.molted.email/v1/outcomes/journey-impact" \
  -H "Authorization: Bearer YOUR_API_KEY"
```

```bash title="CLI"
molted outcomes journey-impact
```

Returns every stored impact snapshot, newest `computed_at` first, so a journey appears once per computation. Each row: `id, tenant_id, journey_id, journey_name, total_outcomes, total_revenue, attributed_outcomes, attributed_revenue, segment_breakdown, experiment_breakdown, computed_at, created_at` (revenue values come back as strings). There is no journey filter, and a `model` parameter is accepted but ignored: snapshots use linear attribution. MCP: `journey_impact`.

### Attribution models

Touchpoints are the sends (accepted or sent) to that contact in the 30 days before the outcome's `occurredAt`; opens and clicks don't count. All four models are computed for every outcome (see `GET /v1/outcomes/:id`); journey impact and the portal use `linear`.

| Model | Description |
| - | - |
| `first_touch` | 100% credit to the first send in the window. |
| `last_touch` | 100% credit to the last send before the outcome. |
| `linear` | Credit distributed equally across all sends in the window. |
| `time_decay` | More credit to later sends: a 7-day half-life counted back from the latest send. |

## The Analytics page in the portal

A person gets a summary of the same data at `/app/analytics` in the [portal](https://molted.email/docs/portal.md), from the numbers in the Home sentence or the **Analytics** entry in the command menu. It answers "how is it going?" one metric at a time and is meant to be glanced at; for anything more, use the API above.

- **Range.** The last 7, 30 or 90 days (7 by default). Each figure is compared with the period just before it of the same length.
- **Mailbox scope.** All mailboxes, or one chosen from the list. The heading says which. The range, metric and mailbox are in the address, so a link keeps them: a mailbox's own page links here with its mailbox already chosen.
- **The chart.** Tabs for **Sent**, **Replies** and **Bounce rate**, with the big number, the change against the previous period, a chart by day and a **Show as table** view. A day on which nothing was sent has no bounce rate, so the chart leaves a gap rather than drawing 0%.
- **Bounce health.** The bounce rate card carries a one-word verdict that turns amber, then red, as the rate climbs. It is a guide to look at, not the point where a mailbox pauses; those limits are in [Reputation](https://molted.email/docs/mailboxes.md#reputation).
- **Other cards.** Spam complaints (as a share of emails sent), unsubscribes, reply rate, and **Sent by agent**. Unsubscribes always count the whole workspace, because an unsubscribe is not tied to a mailbox, so they stay put when you pick one mailbox.
- **Plan usage.** How much of this month's send allowance you have used, for the whole workspace whichever mailbox you pick.
- **Outcomes.** The conversions your agents have recorded, when there are any.
