PostHog delivery events
Which analytics events Outreach emits, when they fire, and what properties they carry. Used by campaign stats and auto-created PostHog dashboards.
Capture is a no-op unless
POSTHOG_HOST and POSTHOG_PROJECT_API_KEY are set, and the subscriber has segment_person_id (PostHog distinct id). Failures never break send/webhook paths.1. Funnel (how it fits together)
Typical email path. In-app uses sent → read instead of delivered/opened.
Trigger / campaign
→ Temporal channel Activity (email/telegram/…)
→ writeAuditActivity(status=sent)
→ PostHog: notification_sent
DashaMail Deliveries webhook
→ /v1/webhooks/dashamail
→ update delivery attempt → delivered
→ PostHog: notification_delivered
DashaMail Opens webhook
→ /v1/webhooks/dashamail
→ ingress logged (ignored for attempt status)
→ PostHog: notification_opened
In-app widget “mark read”
→ writeAuditActivity(status=read)
→ PostHog: notification_read2. Events we emit
Only these four delivery analytics events. Bounce/spam/click are stored in Delivery log but not captured to PostHog today.
| Event | Meaning | Sources | Notes |
|---|---|---|---|
| notification_sent | Отправлено |
| Usually first event in the funnel. Idempotent uuid per idempotency_key + sent + channel. |
| notification_delivered | Получено / delivered |
| Campaign UI “Received” counts this (+ legacy notification_read). Requires matching delivery attempt. |
| notification_opened | Просмотрено / opened |
| Logged as ignored for delivery-attempt status, but still captured to PostHog when attempt + segmentPersonId exist. |
| notification_read | Прочитано in-app |
| Not from DashaMail. Still contributes to campaign “Received” for backwards compatibility. |
3. Payload shape
Emitted via
posthog-node capture(). UUID is stable so retries do not double-count the same outcome.{
"distinctId": "<subscriber.segment_person_id>",
"event": "notification_delivered",
"uuid": "<stable sha256-based uuid>",
"properties": {
"workflow_id": "order-shipped",
"idempotency_key": "campaign:<campaignUuid>:<subscriberId>",
"campaign_id": "<campaignUuid>",
"channel": "email",
"provider": "dashamail",
"provider_message_id": "<id from DashaMail>"
}
}Properties
workflow_id— alwaysidempotency_key— always; seeds the stable uuid withstatus+channelcampaign_id— when key looks likecampaign:<uuid>:…channel— email / telegram / push / inapp / webhook when knownprovider,provider_message_id— set on provider webhook captures (e.g. dashamail)
4. Env & identity
Project token is for capture; Personal API key is for admin HogQL / dashboards.
POSTHOG_HOST=https://posthog.example
POSTHOG_PROJECT_API_KEY=phc_… # capture (worker + ingestion-api)
POSTHOG_API_KEY=phx_… # admin stats / workspace API
POSTHOG_PROJECT_ID=1
distinct_id = subscriber.segment_person_id
# set when cohort sync / PostHog Action / ensureSubscriber links the person5. Campaign stats mapping
Admin Campaigns cards query these events in HogQL.
Sent = count(notification_sent)
Received = count(notification_delivered) + count(notification_read)
Opened = count(notification_opened) # dashboards; card rate uses Received/Sent
Filter: properties.campaign_id = <campaign uuid>Provider setup for email status webhooks: DashaMail webhooks. In-app realtime: Centrifugo. Live ingress: Delivery log.