MOLTED EMAIL

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
curl
curl "https://mailbox.molted.email/v1/agent/analytics/contact-fatigue?contactEmail=user@example.com" \
  -H "Authorization: Bearer YOUR_API_KEY"
CLI
molted analytics fatigue --contact user@example.com

MCP: fatigue_score (email).

Response

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 rangeRecommendation
0–39safe_to_send -- contact is not fatigued.
40–69reduce_frequency -- consider spacing out sends.
70–100stop_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
curl
curl "https://mailbox.molted.email/v1/agent/analytics/send-velocity" \
  -H "Authorization: Bearer YOUR_API_KEY"
CLI
molted analytics velocity

MCP: send_velocity (no parameters).

Response

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 }
  ]
}
FieldTypeDescription
tenantIdstringYour tenant ID.
lastHournumberTotal sends accepted or sent in the last hour.
last24HoursnumberTotal sends in the last 24 hours.
last7DaysnumberTotal sends in the last 7 days.
hourlyTrendarrayHourly 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

ParameterTypeRequiredDescription
periodstringNo24h, 7d (default), or 30d.
curl
curl "https://mailbox.molted.email/v1/agent/analytics/deliverability?period=7d" \
  -H "Authorization: Bearer YOUR_API_KEY"
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

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-check

Parameters

ParameterTypeRequiredDescription
contactEmailstringYesThe contact's email address.
segmentIdstringYesThe segment ID to check membership against.
curl
curl "https://mailbox.molted.email/v1/agent/analytics/segment-check?contactEmail=user@example.com&segmentId=SEGMENT_ID" \
  -H "Authorization: Bearer YOUR_API_KEY"
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

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
curl
curl "https://mailbox.molted.email/v1/agent/analytics/policy-decisions?period=7d" \
  -H "Authorization: Bearer YOUR_API_KEY"
CLI
molted analytics policy-decisions --period 7d

Response

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
curl
curl "https://mailbox.molted.email/v1/agent/analytics/suppressions?period=7d" \
  -H "Authorization: Bearer YOUR_API_KEY"
CLI
molted analytics suppressions --period 7d

Response

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 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
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"
  }'
CLI
molted outcomes ingest --contact user@example.com --event-type deal_closed \
  --event-name "Annual plan purchase" --revenue 99.99
FieldTypeRequiredDescription
contactEmailstringYesThe contact the outcome belongs to.
eventTypestringYesAny string. The MCP tool limits it to activation, trial_conversion, meeting_booked, deal_closed, upsell or custom.
eventNamestringYesA name for this outcome.
revenuenumberNoA decimal currency amount (e.g. 99.99), not cents.
metadataobjectNoAnything you want to keep with it.
occurredAtstringNoISO 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
curl
curl "https://mailbox.molted.email/v1/outcomes?eventType=deal_closed" \
  -H "Authorization: Bearer YOUR_API_KEY"
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
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
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
curl
curl "https://mailbox.molted.email/v1/outcomes/journey-impact" \
  -H "Authorization: Bearer YOUR_API_KEY"
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.

ModelDescription
first_touch100% credit to the first send in the window.
last_touch100% credit to the last send before the outcome.
linearCredit distributed equally across all sends in the window.
time_decayMore 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.