Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 29 additions & 4 deletions changelog.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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.
</Note>

<Update label="June 19, 2026" description="blockedReason is operator-only — not returned via customer API" tags={["Dashboard"]}>
## `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.
</Update>

<Update label="June 17, 2026" description="v0.10.0 — blocked message status, outbound abuse detection" tags={["SDK", "CLI"]}>
## `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"`
Expand Down
2 changes: 1 addition & 1 deletion concepts/messages.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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 |

<Note>
Expand Down
9 changes: 5 additions & 4 deletions sdks/typescript.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -374,10 +374,11 @@ return a `409` (`SenderKitApiError`) — and resolves to
</Note>

<Note>
**`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" })`.
</Note>

```ts
Expand Down