Skip to main content

Welcome email staging verification

PostHog project 165441 (Switchback, UTC). Work through this list on staging-cloud after WP-05 deploy wiring is live and the founder has set GitHub Environment values from WP-00 (#3913).

Founder-only: sign in on staging. Unattended agents never enter credentials.

Preconditions

  • The email: block is present in staging compass.yaml (see deploy logs: Email config: writing email block).
  • email.scheduleProfile is fast so the five-step sequence completes in about twelve minutes.
  • email.allowlist holds team staging addresses only.
  • Resend reserved test addresses for bounce and complaint checks are recorded in WP-00 (#3913), not in this doc. They must also be on the allowlist: the allowlist is checked before the provider, so an unlisted test address lands skipped and no webhook ever fires. The match is exact, so +label variants need their own entries.
  • The staging EMAIL_API_KEY is send-only and cannot read messages back. Inspect headers and the text part (check 3) in the Resend dashboard or a team inbox.

Acceptance checks (WP-06)

Record pass or fail on issue #3919 with row statuses, PostHog event names, and message headers where relevant. Do not paste secret values.

#CheckExpected
1Sign up a fresh staging accountfive queued rows, sendAt spaced by minutes
2Wait one pollstep one is sent with a providerMessageId, and the mail arrives
3Inspect the received messagetext part present, both List-Unsubscribe headers present, links carry UTM tags
4Reset a sent row to queued by handthe provider dedupes on the idempotency key; no second copy arrives
5Point EMAIL_API_KEY at a bad key, redeploy, waitattemptCount climbs with backoff, the row lands failed after five attempts, nothing sends twice
6Enroll the provider bounce test addressthe webhook sets suppressedAt, remaining rows go canceled, email_bounced is captured
7Enroll the provider complaint test addressthe same, via email_complained
8Click the footer unsubscribe linkthe confirmation page renders, unsubscribedAt is set, remaining rows go canceled
9Connect a calendar before step three is duethat row lands skipped, not sent
10Sign up an address outside the allowlistthe row lands skipped, nothing sends
11Delete the staging accountthe rows are gone
12Redeploy with the email: block removedno enrollment, no poller, startup clean
13Check PostHogemail_send_heartbeat arriving with environment=staging, one email_sent per delivered step

Restore EMAIL_API_KEY after check 5 and re-enable the email: block after check 12.

Checks 5 and 12 are faster on the host than through a redeploy: copy ~/compass/compass.yaml aside, edit it, run ./compass restart, and move the copy back (then restart again) when the check is done.

Checks 1 to 11 need only the backend's own code path, not a browser sign-in: an operator script that calls userService.upsertUserFromAuth against the staging config enrolls a user exactly as sign-up does.

Two-replica claim exclusivity is covered by the database test in #3915, not this runbook.

PostHog signals

EventDistinct idNotes
email_send_heartbeatcompass-backend-emailEvery five minutes while the poller runs; includes queued_count, oldest_queued_age_ms, failed_count_24h, environment, deploy version
email_sentCompass user idProperty step is the welcome step key
email_skippedCompass user idAllowlist, skipIf, validation
email_failedCompass user idRow reached failed after retries
email_bouncedCompass user idProvider webhook
email_complainedCompass user idProvider webhook
email_unsubscribedCompass user idUnsubscribe confirmation

Filter staging with environment=staging on per-send events and heartbeat properties.

Launch alerts (evaluate, do not notify yet)

Add these when the release owner arms PostHog alerts (same pattern as Meeting monitoring):

SignalFire whenCadence
Provider complaint rateResend complaint rate above 0.1%Match provider dashboard / weekly review
Email send failuresfailed_count_24h > 0 on email_send_heartbeatTwo consecutive 5-minute heartbeat samples (10 minutes)

Also confirm heartbeat freshness: zero email_send_heartbeat samples in 10 minutes while the email: block is configured means the poller or PostHog path is down, distinct from an empty queue (queued_count=0 with samples present).

Startup confirmation

After deploy, backend logs include one line (no addresses):

Welcome email: provider=<resend|log>, allowlist=<N> address(es)

Config is read once at startup; this line confirms a redeploy picked up allowlist or provider changes.

Redeploying the same tag rewrites compass.yaml but does not restart the backend, because ./compass update only recreates containers whose image changed. After changing any EMAIL_* value, run ./compass restart on the host and look for this line with the new timestamp. Until then the webhook route answers 404 (Email is not configured).