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-fatiguecurl "https://mailbox.molted.email/v1/agent/analytics/contact-fatigue?contactEmail=user@example.com" \
-H "Authorization: Bearer YOUR_API_KEY"molted analytics fatigue --contact user@example.comMCP: fatigue_score (email).
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-velocitycurl "https://mailbox.molted.email/v1/agent/analytics/send-velocity" \
-H "Authorization: Bearer YOUR_API_KEY"molted analytics velocityMCP: send_velocity (no parameters).
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/deliverabilityParameters
| Parameter | Type | Required | Description |
|---|---|---|---|
period | string | No | 24h, 7d (default), or 30d. |
curl "https://mailbox.molted.email/v1/agent/analytics/deliverability?period=7d" \
-H "Authorization: Bearer YOUR_API_KEY"molted analytics deliverability --period 7dMCP: deliverability (period: 24h, 7d or 30d). The period isn't validated by the API: an unknown value returns zeros with hasData: false.
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.
GET https://mailbox.molted.email/v1/agent/analytics/segment-checkParameters
| Parameter | Type | Required | Description |
|---|---|---|---|
contactEmail | string | Yes | The contact's email address. |
segmentId | string | Yes | The segment ID to check membership against. |
curl "https://mailbox.molted.email/v1/agent/analytics/segment-check?contactEmail=user@example.com&segmentId=SEGMENT_ID" \
-H "Authorization: Bearer YOUR_API_KEY"molted analytics segment-check --contact user@example.com --segment SEGMENT_IDMCP: check_membership (segment_id, contact_email; in the opt-in journeys toolset).
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-decisionscurl "https://mailbox.molted.email/v1/agent/analytics/policy-decisions?period=7d" \
-H "Authorization: Bearer YOUR_API_KEY"molted analytics policy-decisions --period 7dResponse
{
"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/suppressionscurl "https://mailbox.molted.email/v1/agent/analytics/suppressions?period=7d" \
-H "Authorization: Bearer YOUR_API_KEY"molted analytics suppressions --period 7dResponse
{
"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 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/ingestcurl -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"
}'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/outcomescurl "https://mailbox.molted.email/v1/outcomes?eventType=deal_closed" \
-H "Authorization: Bearer YOUR_API_KEY"molted outcomes list --event-type deal_closed --from 2026-03-01 --to 2026-03-31Optional 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/:idmolted outcomes get OUTCOME_IDReturns 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/dashboardmolted outcomes dashboardReturns { 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-impactcurl "https://mailbox.molted.email/v1/outcomes/journey-impact" \
-H "Authorization: Bearer YOUR_API_KEY"molted outcomes journey-impactReturns 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, 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.
- 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.