From 7aa2b48db780ce4546c69ca3a79f5fc2ba3ee2ac Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 19 Jun 2026 22:13:11 +0000 Subject: [PATCH] docs: blockedReason is operator-only, not returned via customer API MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit senderkit-app #211 removed blockedReason from all customer-facing message projections (messages.list, messages.get, SSE tail, logs page). Customers see only a generic "Blocked by automated content safety checks." timeline entry; the detailed detection signal breakdown is operator-only. The June 17 changelog entry and the SDK/concepts docs incorrectly described blockedReason as a customer-visible field with meaningful content — corrected across all three files. Co-Authored-By: Claude Sonnet 4.6 Claude-Session: https://claude.ai/code/session_01L1Vn6uVk4YTQShZyH8j6oi --- changelog.mdx | 33 +++++++++++++++++++++++++++++---- concepts/messages.mdx | 2 +- sdks/typescript.mdx | 9 +++++---- 3 files changed, 35 insertions(+), 9 deletions(-) diff --git a/changelog.mdx b/changelog.mdx index 971d78f..930e6db 100644 --- a/changelog.mdx +++ b/changelog.mdx @@ -14,21 +14,46 @@ This page tracks releases of SenderKit's developer libraries — the documents. New entries land here as each library ships a version. + + ## `blockedReason` is operator-only + + The `blockedReason` field is not returned in any customer-facing message read + (`messages.list`, `messages.get`, the SDK, the SSE tail, or the Logs page). + + When the outbound abuse scanner halts a send, the customer-visible message timeline + records only a generic notice: **"Blocked by automated content safety checks."** + The detailed signal breakdown (which heuristic fired, LLM confidence score, etc.) + is kept operator-only in the admin console and is intentionally not disclosed to + senders — surfacing detection signals would help bad actors evade them. + + The `Message.blockedReason` property in the SDK type (`string | null | undefined`) + is retained as an optional field but is never populated in customer API responses. + The earlier June 17 changelog entry described `blockedReason` as containing + human-readable detail (e.g. `"High-confidence phishing content detected"`) — that + description was incorrect and has been superseded by this entry. + + ### What still works + + - `messages.list({ status: "blocked" })` — filtering for blocked messages works as + documented. Only `blockedReason` is withheld; every other message field is present. + - `Message.status === "blocked"` — detection and the status transition are unchanged. + + - ## `blocked` message status + `blockedReason` + ## `blocked` message status **`@senderkit/sdk@0.10.0` / `@senderkit/cli@0.6.3`** SenderKit now runs outbound anti-phishing detection over email and SMS content before handing a message to a provider. A flagged send is halted and the message - lands in a new terminal `blocked` status with a human-readable reason. + lands in a new terminal `blocked` status. ### What's new - **`blocked` status** — `Message.status` can now be `"blocked"`. A blocked message was stopped by the abuse scanner before provider dispatch and will not be retried. - - **`Message.blockedReason`** — optional `string | null` field that explains why the - message was flagged (e.g. `"High-confidence phishing content detected"`). + The message timeline records a generic notice; detection details are operator-only + (see the [June 19 entry](#june-19-2026) for clarification). - **`messages.list({ status: "blocked" })`** — the status filter now accepts `"blocked"` in the SDK, CLI (`--status blocked`), and MCP (`senderkit_messages_list`). Before this release, filtering by `"blocked"` diff --git a/concepts/messages.mdx b/concepts/messages.mdx index 992ba6d..6812881 100644 --- a/concepts/messages.mdx +++ b/concepts/messages.mdx @@ -40,7 +40,7 @@ stateDiagram-v2 | `delivered` | The provider confirmed delivery | Provider webhook | | `failed` | Delivery failed | Provider webhook, a config error, or retries exhausted | | `opted_out` | Recipient is unsubscribed/suppressed | Provider webhook, one-click unsubscribe link, or send skipped because the recipient previously opted out | -| `blocked` | Delivery halted by outbound abuse detection | Phishing scan flagged the content with high confidence; the message is never handed to a provider. The `blockedReason` field explains why. | +| `blocked` | Delivery halted by outbound abuse detection | Phishing scan flagged the content with high confidence; the message is never handed to a provider. The message timeline records a generic notice; detection details are operator-only and not exposed via the customer API. | | `canceled` | Delivery intentionally stopped before dispatch | `messages.cancel()` or the dashboard cancel action | diff --git a/sdks/typescript.mdx b/sdks/typescript.mdx index 3f4a176..a23d36a 100644 --- a/sdks/typescript.mdx +++ b/sdks/typescript.mdx @@ -374,10 +374,11 @@ return a `409` (`SenderKitApiError`) — and resolves to - **`blocked` status and `blockedReason`** — `Message.status` can be `"blocked"` when - outbound abuse detection halts a send before it reaches a provider. A `blocked` - message also carries an optional `blockedReason: string | null` field explaining the - flag. Filter for blocked messages with `messages.list({ status: "blocked" })`. + **`blocked` status** — `Message.status` can be `"blocked"` when outbound abuse + detection halts a send before it reaches a provider. The message timeline records a + generic notice (`"Blocked by automated content safety checks."`); detailed detection + signals are operator-only and not returned in customer API responses. Filter for + blocked messages with `messages.list({ status: "blocked" })`. ```ts