Skip to content

feat(sending): operator notice for access requests carries account facts - #1058

Merged
jiashuoz merged 1 commit into
mainfrom
feat/sending-access-notice-signals
Sep 28, 2026
Merged

jiashuoz merged 1 commit into
mainfrom
feat/sending-access-notice-signals

Conversation

@jiashuoz

Copy link
Copy Markdown
Member

Summary

The operator email for an external-sending-access request (NotifySendingAccessRequest) showed only Category, Account id, Request id, and Expected daily volume before the audited approve/decline commands — the operator had to run -inspect-external-sending just to learn who filed the request. This adds a server-owned account-facts block, read fresh from the database, between the volume line and the command block (still above the customer-supplied fence).

New block (placeholder values):

Account: usr_example123
Request: esar_example456
Expected daily volume: 40

Owner: owner@example.test (Jane Q. Doe)
Signed up: 2026-01-15T00:00:00Z (256 days ago)
Account class: demo
Plan: pro
Owner mailbox verified: yes
Agents: 2 live
Domains: 1 verified
Sending: paused (abuse)
Prior requests: 2 decided (last: declined)

Review, then decide with the audited local command:
  e2a -inspect-external-sending -account-id usr_example123
  ...
  • "Account class" only appears when it isn't standard.
  • "Plan" defaults to free when the account has no account_limits row.
  • "Sending" shows just the state (active) unless paused, in which case it adds the pause class.
  • "Prior requests" is none when the account has no decided history, otherwise a count + the most recently decided outcome (excluding the request currently being notified).
  • The display name is a user-chosen value: it's passed through a fence-safe sanitizer (line breaks become spaces; control characters and bidi embedding/override/isolate controls are dropped) and capped at 80 runes before being printed on its one line, above the fence.
  • If any fact read fails (DB error, deleted account, etc.), a warning is logged and the notice sends without the facts block — the request is already durably queued either way, so enrichment failure never blocks it.

The decision notice to the account owner, the MCP/SDK/CLI, and the OpenAPI spec are all unchanged.

Implementation

  • internal/agent/external_access.go: NotifySendingAccessRequest now composes an accountFactsBlock, which reads the account via the existing identity.Store.GetUserByID and the existing owner-proof predicate (sendingpolicy.ExternalAccessStatus's OwnerRecipientVerified), plus one small new identity read and one small new sendingpolicy read (below). sanitizeOperatorLine neutralizes the display name.
  • internal/identity/sending_access_notice.go (new): Store.SendingAccessNoticeCounts — one statement (plus two bounded subqueries) for plan code, sending-control state/pause class, live-agent count, and verified-domain count. No new tables.
  • internal/sendingpolicy/external_access_admin.go: Module.PriorDecidedAccessRequests — count of decided requests + most recent outcome, excluding the request being notified. Added to the narrow ExternalAccess interface (Module is still its only implementer).

Operational risk

Low. Additive to an existing best-effort notification path (sendFeedbackEmail); no schema change, no /v1 change, no change to what the account owner or any client surface sees. A read failure degrades to the pre-existing notice body rather than failing the request.

Test plan

  • go build ./... — clean.
  • go vet ./internal/agent/... ./internal/identity/... ./internal/sendingpolicy/... — no new findings (pre-existing resp-ordering vet warnings in unrelated test files predate this change).
  • go test -tags integration ./internal/agent/ -run 'External|Access|Notify|Quote' — all pass, including three new tests: all-facts-present body assertions (owner/plan/verified/agents/domains/paused+class/prior-requests, positioned above the fence), a display name with an embedded newline + bidi override neutralized to one safe line, and a failed fact read (nonexistent account) still sending the base notice with a logged warning and no facts block.
  • go test -tags integration ./internal/identity/... -run SendingAccessNoticeCounts and full package run — pass (a handful of unrelated pre-existing failures — account-trash/outreach tests — reproduce identically against a clean shared local test DB; documented local-DB-contention/outreach-baseline issues, not touched by this change).
  • go test -tags integration ./internal/sendingpolicy/... — full package passes, including new TestPriorDecidedAccessRequests.

🤖 Generated with Claude Code

https://claude.ai/code/session_018tVLxUHk3fqQuq8C3wqyHW

The operator email for an external-sending-access request showed only
Category/Account id/Request id/volume, so a reviewer had to run
-inspect-external-sending just to learn who filed it. Add a server-owned
account-facts block (owner identity, signed-up age, non-standard account
class, plan, owner-mailbox verification, live/verified resource counts,
sending state, and prior decided requests) between the volume line and
the command block, still above the customer-supplied fence. A display
name is sanitized and length-capped before printing; any fact-read
failure logs a warning and falls back to the base notice instead of
blocking the request.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018tVLxUHk3fqQuq8C3wqyHW
@jiashuoz
jiashuoz merged commit 0df9122 into main Sep 28, 2026
29 checks passed
@jiashuoz
jiashuoz deleted the feat/sending-access-notice-signals branch September 28, 2026 04:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant