Skip to main content
This page tracks releases of SenderKit’s developer libraries — the TypeScript SDK, the PHP SDK, the CLI, and the MCP server.
The SenderKit API and dashboard are still pre-launch. Only the client libraries have a public release history today, so that’s all this page documents. New entries land here as each library ships a version.
SDKMCP
@senderkit/sdk@0.16.0 — MCP output schemas and structured results

SDK/MCP release: an outputSchema on every tool

@senderkit/sdk@0.16.0
  • Every MCP manifest tool now declares an outputSchema (McpToolSpec.outputSchema, plus one exported *Output Zod shape per tool in @senderkit/sdk/mcp) describing its structured result. The hosted server at mcp.senderkit.com advertises these via tools/list and returns every result as structuredContent alongside the existing JSON text. See Structured results.
  • Message results no longer include internal identifiers on the hosted MCP server: senderkit_messages_list and senderkit_messages_get return the public publicId but not the database row id, workspace id, or provider-connection id. The REST API’s GET /v1/messages responses are unchanged.
  • Exports the TEMPLATE_STATUSES, INBOUND_MESSAGE_STATUSES, INBOUND_DOMAIN_KINDS, and INBOUND_DOMAIN_STATUSES enums the shapes use.
  • The CLI-bundled stdio server is unchanged in this release (it does not declare output schemas yet).
Integration
Twilio: SMS delivery status now updates

Twilio SMS delivery status tracking now works

Twilio SMS connections could never record delivery status — messages stayed at sent, and the message.delivered / message.failed webhooks never fired for SMS sends, regardless of how the connection was configured. This is fixed.Delivery status tracking for a Twilio connection is opt-in: set the connection’s Status callback URL (optional) field to the exact status-callback URL configured in Twilio for that number or Messaging Service (Twilio signs it verbatim, so it must match character-for-character, including any trailing slash). Sends work the same either way — this field only affects whether status updates come back.
SDKCLIMCP
@senderkit/sdk@0.15.0 / @senderkit/cli@0.10.0 — complete MCP safety-hint trio

SDK/CLI/MCP release: explicit safety hints on every tool

@senderkit/sdk@0.15.0 / @senderkit/cli@0.10.0
  • Every MCP tool now states all three safety hints explicitlyreadOnlyHint, openWorldHint, and destructiveHint — instead of leaving some to a client’s inferred defaults. senderkit_send and senderkit_send_raw are the only tools with openWorldHint: true, since they’re the only ones that deliver messages to recipients outside SenderKit; every read-only tool now also states openWorldHint: false and destructiveHint: false directly.
    For TypeScript consumers, this widens the SDK’s ToolAnnotations type: code that previously constructed a partial value (e.g. { readOnlyHint: true }) must now supply all three hints.
  • The inbound message verdicts field description is now provider-neutral — it describes scanning verdicts (spam/virus/SPF/DKIM) without naming the underlying receiving infrastructure. The field’s shape and runtime values are unchanged.
  • senderkit_send_raw’s scheduledAt field documents the same “must be in the future, within 30 days” constraint as senderkit_send, and its subject/html fields now state they’re required when channel is "email".
The Python and PHP SDKs are unaffected — these are MCP-manifest-only changes.
SDKCLIMCP
@senderkit/sdk@0.14.2 / @senderkit/cli@0.9.2 — SMS recipient format documentation

SDK/CLI/MCP release: SMS recipient format

@senderkit/sdk@0.14.2 / @senderkit/cli@0.9.2The hosted API rejects a non-E.164 SMS to up front with 400 invalid_recipient — for both template and raw sends — instead of accepting it and failing later at dispatch. This release brings the SDK/CLI/MCP surface in line with that contract:
  • senderkit_send_raw’s to field now spells out the per-channel recipient formats, including the E.164 requirement for sms — matching senderkit_send, which already documented it. This flows through to the senderkit send-raw CLI help.
  • The SDK README’s SMS example now notes the E.164 requirement and the 400 invalid_recipient response.
No behavior change in the client itself — recipient validation stays server-side. The Python and PHP SDKs are unaffected.
SDKCLIMCP
@senderkit/sdk@0.14.1 / @senderkit/cli@0.9.1 — inbound tool wording and annotation fixes

SDK/CLI/MCP release: inbound tool corrections

@senderkit/sdk@0.14.1 / @senderkit/cli@0.9.1A follow-up pass on the inbound email tools in the MCP manifest, now also the single source powering the hosted MCP server’s inbound tool suite:
  • senderkit_inbound_addresses_create is annotated destructiveHint: false — creating an address is additive and fully reversed by deleting it, so well-behaved clients no longer prompt for confirmation on it.
  • localPart documents the real format: 1-64 characters of a-z, 0-9, ., _, -, must start and end alphanumeric, lowercased automatically, some names reserved — plus the "*" catch-all shorthand.
  • webhookEndpointId documents the full binding behavior: a bound endpoint receives message.received even if it isn’t itself subscribed to that event, but it must be active and share the address’s livemode; left unset, the event fans out to every active subscribed endpoint in the address’s mode.
  • livemode no longer implies a quota exemption. The field now states plainly that every inbound address, test or live, counts toward the plan’s inbound-address limit, and that a test-mode address’s forwardTo is recorded as a test send rather than actually delivered.
  • senderkit_inbound_messages_list and senderkit_inbound_messages_get were retitled “List Inbound Messages” / “Get Inbound Message” for consistency with the rest of the tool suite.
The Python and PHP SDKs are unaffected — these are MCP-manifest-only changes.
SDKCLIMCP
@senderkit/sdk@0.14.0 / @senderkit/cli@0.9.0 — MCP manifest wire-contract fixes

SDK/CLI/MCP release: manifest wire-contract fixes

@senderkit/sdk@0.14.0 / @senderkit/cli@0.9.0A pass over the MCP tool manifest tightened several tool schemas to state the real wire contract:
  • cc / bcc are documented as JSON arrays, capped at 50 recipients. senderkit_send and senderkit_send_raw now say so directly, matching the API’s existing limit. The CLI keeps accepting comma-separated values via its own flag help — nothing changes for CLI users.
  • List limits are enforced in the schema, not just the description. senderkit_messages_list.limit is bounded 1–200 (default 50); senderkit_inbound_messages_list.limit enforces the 1–100 range its description already claimed. An out-of-range value now fails validation instead of being silently clamped server-side.
  • Additive writes carry an honest annotation. Tools that only create something new — senderkit_templates_create and senderkit_inbound_addresses_create — now declare destructiveHint: false instead of true, so well-behaved MCP clients don’t demand confirmation for a reversible, non-destructive call.
The Python and PHP SDKs are unaffected — these are MCP-manifest-only changes.
MCP
MCP/API: field-specific send errors, E.164 SMS validation, stricter inbound cursor

Clearer send errors, plus stricter recipient and cursor validation

A few consistency fixes to the hosted MCP server and the REST API it wraps — no SDK/CLI version bump required:
  • Field-specific validation errors. A malformed senderkit_send / senderkit_send_raw call — e.g. an email send missing subject or html — now names the offending field (invalid_request: subject Required) instead of a generic Invalid input message. The equivalent POST /v1/send / POST /v1/send-raw REST error issues array gained the same precision.
  • SMS recipients are validated as E.164. to on an SMS send must now be a valid E.164 phone number (e.g. +14155551234); previously a malformed value was accepted and simulated as delivered instead of being rejected.
  • before on inbound message listing is validated strictly. senderkit_inbound_messages_list.before and GET /v1/inbound/messages?before= now reject a non-ISO-8601 value with a clear invalid_request error instead of silently returning an empty page.
SDKCLIMCP
@senderkit/sdk@0.13.0 / @senderkit/cli@0.8.0 — inbound namespace, suppressed status, full custom-domain reference

SDK/CLI/MCP release: inbound namespace + suppressed status type

@senderkit/sdk@0.13.0 / @senderkit/cli@0.8.0This release brings the TypeScript SDK, CLI, and MCP server in step with three changes already covered by earlier entries on this page:
  • client.inbound — a new namespace with addresses (list, create, delete), messages (list, get, raw, attachment), and domains (list, create, delete), matching the Inbound Email API in full — including the custom-domain and catch-all support from the July 26 entry below. The CLI gains eight senderkit inbound * commands, and the MCP server eight matching tools; see the Tools reference for parameters.
  • suppressed is now included in the SDK’s exported Message.status values (the status itself was introduced in the July 26 entry below).
  • openedAt / clickedAt on Message — the fields the July 8 entry below described ship in this release.
The Python and PHP SDKs receive the equivalent inbound/suppressed/engagement support in their own upcoming releases.
Dashboard
Logs: retry a failed message

Retry a failed send from the Logs page

Failed messages in the dashboard’s Logs view now have a Retry send action in the row menu. It re-queues the message for another dispatch attempt — the status badge flips to queued and the message follows the normal lifecycle from there, including the usual suppression and content checks. Only messages with a failed status are retryable.
WebhooksDashboard
opted_out no longer overwrites a message's delivery outcome

opted_out now only reflects sends skipped before dispatch

A recipient unsubscribing or complaining after a message was already delivered (or failed) previously flipped that message’s status to opted_out, discarding its actual delivery outcome. opted_out now means exactly one thing: the send was skipped before dispatch because the recipient had already opted out. A delivered message stays delivered even if the recipient unsubscribes from it afterward.
  • New message.complained webhook event, separate from message.opted_out. A spam complaint now fires both events; a plain unsubscribe fires message.opted_out alone.
  • messages.list({ status: "opted_out" }) and the equivalent CLI/MCP status filters now match only pre-send suppressions, not post-delivery unsubscribes or complaints.
  • See Messages → lifecycle for the updated status reference.
Filtering by status: "opted_out" was the de facto way to find a recipient’s unsubscribe/complaint history; this fix narrows it to pre-send skips only, with no replacement query yet. Subscribe to message.opted_out / message.complained to track consent events going forward.
Dashboard
New suppressed message status (managed sending)

suppressed message status

Managed sending (AWS SES) now runs SES email validation against every recipient before attempting delivery. When AWS rejects a recipient as invalid, or as already on the account’s suppression list, the message lands in a new terminal suppressed status instead of failed — keeping bounce metrics and your logs accurate about what was actually attempted versus what never left the building. A per-workspace suppression-rate signal is shown on the channel detail page.suppressed only applies to managed sending; bring-your-own-provider connections are unaffected. The Message.status type in the TypeScript, PHP, and Python SDKs will include "suppressed" in an upcoming release — until then, treat it as a value the API and dashboard can already return.
Update: as of @senderkit/sdk@0.13.0 (see the July 29, 2026 entry above), suppressed is included in the TypeScript SDK’s exported Message.status values. The Python and PHP SDKs pick it up in their own upcoming releases.
Update: a matching message.suppressed webhook event fires whenever a send lands in this status, so you can react to it without polling. See the Webhooks → Events reference.
Dashboard
Inbound email: custom domains and catch-all addresses

Custom inbound domains and catch-all addresses

Two additions to inbound email:
  • Custom inbound domains — receive mail at addresses on your own domain (e.g. inbound.acme.com) instead of only the shared {slug}.in.senderkit.email. Claim a domain from the dashboard or POST /v1/inbound/domains, publish the DNS records it returns (an MX, plus a DKIM TXT proving ownership — skipped if the domain already has a verified sending domain, which reuses its identity), and a background sweep verifies it. New senderkit_inbound_domains_list / _create / _delete MCP tools mirror the REST surface.
  • Catch-all addresses — create an address with localPart: "*" to receive mail sent to any local part on a domain that no exact address already claims. An exact-match address always takes priority over the catch-all.
Both count against the same plan-gated inbound address cap as any other address.
Update: full reference detail for both — DNS records, verification states, and API/MCP parameters — is now live on Inbound Email → Custom domains and in the MCP tools reference.
DashboardWebhooks
Inbound email: receive mail at addresses on your workspace's domain

Receive email, not just send it

Workspaces can now provision inbound addresses on a shared receiving domain ({slug}.in.senderkit.email) and get mail sent to them parsed and delivered as a new message.received webhook event — with an optional forward to a real inbox.
  • New Inbound dashboard page — create and remove addresses, optionally binding one to a specific webhook endpoint or a forwarding address.
  • New /v1/inbound/* API routes — list/create/delete addresses, list and read received messages (parsed body, headers, SES spam/virus/SPF/DKIM verdicts), and fetch attachments or the original raw MIME source (30-day retention). Requires an API key with the new inbound scope.
  • Plan-gated address caps — Free 1, Starter 10, Pro 50. A received live message counts against the same monthly message quota as outbound sends.
See Inbound Email for the full reference.
Dashboard
Templates: per-template email theme (Design tab)

Style emails without touching HTML

Email templates gained a new Design tab in the editor for setting a per-template visual theme:
  • Fonts — independent heading and body fonts, picked from a curated set of web-safe and Google fonts, or a custom Google Font by name.
  • Colors — page background, content background, and body, heading, and link text colors.
  • Content width — the email’s maximum content width, 320–900px.
The theme applies consistently in the editor canvas, the Preview tab, and sent messages. It’s fully optional — templates with no theme set render exactly as before. Mail clients without web-font support (Gmail, Outlook) fall back to a close web-safe equivalent for custom fonts.See Templates → Styling with the Design tab.
Dashboard
Templates: images stay responsive on mobile

Resized images no longer squeeze on narrow screens

Images resized in the email editor previously kept a fixed height on mobile, so shrinking the width to fit a narrow screen distorted the aspect ratio. Resized images now scale proportionally on any screen size.A new size toggle appears when an image is selected, letting you choose whether width or height stays fixed after a resize — useful for a fixed-height banner or badge that shouldn’t scale like a normal inline image.
Integration
BYOP SES: bounce and complaint events fixed for the standard AWS setup

Bounce/complaint/delivery events fixed for identity-level SES notifications

Bring-your-own-provider SES connections that follow AWS’s default bounce/complaint setup — SNS notifications wired directly onto the verified identity, without a configuration set — previously had their bounce, complaint, and delivery events silently dropped. Affected messages stayed stuck at sent, and complaint suppression / the message.opted_out webhook never fired. Both notification shapes SES can send are now recognized; no changes needed on your end.
Integration
Email: preheader text now shows in the inbox preview

Preheader now renders as the actual inbox preview snippet

The preheader field (set in the template editor, and previously visible only in the editor’s own inbox preview mock) now reaches recipients’ inboxes. Mail clients build the preview snippet shown next to the subject line from the message itself, and that wasn’t happening consistently — so the snippet recipients saw was often scraped from the body instead of the preheader you set. This is fixed across every provider connection; no changes needed on your end.
Integration
Email: cc, bcc, replyTo, and attachments now honored on all providers

cc, bcc, replyTo, and attachments now work on every connection type

These send-time envelope fields previously had no effect on some provider connections: the send would succeed, but cc/bcc recipients wouldn’t receive the message, replies would go to the From address instead of replyTo, and attachments would be silently dropped. All four fields now behave identically across every supported provider, including managed sending. No API or field changes — this is a behavior fix only.
Integration
Email: From display name quoting fix

Sender display names are now RFC-correct on every provider

Display names containing commas or other special characters (for example, Acme, Inc.) are now correctly quoted in the From header on every provider connection — previously an unquoted name with a comma could produce a malformed header. Display names (fromName) are also now consistently applied on every provider connection.
Integration
Provider webhooks: reactivation no longer marked as opted_out

Recipient reactivations are no longer treated as an opt-out

A resubscribe/reactivation event from a provider could previously be misclassified the same way as an unsubscribe, incorrectly flipping the message to opted_out status and firing a message.opted_out webhook. Reactivation events are no longer treated as opt-outs; only genuine suppressions do.
Integration
Provider webhooks: delivery status reliability fix

More reliable status updates on inbound provider events

Some provider delivery, bounce, and engagement webhook events could fail to match their originating message, leaving affected messages from that provider connection stuck at sent instead of progressing to delivered / failed, with opens and clicks not recorded. This correlation issue is fixed; no action is needed on your end.
SDKCLIWebhooks
Message engagement: opens and clicks

Provider-reported opens and clicks on messages

SenderKit now surfaces provider-reported email opens and link clicks on messages. Both are read-only and set once, on the first occurrence — later opens/clicks never update them.
  • Message gains openedAt and clickedAt — ISO 8601 strings, or null until the event happens — returned by messages.get and messages.list.
  • senderkit messages get prints both fields.
  • Two new subscribable webhook events: message.opened and message.clicked. These are engagement signals only — they never change a message’s status (delivered stays the terminal state). The message.clicked payload additionally carries the clicked link.
This ships as a minor version bump — @senderkit/sdk@0.13.0 / @senderkit/cli@0.8.0 — in the next release. The Python and PHP SDKs receive the equivalent openedAt/clickedAt fields in their own upcoming releases.
Update: @senderkit/sdk@0.13.0 / @senderkit/cli@0.8.0 has shipped — see the July 29, 2026 entry above.
SDKCLIMCP
v0.12.0 — per-send From overrides on every email send

Per-message From overrides, now identical on templated and raw sends

@senderkit/sdk@0.12.0 / @senderkit/cli@0.7.0Email sends can now override both parts of the From header, on both send() (templated) and sendRaw() (raw):
  • from — From address override (bare address). Previously accepted on sendRaw() only; now also accepted on send().
  • fromName — new on both — From display name override, rendered as Name <address>. Max 128 characters; no control characters or angle brackets.
Either can be set on its own; both fall back to the provider connection’s configured values. On managed sending, the from address is honored only on the workspace’s verified sending domain, while fromName always applies.
  • CLI: senderkit send and senderkit send-raw gain --from / --from-name.
  • MCP: senderkit_send and senderkit_send_raw gain matching from / fromName inputs.
The Python and PHP SDKs receive the equivalent fields in their own releases.
SDKCLI
v0.11.0 — `Message.blockedReason` removed from the SDK type

blockedReason removed from the SDK type

@senderkit/sdk@0.11.0 / @senderkit/cli@0.6.4The June 19 entry below noted that blockedReason was retained on the Message type but never populated by the customer API. As of this release the property is removed from the type entirely — referencing message.blockedReason in TypeScript is now a compile error rather than an always-undefined runtime value.Nothing else changes: blocked remains a valid Message.status value, and messages.list({ status: "blocked" }) / --status blocked / senderkit_messages_list still accept it as a filter. If you already branch on status === "blocked" you’re unaffected; if you referenced blockedReason anywhere, remove that reference.
Integration
Inbound hooks: retried deliveries no longer send duplicate emails

Inbound webhook retries are now deduplicated

Sources that deliver to a SenderKit inbound hook endpoint — including the Supabase auth email hook — retry a delivery whenever they don’t receive a prompt 2xx, including after transient network errors. Previously, a retried delivery could enqueue the underlying email a second time, so a recipient occasionally received a duplicate confirmation, magic-link, or recovery email.Retried deliveries of the same event are now recognized and deduplicated automatically — a retry resolves without sending again. No configuration changes are needed.
Dashboard
Logs: preview the rendered content of any message

Message preview in Logs

The Dashboard → Logs detail drawer now includes a Preview action — click the kebab menu on any selected message to see what was actually sent:
  • Email — renders the HTML in a sandboxed iframe (no remote resources, tracking pixels, or external fonts load inside the preview).
  • SMS / push / web-push — shows the message body as plain text.
The preview is fetched on demand when you open it, so the logs list loads as fast as before.Fidelity note: if the template uses a shared reusable block, the preview renders the block’s current content rather than a snapshot of what it was at send time. The drawer flags this with an “approximate” badge so you know the preview may differ slightly from what the recipient received.
Dashboard
blockedReason is operator-only — not returned via customer API

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. (Update: as of the July 6, 2026 entry above, the property has been removed from the SDK type entirely.) The earlier June 19 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.
Dashboard
Logs: scroll back through full message history

Load older message history in Logs

The Dashboard → Logs page previously showed only the live tail — a streaming view of your most recent sends with no way to page backward to investigate earlier messages.Older history now loads beneath the live tail as one continuous list:
  • Scroll to load — as you scroll toward the bottom, the next 50 messages load automatically. A manual “Load older messages” button is available as a fallback.
  • Live updates while reading back — when you’re scrolled away from the top, incoming live messages are buffered behind a ”↑ N new messages” pill instead of shifting the viewport. Returning to the top flushes the buffer and resumes the live tail.
  • “Beginning of history” footer appears once all available records have loaded.
  • The client-side text filter applies to all loaded messages combined.
The underlying API (GET /v1/messages?cursor=…) is unchanged — the dashboard now surfaces its pagination to you directly.
SDKCLI
v0.10.0 — blocked message status, outbound abuse detection

blocked message status

@senderkit/sdk@0.10.0 / @senderkit/cli@0.6.3SenderKit 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.

What’s new

  • blocked statusMessage.status can now be "blocked". A blocked message was stopped by the abuse scanner before provider dispatch and will not be retried. The message timeline records a generic notice; detection details are operator-only (see the June 19 entry 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" was rejected as an invalid enum value.
The synchronous send response is unchanged — a blocked message still starts as queued; the abort happens asynchronously during content scanning.See Messages → lifecycle for the full status reference.
DashboardBilling
Starter plan, updated pricing

Starter plan + pricing restructure

SenderKit’s pricing now has four tiers:

What changed

  • Free — template cap raised from 5 → 10; message cap lowered from 3,000 → 500/month. The free tier is now scoped for evaluation and low-volume personal projects rather than production transactional mail.
  • Starternew — $5/month: unlimited templates, 5,000 messages/month, and access to custom sending domains for DKIM alignment.
  • Pro — now has an enforced 50,000 messages/month cap (was uncapped). Price and unlimited templates are unchanged.
  • Custom sending domains are now available on any paid plan (Starter and Pro), not just Pro. See Channels → Custom sending domains.

Built-in email sender caps

The built-in @tx.senderkit.email sender enforces caps to protect the shared domain’s reputation. These caps are separate from and lower than the plan’s overall message quota — verifying a custom sending domain unlocks the higher tier:Connecting your own BYOP email provider (Postmark, SES, etc.) routes through that provider instead and bypasses the built-in caps entirely.See Channels → Built-in email sender for full details.

Upgrading

Existing free workspaces are not automatically migrated — you keep your current limits until you choose to upgrade. Select a plan from Dashboard → Billing.
DashboardIntegration
Supabase auth-email integration

Route Supabase auth emails through SenderKit

A new Integrations page in the SenderKit dashboard lets you connect your Supabase project and route all of its auth emails through SenderKit in one click — sign-up confirmation, magic link, password reset, invite, email change, and reauthentication.

One-click setup

Go to Dashboard → Integrations → Connect Supabase. Authorize SenderKit, pick a project, and SenderKit automatically:
  • Provisions a signed inbound webhook endpoint
  • Seeds and publishes six curated auth templates (supabase-signup, supabase-magiclink, supabase-recovery, supabase-invite, supabase-email-change, supabase-reauthentication)
  • Configures Supabase’s Send Email Hook to point at the endpoint
After connecting, customize the seeded templates in Templates to match your brand. Verify a custom sending domain under Channels → Domains for DKIM alignment and best inbox placement.See the Supabase auth emails guide for template variable reference, limits, and troubleshooting.
SDKCLI
MCP tool manifest improvements

MCP tool manifest sync

Minor improvements to the tool descriptions in @senderkit/sdk and the CLI-bundled MCP server, keeping them in sync with the hosted endpoint at mcp.senderkit.com:
  • senderkit_send.to now explicitly covers all four channels: email address, E.164 phone number, push device token, and JSON-encoded web-push PushSubscription.
  • senderkit_messages_list.status is now a strict enum in the tool schema (scheduled, queued, rendered, dispatched, sent, delivered, failed, opted_out, canceled), matching the API’s behavior of rejecting unknown statuses with 400. The CLI validates --status client-side and lists choices in --help.
  • Template slugs are documented as always lowercase in the senderkit_send template parameter and senderkit_templates_get slug parameter. Slugs are canonicalized to lowercase on every write path (existing mixed-case slugs continue to resolve, but new slugs are created lowercase).
SDK
PHP SDK v0.2.0 — core, Laravel, and Symfony

PHP SDK: senderkit/senderkit-php v0.2.0

The official PHP SDK is now available. It ships as three Composer packages:
  • senderkit/senderkit-php — framework-agnostic core (PSR-18 HTTP client, PHP 8.1+).
  • senderkit/senderkit-laravel — service provider, SenderKit facade, senderkit notification channel (all four channels), senderkit mail transport for existing Mailables, and VerifyWebhookSignature middleware.
  • senderkit/senderkit-symfony — bundle with autowiring and a RequestVerifier webhook helper.
The Client class mirrors the TypeScript SDK: send(), sendRaw(), sendBatch(), context(), messages->*, and templates->*. A PSR-18 HTTP client (Guzzle or symfony/http-client) is auto-discovered or can be injected.
See the PHP SDK reference for the full API, Laravel notification channel, mail transport, and Symfony bundle setup.
Dashboard
Custom sending domains (Pro)

Custom sending domains

Pro workspaces can now connect their own sending domains and send from any address on a verified domain — without the shared @tx.senderkit.email address appearing to recipients.How it works:
  • Add a domain in Dashboard → Channels → Add domain (Pro-gated; free workspaces see an upgrade prompt).
  • Publish four DNS records: a white-label DKIM TXT record at senderkit._domainkey.{domain}, an MX + SPF pair on send.{domain} for the custom MAIL FROM, and optionally a root SPF and DMARC record. The dashboard shows the exact values to copy and probes DNS live once records are detected.
  • SenderKit verifies the domain (polls every 5 minutes; Check now button for immediate re-check). Verification completes within minutes once records propagate; claims expire after 72 hours.
  • Once verified, the domain activates as a managed connection in Channels. It can be set as the workspace default or targeted per template. The shared tx.senderkit.email sender stays as the implicit fallback.
From-address override — raw sends (POST /v1/send-raw, senderkit send-raw, senderkit_send_raw) now honor a from address whose domain exactly matches a verified custom domain:
Custom-domain connections have their own per-connection cap tier and billing meter, separate from the built-in shared sender caps.See Channels → Custom sending domains.
SDKCLI
v0.8.0 — API key scopes enforced, SenderKitPermissionError

Least-privilege API key scopes

API keys now carry an enforced scope set. Previously, scopes were stored but never checked — every valid key had full access. Starting with this release, a scoped key used outside its grant returns 403 with code: "insufficient_scope".

Scope model

Three scopes: read · send · cancel. All three are granted by default when creating a key; the dashboard scope picker lets you restrict to fewer. Keys minted without explicit scopes remain unscoped (full access) — no migration needed, no existing integrations break.

TypeScript SDK (@senderkit/sdk v0.8.0)

New SenderKitPermissionError403 responses now throw SenderKitPermissionError instead of the previous SenderKitAuthenticationError. Because it extends SenderKitApiError, existing catch (SenderKitApiError) blocks still handle it; add a specific SenderKitPermissionError branch to distinguish scope errors from auth errors:
New ApiScope type"read" | "send" | "cancel", exported from @senderkit/sdk.

CLI (@senderkit/cli v0.6.1)

A 403 is now reported as “Permission denied: … The API key is valid but lacks the required scope.” (JSON: type: "permission") instead of the previous misleading “Authentication failed” message.See Authentication → Scopes and the SDK error reference.
CLI
CLI: senderkit mcp install defaults to hosted OAuth

senderkit mcp install now defaults to hosted OAuth

senderkit mcp install now writes a hosted endpoint config (url-only, pointing at https://mcp.senderkit.com) with OAuth by default — no API key is stored on disk and your MCP client handles sign-in. Previously the default was a local stdio subprocess with an API key.
Codex users get a native streamable-HTTP url in config.toml (no longer bridged through mcp-remote), and can sign in with codex mcp login senderkit.The --remote flag is kept as a no-op alias for back-compat (it now just selects the new default behavior). The --local + --url combination is rejected with a clear error.
SDK
v0.7.0 — lean template reads, TemplateVersion.content removed

Lean template and message reads (TemplateVersion.content removed)

@senderkit/sdk@0.7.0 / @senderkit/cli@0.5.1templates.get() no longer includes a content field in the response — it was already undefined at runtime after the server stopped returning the raw template body. This release makes the TypeScript types reflect reality.

What changed

  • TemplateVersion.content is removed from the Template type. Code that accessed template.currentVersion.content will now produce a TypeScript compile error. The rest of currentVersion (versionNumber, variables, publishedAt) is unchanged.
  • messages.list() and messages.get() also omit rendered content; vars, timeline, and metadata are still returned on every message.
No CLI behavior changesenderkit templates get only surfaced version metadata, never raw content.
MCP
MCP template generation tools

AI-powered template creation via MCP

Two new tools on the hosted MCP endpoint let an AI agent author SenderKit templates from a plain-language brief, without the user opening the dashboard.

senderkit_templates_create

Generate a new template from a brief and save it as a draft. The tool composes channel-native content server-side and returns a deep link to the editor for review. Nothing is sent or published until the user approves the draft. Enforces the workspace’s template cap before the AI call runs.

senderkit_templates_regenerate

Fully rewrite an existing draft template from a new brief, reusing any already-declared variables. Only draft templates can be regenerated — published templates are left untouched.Both tools are available on the hosted endpoint (mcp.senderkit.com) and via OAuth; they are not available on the CLI-bundled stdio server because they require server-side AI generation. See the Tools reference for parameters.
SDKCLIMCP
v0.4.0 — senderkit_context, OAuth MCP, email deliverability

senderkit_context tool + mode on send results

TypeScript SDK

No SDK-visible changes in this release.

CLI

New senderkit context command reports the active connection’s live/test mode. Useful in scripts and in MCP sessions to confirm which key is active before sending.

MCP server

New senderkit_context tool — returns { workspaceId, mode, livemode }. Call it before sending to confirm whether the connection is live (real delivery) or test (recorded only). Available on both the CLI stdio server and the hosted endpoint.mode field on send resultssenderkit_send and senderkit_send_raw results now include a mode field alongside the message id and status, giving the model a post-send confirmation of which mode the message was dispatched in.Tool titles and behaviour annotations — every tool now exposes a human-readable title and a behaviour hint (readOnlyHint or destructiveHint) per the MCP protocol. These appear in compatible client UIs and are required for the Anthropic Claude Connectors Directory.OAuth connection for claude.aimcp.senderkit.com now acts as a full OAuth 2.1 authorization server, so claude.ai and other OAuth-only MCP clients can connect without a manual API key. Sign in with your SenderKit account and pick a workspace + mode on the consent screen. See Installation → Connect from claude.ai.
SDKCLIMCP
v0.6.0 — workspace identity & client.context()

Workspace identity in context

TypeScript SDK

New client.context() method — calls GET /v1/context and returns a SenderKitContext ({ workspace: { id, slug, name }, mode }). Use it to confirm which workspace an API key belongs to before sending:
SenderKitContext is now a named export alongside the existing types.

CLI

senderkit context now calls GET /v1/context (was a local key-prefix check) and includes the workspace name and slug in its output:
--json returns { workspace: { id, slug, name }, mode }.

MCP server

senderkit_context now returns { workspace: { id, slug, name }, mode }. The workspaceId and livemode fields from v0.4.0 are replaced by the structured workspace object and a single mode string.
SDKCLI
v0.3.1 — default URL corrections

Default URL corrections

TypeScript SDK

DEFAULT_BASE_URL changed from https://senderkit.com/api to https://api.senderkit.com. The SDK now targets https://api.senderkit.com/v1/send (and sibling routes) by default, matching the OpenAPI spec’s canonical server. The previous apex URL triggered an apex→www redirect that stripped the Authorization header, causing 401s. Callers passing an explicit baseUrl are unaffected.

CLI

The hosted MCP default corrected from https://mcp.senderkit.com/mcp to https://mcp.senderkit.com. On the dedicated MCP host the subdomain root is the endpoint — any other path (including /mcp) returns 404.
SDKCLIMCP
v0.3.0 — web-push channel

Web Push channel

web-push (browser notifications via the Web Push Protocol + VAPID) is now a first-class channel, distinct from the mobile push channel.

TypeScript SDK

  • Channel now includes "web-push".
  • New RawWebPushContent type: { title, body, icon?, clickUrl?, badge?, data? }.
  • sendRaw({ channel: "web-push", to, content })to is the JSON-encoded browser PushSubscription (endpoint + p256dh/auth keys). Your application collects these after the user grants browser permission.

CLI

  • senderkit send-raw --channel web-push with --title, --body, --icon, --click-url, --badge, and --push-data flags.

MCP server

  • senderkit_send_raw and senderkit_messages_list channel enums now accept "web-push". The icon and clickUrl inputs are available on senderkit_send_raw. Both the CLI stdio server and the app-hosted HTTP MCP server are updated.

Dashboard / API

The web-push channel is also fully supported in the SenderKit dashboard and REST API: connect a VAPID keypair, create web-push templates, and send via POST /v1/send or POST /v1/send-raw.
SDKCLI
v0.2.1 — CLI email-envelope flags

CLI cc/bcc/attachments flags fixed

--cc, --bcc, and --attachments flags on senderkit send and senderkit send-raw were advertised in --help but threw a Zod validation error at runtime — the CLI adapter passed them as strings while the validator expected arrays or objects. They now work correctly:
  • --cc / --bcc — accept a comma-separated string (a@x.com,b@x.com) or a JSON array string ('["a@x.com","b@x.com"]').
  • --attachments — accepts a JSON array string of attachment objects.
The MCP server path (real arrays/objects) is unchanged.Also fixes the SDK’s VERSION constant and the CLI’s --version output, which were reporting stale values. Both are now derived directly from package.json at build time and cannot drift.
SDKCLIMCP
v0.2.0 — initial public release

Initial public release

The first public release of the SenderKit developer libraries. All three libraries are versioned together and published to npm at 0.2.0.

TypeScript SDK — @senderkit/sdk@0.2.0

  • SenderKit client with send(), sendRaw(), and sendBatch() for multi-channel delivery (email, SMS, push).
  • messages.list() (with metadata filtering), messages.get(id), and messages.cancel(id).
  • templates.list() and templates.get(slug).
  • scheduledAt on send / sendRaw for scheduled delivery.
  • client.mode ("live" / "test"), derived from the API key prefix; the client validates that keys start with sk_live_ or sk_test_ at construction.
  • Typed error hierarchy: SenderKitApiError, SenderKitAuthenticationError, SenderKitValidationError, SenderKitRateLimitError, SenderKitTimeoutError, and SenderKitNetworkError.
See the SDK reference for full usage.

CLI — @senderkit/cli@0.2.0

  • senderkit login for interactive authentication (API key input is hidden).
  • senderkit send and senderkit send-raw.
  • senderkit templates list / get.
  • senderkit messages list / get / cancel.
  • --json flag for structured, machine-readable output (including errors).
See the CLI docs to get started.

MCP server — bundled in @senderkit/cli@0.2.0

Exposes SenderKit to AI agents over the Model Context Protocol, via the senderkit mcp command (stdio and HTTP transports). Tools:
  • senderkit_send and senderkit_send_raw
  • senderkit_templates_list and senderkit_templates_get
  • senderkit_messages_list, senderkit_messages_get, and senderkit_cancel_message
See the MCP overview and tools reference.