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.

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_read
2. Events we emit
Only these four delivery analytics events. Bounce/spam/click are stored in Delivery log but not captured to PostHog today.
EventMeaningSourcesNotes
notification_sentОтправлено
  • Temporal writeAuditActivity when channel send succeeds (status=sent)
  • Provider webhook event=send|sent (e.g. DashaMail Sent)
Usually first event in the funnel. Idempotent uuid per idempotency_key + sent + channel.
notification_deliveredПолучено / delivered
  • Provider webhook event=delivered (DashaMail Deliveries)
  • Temporal audit when status=delivered is written
Campaign UI “Received” counts this (+ legacy notification_read). Requires matching delivery attempt.
notification_openedПросмотрено / opened
  • Provider webhook event=opened|open|opens (DashaMail Opens)
Logged as ignored for delivery-attempt status, but still captured to PostHog when attempt + segmentPersonId exist.
notification_readПрочитано in-app
  • In-app mark-read / writeAuditActivity status=read (Centrifugo inbox)
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 — always
  • idempotency_key — always; seeds the stable uuid with status + channel
  • campaign_id — when key looks like campaign:<uuid>:…
  • channel — email / telegram / push / inapp / webhook when known
  • provider, 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 person
5. 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.