Add isLoop helper to functions:init scaffolded handler#80
Conversation
A deployed Function receives catch-all inbound for the managed *.primitive.email subdomain, including bounces from its own outbound traffic. Without a loop guard the handler can respond to its own bounces and create a fan-out loop. The scaffolded handler now exports a small isLoop(event) helper that returns true when the From header is on any *.primitive.email address or matches the configured REPLY_FROM. The fetch dispatcher calls it right after the event-type check and returns early with skipped: loop. The default is intentionally small. Comments above the helper list common extensions (RFC 3834 Auto-Submitted headers, Message-ID chain tracking, signup-email matching) so the next author knows where to add sophistication without rediscovering the need.
Greptile SummaryThis PR replaces the scaffolded handler's inline self-reply guard with a named, exported
Confidence Score: 5/5Safe to merge — the change is additive, scoped entirely to the scaffolded handler template, and ships with full test coverage. The predicate logic is straightforward and its trade-offs (substring false positives, empty-From passthrough) are explicitly documented in both the comment block and the PR description, leaving nothing implicit for the next author. The dispatcher integration is minimal and the existing catch-all error handler remains intact. No files require special attention. Important Files Changed
Reviews (3): Last reviewed commit: "Merge branch 'main' into functions-init-..." | Re-trigger Greptile |
Summary
A deployed Primitive Function receives catch-all inbound for the managed
*.primitive.emailsubdomain. That stream includes bounces and auto-replies generated by the handler's own outbound traffic. Without a loop guard the handler can respond to its own bounces and create a fan-out loop.The scaffolded handler now exports a small
isLoop(event)helper. The fetch dispatcher calls it right after the event-type check and returns early withResponse.json({ skipped: 'loop' })when it returns true.Default predicate:
Fromon any*.primitive.emailaddress (covers the catch-all managed subdomain, simple self-reply, and bounces frommailer-daemon@*.primitive.email).Frommatching the configuredREPLY_FROM(covers handlers whose sender is on a non-managed domain).Comparisons are case-insensitive substring matches so a display-name form (
Alice <alice@example.com>) works without parsing the bracketed address.The default is intentionally small. The comment block above the helper lists common extensions so the next author knows where to add sophistication:
Auto-Submittedheaders andPrecedence: bulk/List-Unsubscribe.Message-ID/In-Reply-Tochains to break ping-pong loops between two cooperating handlers on different domains.Test plan
pnpm --filter @primitivedotdev/cli lintpnpm --filter @primitivedotdev/cli typecheckpnpm --filter @primitivedotdev/cli test functions-init(27 tests pass; new tests cover the helper export, the dispatcher call site, the two predicate arms, and the extension-point comment)