ADR-0011: Retire propflow-inbox-poller Lambda
- Status: Accepted
- Date: 2026-04-17
- Deciders: Fede
- Related: ADR-0001 (decouple web from agents), ADR-0003 (all crons on EventBridge), ADR-0005 (graph send-lock dedup)
Context
propflow-inbox-poller was a Lambda triggered by EventBridge rate(1 minute)
that polled every connected mailbox via Microsoft Graph (or Gmail), diffed
against recentMessageIds, and published any new message into
propflow-inbound.fifo. It pre-dated the Microsoft Graph change-notification
(webhook) integration and was the only inbound path until early April 2026.
Since the Graph webhook subscription pipeline shipped (ADR-0001 + the
/api/integrations/outlook/webhook route), every connected mailbox also
delivers messages in real time:
- Camellia Apartments and PropFlow Test Property: live (
webhookShadowMode: false). - Yale 25 Station: still in shadow mode but not on the prospect-facing critical path; flipping it live is a one-line DDB update.
- Subscription expiration is handled by
propflow-outlook-subscription-reneweronrate(30 min).
Real-time delivery makes the poller redundant. Keeping it around costs:
- A duplicate code path that has its own copy of
process-inbound-messagesemantics (different dedup state:recentMessageIdsvswebhookSeenIds). - The 2026-04-17 Camellia incident: an Apr 15 website-form email sat unprocessed for 2 days, then the poller "caught up" and Clara replied to a stale prospect with a tour offer for a property they never asked about. Real-time webhook delivery would have surfaced the email within seconds.
- Two Lambdas to operate (prod +
propflow-inbox-poller-preview), EventBridge rule, deploy workflow, smoke fixtures, an entirecompare-webhook-vs-poller.tsaudit script.
Decision
Retire propflow-inbox-poller. Specifically:
- Delete
lambda/inbox-poller/(handler, build, deploy, fixtures). - Delete
.github/workflows/deploy-inbox-poller.yml. - Delete
scripts/compare-webhook-vs-poller.ts(no longer applicable — nothing to compare the webhook against). - Update
lambda/README.mdto remove the row + fixture example. - Update related ADRs (0001, 0003, 0004, 0005) and the deployment-cleanup architecture doc to reflect the retirement.
- AWS infra teardown after merge:
aws events disable-rule --name propflow-inbox-poller-schedule aws events remove-targets --rule propflow-inbox-poller-schedule --ids <target> aws events delete-rule --name propflow-inbox-poller-schedule aws lambda delete-function --function-name propflow-inbox-poller aws lambda delete-function --function-name propflow-inbox-poller-preview
Explicitly kept:
propflow-outlook-subscription-renewerLambda — the webhook depends on active subscriptions; the renewer is what keeps them alive.recentMessageIdsfield onEmailIntegration— still updated by the webhook handler to dedup Graph re-deliveries.webhookShadowModeflag — still used by Yale's not-yet-live integration. Will be removed in a follow-up once Yale flips live.
Consequences
Positive:
- One less inbound path. The webhook is now the only way an email enters the inbound pipeline (besides SES, which is a separate channel).
- No more 2-day delays caused by a poller being disabled or behind.
- Latency improves from "up to 1 minute" to "seconds" for every email.
Negative / trade-offs:
- If the Graph webhook subscription expires and the renewer fails to
extend it, mail will queue in the mailbox until Microsoft accepts a new
subscription. Mitigation: the renewer's
RENEWAL_THRESHOLD_MINUTES = 90gives ~30× the cron cadence to recover; CloudWatch logs should be alerted on to catch repeated renewal failures. - If Microsoft Graph has an outage, real-time delivery stops. Previously, the poller would have caught up after the outage cleared. Mitigation: the renewer doubles as a "subscription health check" — if a subscription goes missing, the renewer will recreate it, which causes Graph to backfill any missed notifications.
Migration
This ADR assumes ADR-0005 send-lock is in place so that any duplicate message that does sneak through (e.g. a renewer-recreated subscription re-delivering an already-processed message) cannot trigger a duplicate outbound reply.
References
- Phase 4 extraction (#163) —
agents/clara/subproject. - PR #176 — fix(webhook): refuse to coin-flip when two properties share a Graph subscription. Same incident chain.