senderkit/senderkit-php). For framework
integrations see the dedicated pages:
Laravel
Service provider, notification channel, mail transport, and webhook middleware.
Symfony
Bundle with autowiring and a webhook request verifier.
Requirements
- PHP 8.1+
- A PSR-18 HTTP client — Guzzle or
symfony/http-clientare auto-discovered; you can also inject your own.
Install
Quickstart
Client construction
Your API key — must start with
sk_live_ (live mode) or sk_test_ (test
mode). The constructor throws \InvalidArgumentException for any other
prefix. See Authentication.Override the API base URL. Useful for proxies or self-hosted gateways.
Per-request timeout in milliseconds.
Max retry attempts for transient failures — network errors, timeouts,
429,
and 5xx. Retries use exponential backoff with jitter.Inject a PSR-18 HTTP client. Defaults to auto-discovery via
php-http/discovery (Guzzle or symfony/http-client if either is
installed).$client->mode property ('live' or 'test') is set as a read-only
value after construction, derived from the API key prefix.
send()
Send a templated message, substituting
variables at send time.
Template slug, e.g.
'welcome'.Recipient address — email, phone number, or push token.
Template variables. Defaults to
[].Force a channel (
Channel::Email, Channel::Sms, Channel::Push,
Channel::WebPush). Defaults to the template’s primary channel.Pin a specific template version. Omit to use the
current published version for the environment.
Free-form metadata attached to the message. Indexed server-side, so you can
later filter with
messages->list(new ListMessagesParams(metadata: [...])).Defer delivery to a future time — a
DateTimeInterface or an ISO 8601
string. Must be in the future and within 30 days. See
Sending.Idempotency key. If omitted, the SDK auto-generates one so a retried
request never duplicates a send. Reusing a key returns the original message.
Cc / Bcc recipients. Email only.
Reply-To address. Email only.
File or inline attachments (email only). Each
Attachment takes filename,
contentType, content (base64-encoded bytes), and optional
inline/contentId. Provider caps total across all attachments at 10 MB.Response
SendResult has three properties: id (e.g. "msg_…"), status
("queued" or "scheduled"), and livemode (bool).
sendRaw()
Send inline content without a registered template. Pass one of the typed
content classes — the channel is inferred from the content type.
Recipient address.
Typed content object — determines the channel.
Email only. Must match a
verified custom sending domain
when using the managed email sender.
Set
true to run server-side variable substitution over the raw content
using the vars values.- email
- sms
- push
- web-push
sendBatch()
Send many messages sequentially with per-item error isolation. A failure on
one item never throws — each result carries a success flag so the rest of the
batch is not affected.
Base idempotency key. Each item is dispatched with
{key}-{index} unless
the item already carries its own key.BatchResult:
$result->ok—trueon success,falseon failure.$result->index— position in the input array.$result->result—SendResultwhenok === true.$result->error—SenderKitExceptionwhenok === false.
context()
Fetch the workspace the API key belongs to and the active send mode.
Context object with workspace (id, slug, name) and mode
('live' or 'test'). Mirrors
GET /v1/context.
Templates
list() returns templates without their version body. get($slug) includes
currentVersion (versionNumber, variables, publishedAt). A Template
has slug, channel, description, status, and updatedAt.
The
content (raw HTML/blocks) field is intentionally omitted from list()
and get() responses to keep payloads lean. Use the dashboard or the
/v1/templates/{slug}/render endpoint when you need the rendered output.Messages
cancel() only works on scheduled or queued messages — later states
return a 409 (ApiException). list() returns data (array of Message)
and nextCursor (string or null).
Error handling
All exceptions extendSenderKitException (which extends \RuntimeException).
API errors carry $status, $apiCode, $issues, and $requestId (quote
$requestId in support requests).
| Class | Thrown when | Extra properties |
|---|---|---|
ApiException | Any non-2xx (e.g. 403, 409, 5xx) | $status, $apiCode, $issues, $requestId |
AuthenticationException | 401 — bad, missing, or revoked key | (inherits ApiException) |
ValidationException | 400 / 422 — invalid request | (inherits ApiException) |
RateLimitException | 429 — rate limited | $retryAfterMs (milliseconds) + inherited |
TimeoutException | Request exceeded $timeoutMs | — |
NetworkException | Network-level failure | $cause |
SignatureVerificationException | Invalid or expired webhook signature | — |
429, 5xx, network, and timeout failures up to $maxRetries
with backoff, so a thrown exception means retries were exhausted.
A 403 insufficient_scope error (scoped key used outside its grant) comes back
as ApiException with $status = 403 and $apiCode = "insufficient_scope".
See Authentication → Scopes.
Webhooks
verify() checks the HMAC-SHA256 signature and validates that the timestamp
is within 300 seconds (configurable via $toleranceSeconds). It throws
SignatureVerificationException on any failure — empty secret, malformed
header, stale timestamp, or signature mismatch.
See Webhooks for the full event-type list and payload schema.
Laravel
Notification channel, mail transport, and webhook middleware.
Symfony
Bundle autowiring and webhook verifier.
Sending
Channels, scheduling, and delivery lifecycle.
API Reference
The underlying REST endpoints.