Sendpit Developer Documentation
1. Introduction
Sendpit is an email testing platform designed for development and QA workflows. It provides isolated SMTP mailboxes that capture outgoing emails, allowing teams to test email functionality without sending messages to real recipients.
Sendpit offers a full-featured REST API for programmatic access to messages, search, webhooks, and inbox management, along with realtime WebSocket events for instant notifications.
Quick Start: If you just need to configure SMTP and send test emails, see the Integration Guide for language-specific examples (PHP, Node.js, Python, Ruby, Java, Go).
Who This Documentation Is For
- Backend developers integrating email sending into applications
- QA engineers validating email content and delivery
- DevOps teams setting up CI/CD pipelines with email verification
- Technical teams building automation around email testing
Common Use Cases
- Development Testing: Capture emails from local or staging environments
- CI/CD Integration: Verify transactional emails as part of automated test suites using the REST API and Wait endpoint
- QA Verification: Inspect email content, headers, attachments, and spam scores via API
- Webhook-Driven Automation: Trigger external workflows when emails are received
- Inbox Management: Create and delete mailboxes programmatically via the API
2. Getting Started
Creating an Organization
- Sign up at
https://sendpit.com/register - An organization is automatically created for your account
- You become the organization owner with full administrative access
Organizations are the top-level container for all resources. Team members, mailboxes, and billing are scoped to the organization.
Creating a Mailbox
- Navigate to Mailboxes in the dashboard
- Click Create Mailbox
- Enter a descriptive name (e.g., "Staging Environment", "CI Pipeline")
- Sendpit generates unique SMTP credentials automatically
Each mailbox receives:
- Username:
mb_followed by 16 random characters - Password: 32 random characters
- SMTP Host:
smtp.sendpit.com - SMTP Port:
1025
Sending a Test Email
Configure your application's SMTP settings:
MAIL_MAILER=smtp
MAIL_HOST=smtp.sendpit.com
MAIL_PORT=1025
MAIL_USERNAME=mb_xxxxxxxxxxxxxxxx
MAIL_PASSWORD=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
MAIL_ENCRYPTION=null
Send an email using your application's normal email functionality. The message will appear in Sendpit within seconds.
Viewing Received Emails
- Navigate to the mailbox in the dashboard
- Emails are listed in reverse chronological order
- Click any email to view full content, headers, and attachments
3. Authentication & API Access
API Token Authentication
Sendpit uses Bearer token authentication via Sanctum. All API requests must include a valid token in the Authorization header:
Authorization: Bearer sp_1|a8f3bc91d4e7...
Creating API Tokens
- Open the Sendpit dashboard
- Navigate to the mailbox you want API access for
- Go to Settings > API Tokens
- Click Create Token
- Copy and securely store the token — it is only shown once
Token Scopes
Tokens are scoped to control access. There are two scope types:
| Scope | Format | Access |
|---|---|---|
| Mailbox | mailbox:{id} |
Messages, search, wait, webhooks, attachments, and inspections for that mailbox |
| Organization | organization:{id} |
Inbox CRUD (list, create, delete mailboxes) for that organization |
A token carries exactly one scope. To access both mailbox messages and organization management, create separate tokens.
Plan Gating
API access is gated by your organization's plan:
| Plan | API Access | Rate Limit |
|---|---|---|
| Free | No | — |
| Basic | Yes | 60 requests/minute |
| Pro | Yes | 120 requests/minute |
| Max | Yes | 300 requests/minute |
Free plan tokens receive a 403 error on all API endpoints.
SMTP Authentication
SMTP access uses per-mailbox credentials. Each mailbox has independent credentials that:
- Are generated automatically on mailbox creation
- Can be regenerated at any time (invalidates previous credentials)
- Are stored encrypted at rest
4. Mailboxes
What a Mailbox Represents
A mailbox is an isolated SMTP endpoint that:
- Accepts emails via SMTP on port 1025
- Stores messages for later inspection
- Can trigger webhooks on email receipt
- Has its own access control (team members can be granted per-mailbox access)
Address Format
Emails sent to a mailbox can use any address format:
anything@sendpit.com # Routed by SMTP credentials
user@yourdomain.com # Captured based on SMTP auth
noreply@staging.example.org # Any sender/recipient works
The routing is determined by SMTP authentication, not the email address.
Per-Mailbox Behavior
Each mailbox maintains:
- Independent email storage
- Separate webhook configurations
- Individual access control lists
- Isolated credential sets
Lifecycle Considerations
- Deletion: Deleting a mailbox permanently removes all associated emails
- Credential Regeneration: Invalidates existing SMTP credentials immediately
- Plan Downgrade: Excess mailboxes become read-only; new emails are rejected
5. Emails & Attachments
Compact Schema (List View)
When listing messages, each email includes these fields:
| Field | Type | Description |
|---|---|---|
id |
string | Unique message identifier |
message_id |
string|null | RFC 5322 Message-ID header |
from |
object | {address, name} — sender |
to |
array | [{address, name}] — recipients |
cc |
array | [{address, name}] — CC recipients |
subject |
string | Email subject line |
received_at |
string | ISO 8601 timestamp |
size_bytes |
integer|null | Total message size in bytes |
is_read |
boolean | Whether the email has been viewed |
has_attachments |
boolean | Whether attachments are present |
attachment_count |
integer | Number of attachments |
has_codes |
boolean | Whether OTP/verification codes were extracted |
has_links |
boolean | Whether links were extracted |
spam_score |
number|null | Spam analysis score |
is_spam |
boolean|null | Whether classified as spam |
Full Schema (Detail View)
The detail view includes all compact fields plus:
| Field | Type | Description |
|---|---|---|
in_reply_to |
string|null | In-Reply-To header value |
bcc |
array | [{address, name}] — BCC recipients |
is_starred |
boolean | User-applied star flag |
labels |
array | Applied label IDs |
bodies.text |
string|null | Plain text body |
bodies.html |
string|null | HTML body |
bodies.text_size_bytes |
integer|null | Text body size |
bodies.html_size_bytes |
integer|null | HTML body size |
headers |
object | Full parsed email headers |
attachments |
array | See attachment object below |
extractions.codes |
array | Extracted OTP/verification codes |
extractions.links |
array | Extracted links |
spam.score |
number|null | Spam score |
spam.threshold |
number|null | Spam threshold |
spam.is_spam |
boolean|null | Spam classification |
spam.is_analyzed |
boolean | Whether spam analysis completed |
spam.analyzed_at |
string|null | ISO 8601 analysis timestamp |
content_hash |
string|null | Content hash for deduplication |
Attachment Object
Each attachment in the attachments array:
| Field | Type | Description |
|---|---|---|
index |
integer | Zero-based index |
filename |
string|null | Original filename |
content_type |
string|null | MIME type |
size_bytes |
integer|null | File size in bytes |
download_url |
string | API URL to download the file |
Attachment Access
- Attachments are accessible only through authenticated API requests
- Download URLs redirect to time-limited signed URLs (1 hour expiry)
- Attachments are not publicly accessible
6. Webhooks
Overview
Webhooks provide real-time HTTP notifications when emails are received, deleted, or cleared. Instead of polling, your application receives a POST request immediately after an event occurs.
Availability & Plans
| Plan | Webhook Access | Webhooks per Mailbox |
|---|---|---|
| Free | Not available | 0 |
| Basic | Not available | 0 |
| Pro | Available | 5 |
| Max | Available | 20 |
Creating a Webhook
Webhooks can be created via the dashboard or the REST API (see Webhook CRUD in the API Reference).
Via Dashboard:
- Navigate to a mailbox in the dashboard
- Open Settings > Webhooks
- Click Add Webhook
- Enter your HTTPS endpoint URL
- Optionally add a description
- Save the webhook
Upon creation, Sendpit generates a unique signing secret. Store this secret securely — it's required for signature verification.
Requirements:
- Endpoint must use HTTPS (HTTP is rejected)
- Endpoint must respond within 10 seconds
- Endpoint must return a 2xx status code
Event Types
| Event | Description | Payload Version |
|---|---|---|
message.received |
New email arrives | v1, v2 |
message.deleted |
Single message deleted | v2 only |
messages.cleared |
All messages cleared from mailbox | v2 only |
Webhook Payload
Example message.received Payload:
{
"event": "message.received",
"timestamp": "2026-03-15T14:32:18+00:00",
"webhook_id": 42,
"mailbox": {
"id": 15,
"name": "Staging Environment"
},
"email": {
"message_id": "<abc123@mail.example.com>",
"from": "sender@example.com",
"to": ["recipient@yourdomain.com"],
"subject": "Order Confirmation #12345",
"received_at": "2026-03-15T14:32:17+00:00"
}
}
Important: The payload contains metadata only. Email body content and attachments are not included. Use the REST API to retrieve full message content.
Security & Signature Verification
Every webhook request includes cryptographic signatures for verification.
Headers Sent:
| Header | Description | Format | Example |
|---|---|---|---|
X-Sendpit-Signature |
HMAC-SHA256 signature for payload verification | sha256=<hex> |
sha256=a1b2c3... |
X-Sendpit-Timestamp |
Unix timestamp when request was sent | Integer (seconds) | 1705329138 |
X-Sendpit-Webhook-Id |
Webhook configuration ID from your dashboard | Integer | 42 |
X-Sendpit-Event |
Event type that triggered the webhook | String | message.received |
Content-Type |
Payload content type | MIME type | application/json |
User-Agent |
Sendpit webhook client identifier | String | Sendpit-Webhook/1.0 |
Signature Verification (Recommended):
import hmac
import hashlib
import time
def verify_signature(secret: str, timestamp: str, payload: str, signature: str) -> bool:
# Check timestamp is within 5 minutes (replay protection)
current_time = int(time.time())
request_time = int(timestamp)
if abs(current_time - request_time) > 300:
return False
# Compute expected signature
message = f"{timestamp}.{payload}"
expected = "sha256=" + hmac.new(
secret.encode(),
message.encode(),
hashlib.sha256
).hexdigest()
# Constant-time comparison
return hmac.compare_digest(expected, signature)
const crypto = require('crypto');
function verifySignature(secret, timestamp, payload, signature) {
// Check timestamp is within 5 minutes
const currentTime = Math.floor(Date.now() / 1000);
const requestTime = parseInt(timestamp, 10);
if (Math.abs(currentTime - requestTime) > 300) {
return false;
}
// Compute expected signature
const message = `${timestamp}.${payload}`;
const expected = 'sha256=' + crypto
.createHmac('sha256', secret)
.update(message)
.digest('hex');
// Constant-time comparison
return crypto.timingSafeEqual(
Buffer.from(expected),
Buffer.from(signature)
);
}
function verifySignature(string $secret, string $timestamp, string $payload, string $signature): bool
{
// Check timestamp is within 5 minutes
$currentTime = time();
$requestTime = (int) $timestamp;
if (abs($currentTime - $requestTime) > 300) {
return false;
}
// Compute expected signature
$message = $timestamp . '.' . $payload;
$expected = 'sha256=' . hash_hmac('sha256', $message, $secret);
// Constant-time comparison
return hash_equals($expected, $signature);
}
Delivery, Retries, and Failure Behavior
Retry Schedule:
If delivery fails, Sendpit retries with exponential backoff:
| Attempt | Delay After Failure |
|---|---|
| 1 | Immediate |
| 2 | 1 minute |
| 3 | 5 minutes |
| 4 | 15 minutes |
| 5 | 1 hour |
| 6 | 2 hours (final) |
Retriable Status Codes: 408, 429, 500, 502, 503, 504
Non-Retriable Failures: Other 4xx errors, timeouts beyond 10 seconds, connection failures.
Auto-Disable Behavior: After 10 consecutive failures, the webhook is automatically disabled. Re-enable it via the dashboard or the API.
Webhook Logs & Retention
Each webhook delivery attempt is logged with timestamp, HTTP status code, response time, error message, and delivery status (success, failed, retrying).
Retention: Logs are retained for 30 days and then permanently deleted.
7. REST API Reference
Base URL
https://sendpit.com/api/v1
Required Headers
| Header | Value | Required |
|---|---|---|
Authorization |
Bearer {token} |
Yes |
Accept |
application/json |
Recommended |
Content-Type |
application/json |
For POST/PUT requests |
Error Format
All errors follow a consistent structure:
{
"error": {
"code": "not_found",
"message": "Message not found.",
"details": []
}
}
Error Codes
| Code | HTTP Status | Description |
|---|---|---|
unauthenticated |
401 | Missing or invalid Bearer token |
forbidden |
403 | Token lacks required scope |
plan_required |
403 | Feature not available on your plan |
plan_limit |
403 | Plan quota exceeded (e.g., max webhooks) |
not_found |
404 | Resource does not exist or is not accessible |
validation_failed |
422 | Request body or parameters failed validation |
conflict |
409 | Conflicting state (e.g., multiple matches on wait with max_results=1) |
rate_limited |
429 | Rate limit exceeded |
server_error |
500 | Internal server error |
Observability Headers
Every API response includes:
| Header | Description | Example |
|---|---|---|
X-Request-Id |
Unique identifier for the request | 9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d |
X-Processing-Time-Ms |
Server-side processing time in milliseconds | 42 |
Include X-Request-Id in support tickets for faster debugging.
Rate Limiting
Rate limits are enforced per-token on a per-minute sliding window.
Response Headers:
| Header | Description |
|---|---|
X-RateLimit-Limit |
Maximum requests per minute |
X-RateLimit-Remaining |
Remaining requests in the current window |
Retry-After |
Seconds until the rate limit resets (only on 429 responses) |
When rate-limited, the API returns 429 Too Many Requests.
Idempotency
For DELETE operations, you can include an X-Idempotency-Key header to ensure the operation is only processed once, even if retried:
X-Idempotency-Key: my-unique-key-12345
Cursor Pagination
List endpoints use cursor-based pagination. Pass after, before, and limit query parameters.
Paginated Response Envelope:
{
"data": [...],
"meta": {
"next_cursor": "eyJjIjoiMjAyNi0wMy0xNVQxNDozMjoxNy4wMDArMDA6MDAiLCJpZCI6IjY1YTFiMmMzZDRlNWY2Nzg5MGFiY2RlZiJ9",
"has_more": true,
"limit": 25,
"count": 25
}
}
| Parameter | Type | Default | Description |
|---|---|---|---|
after |
string | — | Cursor to fetch the next page (from meta.next_cursor) |
before |
string | — | Cursor to fetch the previous page |
limit |
integer | 25 | Number of items per page (1-100) |
Messages
List Messages
Retrieve messages for the authenticated mailbox with optional filters and cursor pagination.
GET /messages
Query Parameters:
| Parameter | Type | Description |
|---|---|---|
to |
string | Filter by recipient address (substring match) |
from |
string | Filter by sender address (substring match) |
subject |
string | Filter by subject (substring match) |
since |
date | Messages received after this date |
until |
date | Messages received before this date |
has_attachments |
boolean | Filter by attachment presence |
unread |
boolean | Filter by read status |
after |
string | Pagination cursor |
limit |
integer | Results per page (1-100, default 25) |
Example Request:
curl -s "https://sendpit.com/api/v1/messages?from=noreply&has_attachments=true&limit=10" \
-H "Authorization: Bearer sp_1|a8f3bc91d4e7..." \
-H "Accept: application/json"
Example Response:
{
"data": [
{
"id": "65a1b2c3d4e5f67890abcdef",
"message_id": "<20260315143217.abc123@mail.example.com>",
"from": {
"address": "noreply@example.com",
"name": null
},
"to": [
{
"address": "user@staging.myapp.com",
"name": null
}
],
"cc": [],
"subject": "Your invoice #INV-2026-0042 is ready",
"received_at": "2026-03-15T14:32:17+00:00",
"size_bytes": 18432,
"is_read": false,
"has_attachments": true,
"attachment_count": 1,
"has_codes": false,
"has_links": true,
"spam_score": 1.2,
"is_spam": false
}
],
"meta": {
"next_cursor": "eyJjIjoiMjAyNi0wMy0xNVQxNDozMjoxNy4wMDArMDA6MDAiLCJpZCI6IjY1YTFiMmMzZDRlNWY2Nzg5MGFiY2RlZiJ9",
"has_more": true,
"limit": 10,
"count": 10
}
}
Get Message
Retrieve full detail for a single message.
GET /messages/{id}
Example Request:
curl -s "https://sendpit.com/api/v1/messages/65a1b2c3d4e5f67890abcdef" \
-H "Authorization: Bearer sp_1|a8f3bc91d4e7..." \
-H "Accept: application/json"
Example Response:
{
"data": {
"id": "65a1b2c3d4e5f67890abcdef",
"message_id": "<20260315143217.abc123@mail.example.com>",
"from": {
"address": "noreply@example.com",
"name": null
},
"to": [
{
"address": "user@staging.myapp.com",
"name": null
}
],
"cc": [],
"subject": "Your invoice #INV-2026-0042 is ready",
"received_at": "2026-03-15T14:32:17+00:00",
"size_bytes": 18432,
"is_read": true,
"has_attachments": true,
"attachment_count": 1,
"has_codes": false,
"has_links": true,
"spam_score": 1.2,
"is_spam": false,
"in_reply_to": null,
"bcc": [],
"is_starred": false,
"labels": [],
"bodies": {
"text": "Your invoice #INV-2026-0042 is attached.\n\nTotal: $149.99\nDue: March 30, 2026",
"html": "<html><body><h1>Invoice #INV-2026-0042</h1><p>Total: $149.99</p></body></html>",
"text_size_bytes": 72,
"html_size_bytes": 1024
},
"headers": {
"Message-ID": "<20260315143217.abc123@mail.example.com>",
"Date": "Sat, 15 Mar 2026 14:32:17 +0000",
"From": "noreply@example.com",
"To": "user@staging.myapp.com",
"Subject": "Your invoice #INV-2026-0042 is ready",
"Content-Type": "multipart/mixed; boundary=\"----=_Part_123\"",
"X-Mailer": "MyApp/3.2.1"
},
"attachments": [
{
"index": 0,
"filename": "invoice-2026-0042.pdf",
"content_type": "application/pdf",
"size_bytes": 15360,
"download_url": "https://sendpit.com/api/v1/messages/65a1b2c3d4e5f67890abcdef/attachments/0"
}
],
"extractions": {
"codes": [],
"links": ["https://myapp.com/invoices/INV-2026-0042"]
},
"spam": {
"score": 1.2,
"threshold": 5.0,
"is_spam": false,
"is_analyzed": true,
"analyzed_at": "2026-03-15T14:32:18+00:00"
},
"content_hash": "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
}
}
Delete Message
Delete a single message by ID.
DELETE /messages/{id}
Example Request:
curl -s -X DELETE "https://sendpit.com/api/v1/messages/65a1b2c3d4e5f67890abcdef" \
-H "Authorization: Bearer sp_1|a8f3bc91d4e7..." \
-H "X-Idempotency-Key: del-msg-65a1b2c3"
Response: 204 No Content
Delete All Messages
Delete all messages in the authenticated mailbox.
DELETE /messages
Example Request:
curl -s -X DELETE "https://sendpit.com/api/v1/messages" \
-H "Authorization: Bearer sp_1|a8f3bc91d4e7..." \
-H "X-Idempotency-Key: clear-mailbox-20260315"
Response: 204 No Content
Download Attachment
Download an attachment by message ID and zero-based index. Returns a redirect to a time-limited signed URL.
GET /messages/{id}/attachments/{index}
Example Request:
curl -sL "https://sendpit.com/api/v1/messages/65a1b2c3d4e5f67890abcdef/attachments/0" \
-H "Authorization: Bearer sp_1|a8f3bc91d4e7..." \
-o invoice.pdf
Response: 302 Redirect to a signed download URL (valid for 1 hour).
Search
Search Messages
Perform a structured search with advanced filters. Supports all list filters plus body content, CC/BCC, headers, extracted codes/links, and spam score ranges.
POST /messages/search
Request Body:
| Field | Type | Description |
|---|---|---|
filters.from |
string | Sender address (substring match) |
filters.to |
string | Recipient address (substring match) |
filters.cc |
string | CC address (substring match) |
filters.bcc |
string | BCC address (substring match) |
filters.subject |
string | Subject (substring match) |
filters.body |
string | Body content (substring match, max 1000 chars) |
filters.headers |
object | Header key-value pairs to match (max 10 headers) |
filters.received_after |
date | Messages received after this date |
filters.received_before |
date | Messages received before this date |
filters.has_attachments |
boolean | Filter by attachment presence |
filters.unread |
boolean | Filter by read status |
filters.has_codes |
boolean | Filter by extracted OTP/verification codes |
filters.has_links |
boolean | Filter by extracted links |
filters.spam_score_min |
number | Minimum spam score |
filters.spam_score_max |
number | Maximum spam score |
after |
string | Pagination cursor |
limit |
integer | Results per page (1-100, default 25) |
Example Request:
curl -s -X POST "https://sendpit.com/api/v1/messages/search" \
-H "Authorization: Bearer sp_1|a8f3bc91d4e7..." \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{
"filters": {
"from": "noreply@example.com",
"subject": "verification",
"has_codes": true,
"received_after": "2026-03-14T00:00:00Z"
},
"limit": 5
}'
Example Response:
{
"data": [
{
"id": "65a1b2c3d4e5f67890abcdf0",
"message_id": "<20260315091023.xyz789@mail.example.com>",
"from": {
"address": "noreply@example.com",
"name": null
},
"to": [
{
"address": "newuser@staging.myapp.com",
"name": null
}
],
"cc": [],
"subject": "Your verification code is 847291",
"received_at": "2026-03-15T09:10:23+00:00",
"size_bytes": 4096,
"is_read": false,
"has_attachments": false,
"attachment_count": 0,
"has_codes": true,
"has_links": true,
"spam_score": 0.3,
"is_spam": false
}
],
"meta": {
"next_cursor": null,
"has_more": false,
"limit": 5,
"count": 1
}
}
Wait (Long-Poll)
Wait for Messages
Long-poll until one or more messages matching your filters arrive, or timeout. This is ideal for CI/CD pipelines and test automation where you need to wait for an email to be delivered.
POST /messages/wait
Plan Requirement: Basic or higher (with wait API enabled).
Request Body:
| Field | Type | Default | Description |
|---|---|---|---|
filters.from |
string | — | Sender address (substring match) |
filters.to |
string | — | Recipient address (substring match) |
filters.cc |
string | — | CC address (substring match) |
filters.subject |
string | — | Subject (substring match) |
filters.body |
string | — | Body content (substring match) |
filters.headers |
object | — | Header key-value pairs to match |
filters.received_after |
date | — | Messages received after this date |
filters.received_before |
date | — | Messages received before this date |
filters.has_attachments |
boolean | — | Filter by attachment presence |
filters.unread |
boolean | — | Filter by read status |
timeout |
integer | 15 | Seconds to wait (1-30) |
max_results |
integer | 10 | Maximum messages to return (1-100) |
after_cursor |
string | — | Only return messages after this cursor |
Note: Async-derived filters (has_codes, has_links, spam_score_min, spam_score_max) are not supported in the wait API because they depend on post-processing that may not complete before the timeout. Use POST /messages/search instead.
Example Request:
curl -s -X POST "https://sendpit.com/api/v1/messages/wait" \
-H "Authorization: Bearer sp_1|a8f3bc91d4e7..." \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{
"filters": {
"to": "newuser@staging.myapp.com",
"subject": "Welcome"
},
"timeout": 20,
"max_results": 1
}'
Response Codes:
| Status | Meaning |
|---|---|
200 |
Matching messages found |
204 |
Timeout — no matching messages arrived |
409 |
Conflict — multiple messages matched with max_results=1 |
Example 200 Response:
{
"data": [
{
"id": "65a1b2c3d4e5f67890abcdf1",
"message_id": "<20260315143500.welcome@mail.example.com>",
"from": {
"address": "welcome@example.com",
"name": null
},
"to": [
{
"address": "newuser@staging.myapp.com",
"name": null
}
],
"cc": [],
"subject": "Welcome to Example!",
"received_at": "2026-03-15T14:35:00+00:00",
"size_bytes": 8192,
"is_read": false,
"has_attachments": false,
"attachment_count": 0,
"has_codes": true,
"has_links": true,
"spam_score": 0.1,
"is_spam": false
}
],
"meta": {
"next_cursor": null,
"has_more": false,
"limit": 1,
"count": 1
}
}
Example 409 Response:
{
"error": {
"code": "conflict",
"message": "Multiple messages match the filter with max_results=1. Broaden timeout or narrow filters."
}
}
Inspection Endpoints
These endpoints provide focused access to specific parts of a message. All require a mailbox-scoped token.
Raw Email
Download the raw RFC 2822 .eml file.
GET /messages/{id}/raw
curl -s "https://sendpit.com/api/v1/messages/65a1b2c3d4e5f67890abcdef/raw" \
-H "Authorization: Bearer sp_1|a8f3bc91d4e7..." \
-o message.eml
Response: 200 OK with Content-Type: message/rfc822
Headers
Retrieve parsed email headers as JSON.
GET /messages/{id}/headers
curl -s "https://sendpit.com/api/v1/messages/65a1b2c3d4e5f67890abcdef/headers" \
-H "Authorization: Bearer sp_1|a8f3bc91d4e7..." \
-H "Accept: application/json"
Example Response:
{
"data": {
"Message-ID": "<20260315143217.abc123@mail.example.com>",
"Date": "Sat, 15 Mar 2026 14:32:17 +0000",
"From": "noreply@example.com",
"To": "user@staging.myapp.com",
"Subject": "Your invoice #INV-2026-0042 is ready",
"Content-Type": "multipart/mixed; boundary=\"----=_Part_123\"",
"DKIM-Signature": "v=1; a=rsa-sha256; d=example.com; ...",
"X-Mailer": "MyApp/3.2.1"
}
}
Plain Text Body
Retrieve the plain text body.
GET /messages/{id}/body/text
curl -s "https://sendpit.com/api/v1/messages/65a1b2c3d4e5f67890abcdef/body/text" \
-H "Authorization: Bearer sp_1|a8f3bc91d4e7..."
Response: 200 OK with Content-Type: text/plain; charset=utf-8
Your invoice #INV-2026-0042 is attached.
Total: $149.99
Due: March 30, 2026
HTML Body
Retrieve the HTML body wrapped in JSON (for XSS safety).
GET /messages/{id}/body/html
curl -s "https://sendpit.com/api/v1/messages/65a1b2c3d4e5f67890abcdef/body/html" \
-H "Authorization: Bearer sp_1|a8f3bc91d4e7..." \
-H "Accept: application/json"
Example Response:
{
"data": {
"html": "<html><body><h1>Invoice #INV-2026-0042</h1><p>Total: $149.99</p></body></html>"
}
}
Attachments List
List all attachments for a message with download URLs.
GET /messages/{id}/attachments
curl -s "https://sendpit.com/api/v1/messages/65a1b2c3d4e5f67890abcdef/attachments" \
-H "Authorization: Bearer sp_1|a8f3bc91d4e7..." \
-H "Accept: application/json"
Example Response:
{
"data": [
{
"index": 0,
"filename": "invoice-2026-0042.pdf",
"content_type": "application/pdf",
"size_bytes": 15360,
"download_url": "https://sendpit.com/api/v1/messages/65a1b2c3d4e5f67890abcdef/attachments/0"
}
]
}
Extractions
Retrieve extracted OTP codes and links from the message body.
GET /messages/{id}/extractions
curl -s "https://sendpit.com/api/v1/messages/65a1b2c3d4e5f67890abcdf0/extractions" \
-H "Authorization: Bearer sp_1|a8f3bc91d4e7..." \
-H "Accept: application/json"
Example Response:
{
"data": {
"codes": ["847291"],
"links": [
"https://example.com/verify?token=abc123",
"https://example.com/unsubscribe"
],
"content_extracted_at": "2026-03-15T09:10:24+00:00"
}
}
Inspection
Retrieve spam analysis results and message metadata.
GET /messages/{id}/inspection
curl -s "https://sendpit.com/api/v1/messages/65a1b2c3d4e5f67890abcdef/inspection" \
-H "Authorization: Bearer sp_1|a8f3bc91d4e7..." \
-H "Accept: application/json"
Example Response:
{
"data": {
"spam": {
"score": 1.2,
"threshold": 5.0,
"is_spam": false,
"is_analyzed": true,
"analyzed_at": "2026-03-15T14:32:18+00:00",
"analysis_duration_ms": 85
},
"metadata": {
"size_bytes": 18432,
"text_size_bytes": 72,
"html_size_bytes": 1024,
"content_hash": "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"message_id": "<20260315143217.abc123@mail.example.com>",
"in_reply_to": null
}
}
}
Timeline
Retrieve the processing timeline for a message, showing when each stage of processing occurred.
GET /messages/{id}/timeline
curl -s "https://sendpit.com/api/v1/messages/65a1b2c3d4e5f67890abcdef/timeline" \
-H "Authorization: Bearer sp_1|a8f3bc91d4e7..." \
-H "Accept: application/json"
Example Response:
{
"data": [
{
"event": "received",
"at": "2026-03-15T14:32:17+00:00"
},
{
"event": "webhooks_dispatched",
"at": "2026-03-15T14:32:17+00:00"
},
{
"event": "spam_analyzed",
"at": "2026-03-15T14:32:18+00:00",
"duration_ms": 85
},
{
"event": "content_extracted",
"at": "2026-03-15T14:32:18+00:00"
}
]
}
Timeline Events:
| Event | Description |
|---|---|
received |
Email was received by Sendpit |
webhooks_dispatched |
Webhook notifications were sent |
spam_analyzed |
Spam analysis completed (includes duration_ms) |
content_extracted |
OTP codes and links were extracted |
smart_rules_applied |
Smart rules were evaluated and applied |
Webhook CRUD
Manage webhooks programmatically via the API. Requires a mailbox-scoped token.
List Webhooks
GET /webhooks
curl -s "https://sendpit.com/api/v1/webhooks" \
-H "Authorization: Bearer sp_1|a8f3bc91d4e7..." \
-H "Accept: application/json"
Example Response:
{
"data": [
{
"id": 42,
"mailbox_id": 15,
"endpoint_url": "https://ci.example.com/hooks/sendpit",
"is_active": true,
"description": "CI pipeline webhook",
"event_types": ["message.received"],
"payload_version": 2,
"last_triggered_at": "2026-03-15T14:32:18+00:00",
"last_success_at": "2026-03-15T14:32:18+00:00",
"last_failure_at": null,
"consecutive_failures": 0,
"created_at": "2026-03-01T10:00:00+00:00",
"updated_at": "2026-03-15T14:32:18+00:00"
}
]
}
Create Webhook
POST /webhooks
Request Body:
| Field | Type | Required | Description |
|---|---|---|---|
endpoint_url |
string | Yes | HTTPS URL for webhook delivery (max 2048 chars) |
secret |
string | No | Signing secret (min 16 chars). Auto-generated if omitted |
description |
string | No | Human-readable description (max 255 chars) |
event_types |
array | No | Events to subscribe to (default: ["message.received"]) |
payload_version |
integer | No | Payload format version: 1 or 2 (default: 2) |
Valid event types: message.received, message.deleted, messages.cleared
Note: message.deleted and messages.cleared require payload_version: 2.
curl -s -X POST "https://sendpit.com/api/v1/webhooks" \
-H "Authorization: Bearer sp_1|a8f3bc91d4e7..." \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{
"endpoint_url": "https://ci.example.com/hooks/sendpit",
"description": "CI pipeline webhook",
"event_types": ["message.received", "message.deleted"],
"payload_version": 2
}'
Example Response (201 Created):
{
"data": {
"id": 43,
"mailbox_id": 15,
"endpoint_url": "https://ci.example.com/hooks/sendpit",
"is_active": true,
"description": "CI pipeline webhook",
"event_types": ["message.received", "message.deleted"],
"payload_version": 2,
"last_triggered_at": null,
"last_success_at": null,
"last_failure_at": null,
"consecutive_failures": 0,
"created_at": "2026-03-15T15:00:00+00:00",
"updated_at": "2026-03-15T15:00:00+00:00"
}
}
Get Webhook
GET /webhooks/{id}
curl -s "https://sendpit.com/api/v1/webhooks/42" \
-H "Authorization: Bearer sp_1|a8f3bc91d4e7..." \
-H "Accept: application/json"
Response: Same schema as the webhook object shown above.
Update Webhook
PUT /webhooks/{id}
All fields are optional. Only include the fields you want to change.
| Field | Type | Description |
|---|---|---|
endpoint_url |
string | New endpoint URL |
secret |
string | New signing secret (min 16 chars) |
is_active |
boolean | Enable or disable the webhook |
description |
string|null | Updated description |
event_types |
array | Updated event subscriptions |
payload_version |
integer | 1 or 2 |
curl -s -X PUT "https://sendpit.com/api/v1/webhooks/42" \
-H "Authorization: Bearer sp_1|a8f3bc91d4e7..." \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{
"is_active": false,
"description": "Disabled for maintenance"
}'
Example Response:
{
"data": {
"id": 42,
"mailbox_id": 15,
"endpoint_url": "https://ci.example.com/hooks/sendpit",
"is_active": false,
"description": "Disabled for maintenance",
"event_types": ["message.received"],
"payload_version": 2,
"last_triggered_at": "2026-03-15T14:32:18+00:00",
"last_success_at": "2026-03-15T14:32:18+00:00",
"last_failure_at": null,
"consecutive_failures": 0,
"created_at": "2026-03-01T10:00:00+00:00",
"updated_at": "2026-03-15T16:00:00+00:00"
}
}
Delete Webhook
DELETE /webhooks/{id}
curl -s -X DELETE "https://sendpit.com/api/v1/webhooks/42" \
-H "Authorization: Bearer sp_1|a8f3bc91d4e7..."
Response: 204 No Content
Webhook Delivery Logs
Retrieve delivery logs for a specific webhook.
GET /webhooks/{id}/deliveries
Query Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
per_page |
integer | 25 | Results per page |
curl -s "https://sendpit.com/api/v1/webhooks/42/deliveries?per_page=10" \
-H "Authorization: Bearer sp_1|a8f3bc91d4e7..." \
-H "Accept: application/json"
Example Response:
{
"data": [
{
"id": 1001,
"webhook_id": 42,
"event_type": "message.received",
"status": "success",
"http_status": 200,
"duration_ms": 142,
"attempt_number": 1,
"error_message": null,
"triggered_at": "2026-03-15T14:32:18+00:00",
"delivered_at": "2026-03-15T14:32:18+00:00",
"created_at": "2026-03-15T14:32:18+00:00"
},
{
"id": 998,
"webhook_id": 42,
"event_type": "message.received",
"status": "failed",
"http_status": 503,
"duration_ms": 10012,
"attempt_number": 3,
"error_message": "Service Unavailable",
"triggered_at": "2026-03-14T22:10:05+00:00",
"delivered_at": null,
"created_at": "2026-03-14T22:15:05+00:00"
}
]
}
Inboxes (Organization-Scoped)
Manage mailboxes programmatically. Requires an organization-scoped token (organization:{id}).
List Inboxes
GET /inboxes
curl -s "https://sendpit.com/api/v1/inboxes" \
-H "Authorization: Bearer sp_1|org_token..." \
-H "Accept: application/json"
Example Response:
{
"data": [
{
"id": 15,
"name": "Staging Environment",
"email_address": "mb_a1b2c3d4e5f67890@sendpit.com",
"smtp_username": "mb_a1b2c3d4e5f67890",
"created_at": "2026-02-01T10:00:00+00:00",
"message_count": 247
},
{
"id": 16,
"name": "CI Pipeline",
"email_address": "mb_f8e7d6c5b4a39201@sendpit.com",
"smtp_username": "mb_f8e7d6c5b4a39201",
"created_at": "2026-03-01T08:30:00+00:00",
"message_count": 1832
}
]
}
Create Inbox
POST /inboxes
Request Body:
| Field | Type | Required | Description |
|---|---|---|---|
name |
string | Yes | Mailbox name (max 255 chars) |
curl -s -X POST "https://sendpit.com/api/v1/inboxes" \
-H "Authorization: Bearer sp_1|org_token..." \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{
"name": "E2E Test Suite"
}'
Example Response (201 Created):
{
"data": {
"id": 17,
"name": "E2E Test Suite",
"email_address": "mb_9c8d7e6f5a4b3210@sendpit.com",
"smtp_username": "mb_9c8d7e6f5a4b3210",
"created_at": "2026-03-15T16:30:00+00:00",
"message_count": 0
}
}
Get Inbox
GET /inboxes/{id}
curl -s "https://sendpit.com/api/v1/inboxes/15" \
-H "Authorization: Bearer sp_1|org_token..." \
-H "Accept: application/json"
Response: Same schema as the inbox object shown above.
Delete Inbox
Permanently deletes a mailbox and all its associated messages.
DELETE /inboxes/{id}
curl -s -X DELETE "https://sendpit.com/api/v1/inboxes/17" \
-H "Authorization: Bearer sp_1|org_token..."
Response: 204 No Content
Rate Limit
Check Rate Limit Status
Retrieve your current rate limit status without consuming a rate limit point.
GET /rate-limit
curl -s "https://sendpit.com/api/v1/rate-limit" \
-H "Authorization: Bearer sp_1|a8f3bc91d4e7..." \
-H "Accept: application/json"
Example Response:
{
"data": {
"limit": 120,
"remaining": 98,
"resets_at": "2026-03-15T14:33:17+00:00"
}
}
8. Realtime Events (WebSocket)
Sendpit provides WebSocket-based realtime events for instant notification when messages arrive or are deleted. This uses the Pusher protocol via Laravel Reverb.
Plan Requirement
WebSocket subscriptions require Pro or Max plan. Free and Basic plan tokens receive a 403 from the auth endpoint. For Basic plan users, use the Wait API instead — it provides the same event-driven workflow over standard HTTP.
Channel
All events are broadcast on a private channel:
private-mailbox.{id}
Where {id} is the numeric mailbox ID.
Events
| Event Name | Trigger | Payload |
|---|---|---|
message.received |
New email arrives | {id, type, mailbox_id, occurred_at, data: {message fields}} |
message.deleted |
Single message deleted | {id, type, mailbox_id, occurred_at, data: {id}} |
messages.cleared |
All messages cleared | {id, type, mailbox_id, occurred_at, data: {count}} |
Authentication Flow
- Client connects to the WebSocket server:
wss://sendpit.com/app/{APP_KEY} - Client subscribes to
private-mailbox.{id} - The server sends an auth challenge
- Client POSTs to
/api/broadcasting/authwith the channel name and socket ID - The server verifies access and returns a signed auth token
- Client completes the subscription
Browser Clients (Laravel Echo)
import Echo from 'laravel-echo';
import Pusher from 'pusher-js';
window.Echo = new Echo({
broadcaster: 'reverb',
key: 'your-reverb-app-key',
wsHost: 'sendpit.com',
wsPort: 443,
wssPort: 443,
forceTLS: true,
enabledTransports: ['ws', 'wss'],
});
window.Echo.private(`mailbox.${mailboxId}`)
.listen('.message.received', (event) => {
console.log('New message:', event);
})
.listen('.message.deleted', (event) => {
console.log('Message deleted:', event);
})
.listen('.messages.cleared', (event) => {
console.log('Messages cleared:', event);
});
Non-Browser Clients (API Token Auth)
Non-browser clients authenticate using a Sanctum Bearer token on the broadcasting auth endpoint.
Node.js (pusher-js):
const Pusher = require('pusher-js');
const pusher = new Pusher('your-reverb-app-key', {
wsHost: 'sendpit.com',
wsPort: 443,
wssPort: 443,
forceTLS: true,
enabledTransports: ['ws', 'wss'],
channelAuthorization: {
endpoint: 'https://sendpit.com/api/broadcasting/auth',
headers: {
'Authorization': `Bearer ${API_TOKEN}`,
'Accept': 'application/json',
},
},
});
const channel = pusher.subscribe(`private-mailbox.${mailboxId}`);
channel.bind('message.received', (data) => {
console.log('New message:', data);
});
Python (pysher):
import pysher
import requests
def auth_callback(channel_name, socket_id):
response = requests.post(
'https://sendpit.com/api/broadcasting/auth',
json={'socket_id': socket_id, 'channel_name': channel_name},
headers={
'Authorization': f'Bearer {API_TOKEN}',
'Accept': 'application/json',
}
)
return response.json()
pusher = pysher.Pusher('your-reverb-app-key', custom_host='sendpit.com')
pusher.connection.bind('pusher:connection_established', lambda data: None)
channel = pusher.subscribe(f'private-mailbox.{mailbox_id}', auth_callback)
channel.bind('message.received', lambda data: print('New message:', data))
Manual Auth (Any HTTP Client)
If your language does not have a Pusher library:
- Connect to
wss://sendpit.com/app/{key}?protocol=7&client=manual - Receive
pusher:connection_establishedwithsocket_id - POST to
/api/broadcasting/auth:
curl -s -X POST "https://sendpit.com/api/broadcasting/auth" \
-H "Authorization: Bearer sp_1|a8f3bc91d4e7..." \
-H "Content-Type: application/json" \
-d '{"socket_id": "123.456", "channel_name": "private-mailbox.15"}'
Response:
{
"auth": "app-key:signature-hash"
}
- Send the subscribe message with the auth signature to the WebSocket.
9. Plans & Limits
Feature Availability by Plan
| Feature | Free | Basic | Pro | Max |
|---|---|---|---|---|
| Mailboxes | 1 | 3 | 10 | Unlimited |
| Team Members | 2 | 5 | 15 | Unlimited |
| Emails/Month | 200 | 1,000 | 5,000 | 100,000 |
| Email Retention | 7 days | 14 days | 60 days | 60 days |
| REST API | No | Yes (60 rpm) | Yes (120 rpm) | Yes (300 rpm) |
| Wait API | No | Yes (2 concurrent/token) | Yes (5 concurrent/token) | Yes (10 concurrent/token) |
| Webhooks | No | No | Yes (5/mailbox) | Yes (20/mailbox) |
| Realtime Events (WebSocket) | No | No | Yes | Yes |
| Spam Rule Breakdown | No | Yes | Yes | Yes |
| Link Checking | No | Yes | Yes | Yes |
| Enhanced Header Inspector | No | No | Yes | Yes |
| Share Links | No | No | Yes | Yes |
| Email Forwarding | No | No | Yes | Yes |
| Smart Rules | No | No | Yes (100 rules) | Yes (500 rules) |
Behavior on Downgrade
When downgrading to a lower plan:
- Excess mailboxes: Become read-only; new emails are rejected
- Excess team members: Retain access but no new invitations allowed
- Webhooks: Disabled if new plan doesn't support them; existing configs are preserved but inactive
- API tokens: Tokens remain but API requests return
403if API access is removed - Emails over retention: Purged according to new retention period
Limit Enforcement
Limits are checked at:
- Mailbox creation time
- User invitation time
- Email ingestion time
- Webhook creation time
- API request time (rate limits)
Attempting to exceed limits returns a 403 error with the plan_limit error code.
10. Error Handling & Status Codes
Common Errors
| Scenario | HTTP Status | Error Code | Message |
|---|---|---|---|
| Missing or invalid token | 401 | unauthenticated |
Unauthenticated |
| Token lacks required scope | 403 | forbidden |
Insufficient scope |
| API not available on plan | 403 | plan_required |
Your plan does not include API access |
| Mailbox limit reached | 403 | plan_limit |
Maximum mailboxes for your plan |
| Webhook limit reached | 403 | plan_limit |
Maximum webhooks per mailbox |
| Message not found | 404 | not_found |
Message not found |
| Invalid request body | 422 | validation_failed |
Validation details in error.details |
| Rate limit exceeded | 429 | rate_limited |
Too many requests |
| Invalid SMTP credentials | N/A | — | SMTP authentication failed |
Developer Recommendations
- Check plan limits before operations: Use
GET /rate-limitto check current rate limit status - Handle 403 gracefully: Display upgrade prompts or notify administrators
- Implement retry logic for transient failures: Use exponential backoff for 429 and 5xx responses
- Log error responses: Include
X-Request-Idfor debugging with support - Use cursor pagination: Prefer
after/limitover offset pagination for reliable iteration
11. Security Model
Tenant Isolation
- Organizations are fully isolated from each other
- Mailbox data is scoped to the owning organization
- API tokens are scoped to a single mailbox or organization
- Team members only see resources they have explicit access to
Attachment Access Control
Attachments are protected by:
- Authentication requirement (valid API token or logged-in user)
- Mailbox access verification (token must be scoped to the correct mailbox)
- Signed URLs with 1-hour expiration for downloads
Attachments are not publicly accessible.
Webhook Signing Guarantees
- Secrets are generated using cryptographically secure random bytes (256-bit)
- Secrets are encrypted at rest using AES-256
- Signatures use HMAC-SHA256
- Timestamp validation prevents replay attacks
- Secrets can be regenerated at any time (invalidates old signatures)
12. CI / Automation Examples
GitHub Actions with REST API
name: Email Integration Tests
on:
push:
branches: [main, develop]
jobs:
email-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Send test email via SMTP
run: |
# Your application sends an email here
npm run send-welcome-email -- --to test@staging.example.com
- name: Wait for email via API
run: |
RESPONSE=$(curl -s -X POST "https://sendpit.com/api/v1/messages/wait" \
-H "Authorization: Bearer ${{ secrets.SENDPIT_API_TOKEN }}" \
-H "Content-Type: application/json" \
-d '{
"filters": {
"to": "test@staging.example.com",
"subject": "Welcome"
},
"timeout": 30,
"max_results": 1
}')
echo "$RESPONSE" | jq .
# Verify the email arrived
SUBJECT=$(echo "$RESPONSE" | jq -r '.data[0].subject')
if [ "$SUBJECT" != "Welcome to Our Platform" ]; then
echo "Unexpected subject: $SUBJECT"
exit 1
fi
- name: Extract OTP code
run: |
MESSAGE_ID=$(echo "$RESPONSE" | jq -r '.data[0].id')
EXTRACTIONS=$(curl -s "https://sendpit.com/api/v1/messages/$MESSAGE_ID/extractions" \
-H "Authorization: Bearer ${{ secrets.SENDPIT_API_TOKEN }}")
OTP=$(echo "$EXTRACTIONS" | jq -r '.data.codes[0]')
echo "Extracted OTP: $OTP"
Webhook-Driven Test Workflow
# test_webhook_handler.py
import time
from flask import Flask, request
import threading
app = Flask(__name__)
received_emails = []
email_event = threading.Event()
@app.route('/webhook', methods=['POST'])
def webhook():
payload = request.get_json()
received_emails.append(payload)
email_event.set()
return '', 200
def wait_for_email(timeout=30):
"""Block until an email is received or timeout."""
email_event.clear()
if email_event.wait(timeout):
return received_emails[-1]
raise TimeoutError("No email received within timeout")
# In your test
def test_welcome_email():
register_user("test@example.com")
email = wait_for_email(timeout=10)
assert email['email']['subject'] == "Welcome to Our Platform"
assert email['email']['to'][0] == "test@example.com"
Local Development with ngrok
Step 1: Start ngrok
ngrok http 3000
Step 2: Configure webhook in Sendpit
Use the ngrok HTTPS URL as your webhook endpoint:
curl -s -X POST "https://sendpit.com/api/v1/webhooks" \
-H "Authorization: Bearer sp_1|a8f3bc91d4e7..." \
-H "Content-Type: application/json" \
-d '{
"endpoint_url": "https://a1b2c3d4.ngrok-free.app/webhooks/sendpit",
"description": "Local dev webhook"
}'
13. Versioning & Changelog Policy
API Versioning
The API is versioned via the URL path (/api/v1). The current version is v1.
- Additive changes (new fields, new endpoints): Deployed without notice. Your code should ignore unknown fields.
- Breaking changes (removed fields, type changes, removed endpoints): Announced 30 days in advance via email to organization owners. A new API version will be introduced.
Webhook Payload Versioning
Webhook payloads support version 1 and 2. When creating or updating a webhook, specify the payload_version field:
- Version 1: Supports
message.receivedevents only. - Version 2: Supports all event types (
message.received,message.deleted,messages.cleared).
Backward Compatibility
We commit to:
- Not removing existing response fields without notice
- Not changing field types without notice
- Maintaining signature algorithm compatibility
- Supporting deprecated pagination (offset-based) alongside cursor pagination
14. FAQ
Can I retrieve email content via API?
Yes. Use GET /messages/{id} for the full message including bodies, headers, and attachments. Use the inspection endpoints for focused access to specific parts (raw email, headers, text body, HTML body, extractions).
How do I wait for an email in my CI pipeline?
Use POST /messages/wait with your desired filters and a timeout. The endpoint long-polls until a matching email arrives or the timeout expires. See Wait for Messages.
Why doesn't the webhook payload include the email body?
For security and performance reasons. Email bodies can be large and may contain sensitive data. The webhook notifies you of receipt; use the REST API to retrieve full content.
What happens if my webhook endpoint is down?
Sendpit retries delivery up to 5 times over approximately 3 hours. If all attempts fail, the webhook is logged as failed. After 10 consecutive failures across any deliveries, the webhook is auto-disabled.
What is the difference between mailbox and organization tokens?
Mailbox tokens (mailbox:{id}) provide access to messages, search, wait, webhooks, and attachments for a single mailbox. Organization tokens (organization:{id}) provide access to inbox management (list, create, delete mailboxes) for the entire organization.
Are emails permanently stored?
No. Emails are retained according to your plan's retention period (7-60 days), then automatically deleted.
Can I use custom domains?
SMTP credentials work regardless of the sender/recipient domain in emails. The routing is based on SMTP authentication, not email addresses.
What email size limits apply?
Maximum email size is 25 MB including attachments.
15. Support & Feedback
Reporting Issues
For bugs, unexpected behavior, or security concerns:
- Email: support@sendpit.com
- Include: Organization ID, mailbox name (not credentials),
X-Request-Idheader value, timestamp, and reproduction steps
Feature Requests
Submit feature requests through the in-app feedback form or email. We prioritize based on impact on existing workflows, alignment with product direction, and technical feasibility.
Security Vulnerabilities
Report security issues to security@sendpit.com. We follow responsible disclosure practices and will acknowledge receipt within 48 hours.
Last updated: March 2026