Developer Hub
Connect apps, AI agents, and automation systems to Sendinel in minutes. Start with an API key, wire up REST or Agent Connect, then use llms.txt when an agent needs the compact machine-readable reference.
For AI Agents & LLMs
Agent Connect gives Claude, Synchronex workers, Cursor, or another MCP client scoped, auditable access to your email operations. Point agents at the compact llms.txt reference or the expanded MCP + REST reference before they call Sendinel.
Quick Start
Sendinel is an email operations control plane. Send transactional email, run campaigns, track events, and manage contacts — all through a REST API or the MCP server.
mail.sendinel.ai — a shared sending domain that's already configured, warmed, and ready to send. You can add your own domain any time, but you don't need one to get your first email out the door.Get your API key
Go to Dashboard → Settings → Developer and create a new key. Keys are prefixed with snk_ and scoped to read, write, or admin. Most integrations need write scope.
Keys are hashed (SHA-256) before storage. The plaintext is shown once and cannot be retrieved again.
Option A — TypeScript SDK (recommended)
npm install @sendinel/sdkimport { Sendinel } from "@sendinel/sdk";
const sendinel = new Sendinel({ apiKey: "snk_..." });
// Identify a user — creates or updates their contact profile
await sendinel.identify("user@example.com", {
firstName: "Alex",
properties: { plan: "pro" },
siteId: "your-site-uuid", // subscribes them + triggers signup campaigns
});
// Fire a named event — triggers any matching campaign automations
await sendinel.track("user@example.com", "completed_onboarding");Option B — curl
curl -X POST https://sendinel.ai/api/v1/identify \
-H "Authorization: Bearer snk_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"email": "user@example.com",
"first_name": "Alex",
"properties": { "plan": "pro" },
"site_id": "your-site-uuid"
}'Copy-paste flows
const SENDINEL_API_KEY = process.env.SENDINEL_API_KEY;
const baseUrl = "https://sendinel.ai";
await fetch(`${baseUrl}/api/v1/identify`, {
method: "POST",
headers: {
Authorization: `Bearer ${SENDINEL_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
email: "alex@acme.com",
first_name: "Alex",
properties: { plan: "pro" },
site_id: "your-site-uuid",
}),
});
const eventRes = await fetch(`${baseUrl}/api/v1/events`, {
method: "POST",
headers: {
Authorization: `Bearer ${SENDINEL_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
email: "alex@acme.com",
event: "completed_onboarding",
data: { source: "app" },
}),
});
console.log(await eventRes.json());const res = await fetch("https://sendinel.ai/api/v1/send", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.SENDINEL_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
to: "alex@acme.com",
site_id: "your-site-uuid",
subject: "Your order is confirmed",
html: "<h1>Order #123</h1><p>Ships tomorrow.</p>",
text: "Order #123 ships tomorrow.",
tags: ["transactional", "order"],
idempotency_key: "order-123",
tracking: true,
}),
});
if (!res.ok) throw new Error(await res.text());
console.log(await res.json());Machine-Readable References
Sendinel exposes canonical references for humans, SDK generators, and AI agents. Use these instead of scraping this page when you need a complete machine-readable contract.
| Reference | Best for | URL |
|---|---|---|
| REST OpenAPI | SDK generation, endpoint discovery, typed REST clients | /api/v1/openapi.json |
| MCP OpenAPI | Non-MCP agents that call tools over HTTP | /api/mcp/openapi.json |
| Compact LLM reference | Small prompt/context budgets | /llms.txt |
| Full LLM reference | Agents that need payload shapes, safety rules, and examples | /llms-full.txt |
For AI Agents & LLMs
Sendinel publishes llms.txt files so coding agents and LLM tools can discover the API, MCP server, auth model, and safety notes without scraping the whole site. The convention comes from llmstxt.org: expose a concise, stable text reference at the root of the domain for agent consumption.
/llms.txt/llms-full.txtAgent Connect — remote OAuth (recommended)
Use Dashboard → Agent Connect when you want Sendinel to guide setup, show connection state, and track what each connected agent called. Free workspaces can expose read-only analytics tools; BYOD and Managed workspaces can approve the full tool catalog with scoped groups.
| Step | Action |
|---|---|
| 1 | Open Dashboard → Agent Connect and click "Add to Claude.ai" or copy the MCP URL for your client. |
| 2 | For Claude Code: run the command below. Your browser opens the OAuth approval page. |
| 3 | Approve access — tools appear automatically. No API key or config file needed. |
claude mcp add sendinel --url https://sendinel.ai/mcpAgent connection path — local stdio (advanced)
| Step | Action |
|---|---|
| 1 | Create a project API key in Dashboard → Settings → Developer. |
| 2 | Add @sendinel/mcp-server as a local stdio MCP server and inject SENDINEL_API_KEY. |
| 3 | Use /llms.txt for compact instructions or /llms-full.txt for the complete tool reference. |
{
"mcpServers": {
"sendinel": {
"command": "npx",
"args": ["-y", "@sendinel/mcp-server@latest"],
"env": {
"SENDINEL_API_KEY": "snk_your_api_key"
}
}
}
}REST API Reference
REST endpoints use Bearer token authentication with project-scoped snk_ keys. Send JSON requests to https://sendinel.ai; self-hosted installs can override the base URL.
| Endpoint | Summary | Resource |
|---|---|---|
| GET /api/v1/approvals | List approval requests | approvals |
| GET /api/v1/approvals/{id} | Get approval status without consuming it | approvals |
| GET /api/v1/platform-connections/{id}/events | List events for a platform connection | platform-connections |
| GET, POST /api/v1/platform-connections | List platform connections | platform-connections |
| DELETE /api/v1/platform-connections/{id} | Disconnect a platform connection | platform-connections |
| POST /api/v1/platform-connections/{id}/import | Trigger a contact/data import for a platform connection | platform-connections |
| POST /api/v1/platform-connections/{id}/triggers | Configure platform trigger mappings | platform-connections |
| GET, POST /api/v1/dmarc/monitors | List DMARC monitors | dmarc |
| GET, DELETE /api/v1/dmarc/monitors/{domain} | Get DMARC monitor detail | dmarc |
| POST /api/v1/migrations/campaigns | Start a campaign migration import job | migrations |
| GET /api/v1/migrations | List migration jobs | migrations |
| GET /api/v1/migrations/{id} | Get a migration job and log history | migrations |
| GET /api/v1/org/usage | Get organization usage | org |
| GET, POST /api/v1/org/members | List organization members | org |
| DELETE /api/v1/org/members/{userId} | Remove an organization member | org |
| POST /api/v1/org/invitations | Create or dedupe an organization invitation | org |
| GET /api/v1/org/plans | List available plan limits | org |
| GET, POST /api/v1/automations | List automation sources | automations |
| GET, PATCH, DELETE /api/v1/automations/{id} | Get an automation source | automations |
| GET /api/v1/automations/{id}/history | List automation trigger history | automations |
| GET /api/v1/automations/{id}/preview | Preview recent automation source items | automations |
| POST /api/v1/automations/enroll | Enroll a subscriber into a triggered campaign | automations |
| POST /api/v1/automations/{id}/pause | Pause an automation source | automations |
| POST /api/v1/automations/{id}/resume | Resume an automation source | automations |
| POST /api/v1/automations/{id}/trigger | Trigger automation draft generation | automations |
| POST /api/v1/social-posts/adapt | Adapt content into social posts | social-posts |
| GET, POST /api/v1/social-posts | List social posts | social-posts |
| GET, PATCH, DELETE /api/v1/social-posts/{id} | Get a social post | social-posts |
| POST /api/v1/advisor/subject-lines | Generate or create subject line variants | advisor |
| GET /api/v1/advisor/content-topics | Recommend content topics | advisor |
| GET /api/v1/advisor/campaign/{id} | Get campaign advisor recommendations | advisor |
| GET /api/v1/advisor/send-time | Recommend send times | advisor |
| GET /api/v1/advisor/recovery-plan | Recommend deliverability recovery actions | advisor |
| GET /api/v1/advisor/portfolio-analysis | Analyze portfolio email performance | advisor |
| PATCH /api/v1/sites/{id}/warmup | Update, pause, or resume site warmup schedule | sites |
| GET /api/v1/sites/{id}/dnsbl | Get latest DNSBL snapshots for a site | sites |
| GET /api/v1/sites/{id}/send-queue/dlq | List dead-letter queue entries for a site project | sites |
| GET /api/v1/sites/{id}/send-queue | Get queued and scheduled send status for a site | sites |
| POST /api/v1/sites/{id}/send-queue/cancel | Cancel unclaimed queued sends for a site | sites |
| POST /api/v1/sites/{id}/send-queue/resend | Requeue DLQ sends for a site | sites |
| POST /api/v1/sites/{id}/send-queue/throttle | Set or clear a per-site send throttle | sites |
| GET /api/v1/sites/{id}/smart-schedule | Recommend best send windows for a site | sites |
| GET, POST /api/v1/sites | List sites | sites |
| GET, PATCH /api/v1/sites/{id} | Get a site | sites |
| GET /api/v1/sites/{id}/deliverability | Site deliverability status with DMARC monitor summary | sites |
| GET /api/v1/sites/{id}/domain-health | Site domain health history | sites |
| GET /api/v1/sites/{id}/insights | Site analytics insights | sites |
| GET /api/v1/sites/{id}/abuse-monitor | Site complaint and abuse monitor | sites |
| POST /api/v1/gdpr | Delete a contact and related data under GDPR workflow | gdpr |
| POST /api/v1/identify | Identify or upsert a contact profile | identify |
| GET, POST /api/v1/events | List recorded contact events | events |
| POST /api/v1/trigger | Trigger automation evaluation for a contact event | trigger |
| POST /api/v1/send | Send a transactional email | send |
| POST /api/v1/sms/send | Send an SMS message | sms |
| POST /api/v1/sms/consent | Set contact SMS consent (opt-in/opt-out) | sms |
| POST /api/v1/sms | Send an SMS message (alias of /sms/send) | sms |
| POST /api/v1/sms/campaigns | Create an SMS campaign | sms |
| GET /api/v1/sms/log | List SMS delivery log rows | sms |
| GET /api/v1/sms/inbound | List inbound SMS replies | sms |
| GET /api/v1/sms/provider | Get default SMS provider status | sms |
| GET /api/v1/sms/cost-preview | Estimate SMS send cost | sms |
| POST /api/v1/provision | Provision an account-scoped messaging channel | provision |
| DELETE /api/v1/provision/{channel_handle} | Deprovision an account-scoped messaging channel | provision |
| POST /api/v1/channels/send | Send an outbound channel message through the account-scoped transport | channels |
| POST /api/v1/channels/roster | Sync a project channel roster | channels |
| POST /api/v1/conversion | Record a conversion event | conversion |
| GET, POST /api/v1/domains | List sending domains | domains |
| GET, DELETE /api/v1/domains/{id} | Get sending domain detail | domains |
| GET /api/v1/domains/{id}/health | Get sending domain health history | domains |
| POST /api/v1/domains/{id}/verify | Trigger sending domain verification | domains |
| POST, DELETE /api/v1/service/projects/provision | Provision a Synchronex-bundled Sendinel project | service |
| GET /api/v1/service/projects/{projectId} | Resolve a Sendinel project for Synchronex reconciliation | service |
| POST /api/v1/service/embed-session | Mint a service embed session token | service |
| GET, POST /api/v1/observer/connections | List Observer Mode connections for a Synchronex account | observer |
| GET, DELETE /api/v1/observer/connections/{connectionId} | Get an Observer Mode connection's status | observer |
| GET /api/v1/synchronex/content-opportunity/signals | Export content-opportunity signals for Synchronex | synchronex |
| POST /api/v1/synchronex/recommendations/approve | Apply a Synchronex CEO-approved campaign recommendation | synchronex |
| GET /api/v1/synchronex/signals | Replay Synchronex signal webhooks | synchronex |
| GET /api/v1/synchronex/signals/summary | Summarize recent Synchronex signal activity | synchronex |
| GET, POST /api/v1/contacts | List contacts | contacts |
| POST /api/v1/contacts/import | Bulk import contacts | contacts |
| POST /api/v1/contacts/tags | Bulk add or remove contact tags | contacts |
| POST /api/v1/contacts/estimate | Estimate audience size from contact filters | contacts |
| GET, PATCH, DELETE /api/v1/contacts/{id} | Get a contact | contacts |
| POST /api/v1/contacts/{id}/unsubscribe | Globally unsubscribe a contact | contacts |
| GET /api/v1/contacts/{id}/timeline | Get a contact activity timeline | contacts |
| POST /api/v1/contacts/{id}/enrich | Enrich a contact from connected providers | contacts |
| PUT /api/v1/contacts/{id}/tags | Replace or update contact tags | contacts |
| GET, POST /api/v1/drafts | List AI draft artifacts | drafts |
| GET /api/v1/drafts/{id} | Get an AI draft and its latest review | drafts |
| POST /api/v1/drafts/{id}/approve | Approve an AI draft | drafts |
| POST /api/v1/drafts/{id}/regenerate | Regenerate an AI draft review | drafts |
| POST /api/v1/drafts/{id}/reject | Reject an AI draft | drafts |
| GET, POST /api/v1/forms | List signup forms | forms |
| GET, PATCH, DELETE /api/v1/forms/{id} | Get signup form detail | forms |
| GET /api/v1/forms/{id}/submissions | List signup form submissions | forms |
| GET /api/v1/email-log | List delivery events | email-log |
| GET /api/v1/email-log/export | Export delivery events | email-log |
| GET, POST /api/v1/content-blocks | List reusable content blocks | content-blocks |
| GET, PATCH, DELETE /api/v1/content-blocks/{id} | Get a reusable content block | content-blocks |
| GET, POST /api/v1/pages | List landing pages | pages |
| GET, PATCH, DELETE /api/v1/pages/{id} | Get a landing page | pages |
| GET, POST /api/v1/asset-links | List gated asset links | asset-links |
| GET, PATCH, DELETE /api/v1/asset-links/{id} | Get a gated asset link | asset-links |
| GET /api/v1/asset-links/{id}/views | List gated asset link views | asset-links |
| GET, POST /api/v1/signatures | List signature blocks | signatures |
| GET, PATCH, DELETE /api/v1/signatures/{id} | Get a signature block | signatures |
| GET, POST /api/v1/sending-domains | List managed sending domains | sending-domains |
| GET, DELETE /api/v1/sending-domains/{id} | Get a managed sending domain | sending-domains |
| GET /api/v1/templates/{id}/preview | Preview a template with seeded sample content | templates |
| GET, POST /api/v1/templates | List templates | templates |
| GET, PATCH, DELETE /api/v1/templates/{id} | Get a template | templates |
| GET, PUT /api/v1/scoring-rules | Get project scoring rules | scoring-rules |
| GET /api/v1/social-inbox | List inbound social engagements | social-inbox |
| GET /api/v1/social-analytics/campaigns | Get social campaign analytics | social-analytics |
| GET /api/v1/social-analytics/portfolio | Get aggregate social portfolio analytics | social-analytics |
| GET /api/v1/social-analytics/engagements | List inbound social engagements for worker context | social-analytics |
| POST /api/v1/embed/token | Issue an embed token | embed |
| GET /api/v1/embed/token/validate | Verify a Sendinel-issued embed token (server-to-server, for embedding platforms) | embed |
| GET /api/v1/dmarc-reports | List DMARC report summaries | dmarc-reports |
| GET /api/v1/recipients/{id}/export | Export recipient data | recipients |
| GET /api/v1/export | Export tenant data | export |
| GET, PATCH /api/v1/brand-brain | Get project brand brain settings | brand-brain |
| GET /api/v1/brand-kit | Get a project's assembled brand kit (assets, templates, campaigns, brand settings) | brand-kit |
| GET /api/v1/brand-kit/assets | List a project's confirmed brand assets across ALL brand kits (not scoped to a single kit) | brand-kit |
| POST /api/v1/brand-kit/import-text | Import raw Brand Core / Brand Brain / Content Library / Brand Kit companion markdown text | brand-kit |
| POST /api/v1/brand-kit/ingest-assets | Ingest brand assets from public HTTPS URLs into the project media library | brand-kit |
| GET, POST /api/v1/blog | List blog posts | blog |
| GET, POST /api/v1/newsletters | List newsletters | newsletters |
| GET, PATCH /api/v1/newsletters/{id} | Get a newsletter | newsletters |
| GET, POST /api/v1/newsletters/{id}/issues | List newsletter issues | newsletters |
| POST /api/v1/newsletters/{id}/issues/{issueId}/schedule | Schedule a newsletter issue | newsletters |
| POST /api/v1/newsletters/{id}/issues/{issueId}/send | Send a newsletter issue now | newsletters |
| POST, DELETE /api/v1/newsletters/{id}/subscribe | Add or reactivate a newsletter subscription | newsletters |
| GET /api/v1/newsletters/{id}/subscribers | List newsletter subscribers | newsletters |
| GET /api/v1/newsletters/{id}/stats | Get newsletter stats | newsletters |
| GET /api/v1/content-sources | List content sources available to worker context | content-sources |
| GET /api/v1/surveys | List surveys available to worker context | surveys |
| GET /api/v1/blackout-dates | List campaign scheduling blackout dates | blackout-dates |
| GET /api/v1/revenue | List conversion revenue records and summary | revenue |
| GET, PATCH, DELETE /api/v1/campaigns/{id}/steps/{stepId} | Get a campaign step | campaigns |
| GET, POST /api/v1/campaigns | List campaigns | campaigns |
| GET, PATCH, DELETE /api/v1/campaigns/{id} | Get a campaign | campaigns |
| GET, PATCH /api/v1/campaigns/{id}/status | Campaign delivery status with per-channel breakdown | campaigns |
| POST /api/v1/campaigns/{id}/launch | Launch a draft campaign | campaigns |
| POST /api/v1/campaigns/{id}/clone | Clone a campaign | campaigns |
| POST /api/v1/campaigns/review | Run the campaign portfolio review | campaigns |
| GET, POST /api/v1/campaigns/{id}/steps | List campaign steps | campaigns |
| PATCH, DELETE /api/v1/campaigns/steps/{stepId} | Update a campaign step | campaigns |
| POST /api/v1/campaigns/{id}/enrollments | Enroll one contact or an entire segment into a campaign | campaigns |
| GET, DELETE /api/v1/campaigns/{id}/enrollments/{contactId} | Get one contact's campaign enrollment status | campaigns |
| POST /api/v1/campaigns/{id}/steps/{stepId}/test | Send a campaign step test email | campaigns |
| GET, POST, PATCH /api/v1/campaigns/ab-tests | Check A/B test significance | campaigns |
| GET /api/v1/campaigns/{id}/stats | Per-campaign delivery metrics | campaigns |
| GET /api/v1/openapi.json | Get the OpenAPI specification as JSON | openapi.json |
| POST /api/v1/ai/context-events | Submit Synchronex strategic context into the AI work queue | ai |
| POST /api/v1/webhooks/cal-com/{connectionId} | Receive a Cal.com webhook for a platform connection | webhooks |
| POST /api/v1/webhooks/calendly/{connectionId} | Receive a Calendly webhook for a platform connection | webhooks |
| POST /api/v1/webhooks/hubspot/{connectionId} | Receive a HubSpot webhook for a platform connection | webhooks |
| POST /api/v1/webhooks/shopify/{connectionId} | Receive a Shopify webhook for a platform connection | webhooks |
| POST /api/v1/webhooks/stripe/{connectionId} | Receive a Stripe webhook for a platform connection | webhooks |
| POST /api/v1/webhooks/typeform/{connectionId} | Receive a Typeform webhook for a platform connection | webhooks |
| GET, POST /api/v1/segments | List segments | segments |
| GET, PATCH, DELETE /api/v1/segments/{id} | Get a segment | segments |
| GET /api/v1/segments/{id}/preview | Preview segment membership | segments |
| GET /api/v1/analytics/portfolio | Cross-site portfolio email analytics rollup | analytics |
| GET /api/v1/analytics/product-usage | Product usage analytics from PostHog for worker context | analytics |
| GET /api/v1/analytics/overview | Site-level email overview metrics for a lookback window | analytics |
| GET /api/v1/analytics/campaigns | Per-campaign analytics for a site over the recent lookback window | analytics |
| GET /api/v1/analytics/engagement | Contact engagement lifecycle analytics | analytics |
| POST /api/v1/analytics/conversion | Record a conversion event (analytics alias) | analytics |
| GET /api/v1/analytics/performance | Email performance trends and best send times | analytics |
| GET /api/v1/lists | List contact lists with subscriber counts | lists |
| GET /api/v1/lists/{id}/subscribers | Page through a contact list's subscribers | lists |
| GET, POST /api/v1/social/campaigns | List social campaigns | social |
| GET, PATCH /api/v1/social/campaigns/{id} | Get a social campaign with its posts | social |
| POST /api/v1/social/campaigns/{id}/posts | Add a post to a social campaign | social |
| GET, POST, DELETE /api/v1/suppressions | List suppressions | suppressions |
| DELETE /api/v1/suppressions/{id} | Remove a suppression by id | suppressions |
| GET, POST /api/v1/webhook-subscriptions | List registered outbound webhooks (secrets never returned) | webhook-subscriptions |
| PATCH, DELETE /api/v1/webhook-subscriptions/{id} | Update an outbound webhook (url, events, active, description) | webhook-subscriptions |
Common REST examples
curl "https://sendinel.ai/api/v1/contacts?site_id=site_uuid&limit=50&tag=paying" \
-H "Authorization: Bearer snk_your_api_key"curl -X POST https://sendinel.ai/api/v1/contacts \
-H "Authorization: Bearer snk_your_api_key" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: contact-alex-acme" \
-d '{
"email": "alex@acme.com",
"first_name": "Alex",
"last_name": "Smith",
"site_id": "site_uuid",
"tags": ["paying", "enterprise"],
"utm_source": "stripe"
}'curl -X POST https://sendinel.ai/api/v1/templates \
-H "Authorization: Bearer snk_your_api_key" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: template-trial-expiry-v1" \
-d '{
"name": "Trial expiry reminder",
"brief": "Remind the user their trial ends soon and ask them to choose a plan.",
"subject_hint": "Your trial ends in 3 days",
"category": "lifecycle",
"tags": ["trial", "conversion"]
}'curl -X POST https://sendinel.ai/api/v1/campaigns \
-H "Authorization: Bearer snk_your_api_key" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: campaign-welcome-v1" \
-d '{
"name": "Welcome sequence",
"site_id": "site_uuid",
"type": "drip"
}'curl "https://sendinel.ai/api/v1/analytics/overview?site_id=site_uuid&days=30" \
-H "Authorization: Bearer snk_your_api_key"curl "https://sendinel.ai/api/v1/export?site_id=site_uuid&format=json" \
-H "Authorization: Bearer snk_admin_api_key"SDKs & Integrations
@sendinel/sdk is a zero-dependency TypeScript client for the Sendinel v1 API. Works in Node.js 18+ (uses native fetch). Full type coverage included.
npm install @sendinel/sdkFull example
import { Sendinel, SendinelError } from "@sendinel/sdk";
const sendinel = new Sendinel({
apiKey: process.env.SENDINEL_API_KEY!,
// baseUrl: "https://sendinel.ai" // default, override for self-hosted
});
// Identify — creates or updates a contact profile (idempotent by email)
const { contact_id, created } = await sendinel.identify("alex@example.com", {
firstName: "Alex",
lastName: "Smith",
properties: { plan: "pro", company: "Acme" },
tags: ["paying", "enterprise"],
siteId: "your-site-uuid", // subscribes to site + enrolls in signup campaigns
});
// Track — fires a named event, auto-creates contact if needed
const result = await sendinel.track("alex@example.com", "completed_onboarding", {
steps_completed: 5,
duration_seconds: 142,
});
// result.enrolled_campaigns → ["Welcome to Pro"]
// result.contact_created → false (contact already existed)
// Send — transactional email, one-off
await sendinel.send({
to: "alex@example.com",
siteId: "your-site-uuid",
subject: "Your order is confirmed",
html: "<p>Order #123 ships tomorrow.</p>",
idempotencyKey: "order-123", // prevents duplicate sends
});Error handling
import { Sendinel, SendinelError } from "@sendinel/sdk";
try {
await sendinel.identify("bad-email");
} catch (err) {
if (err instanceof SendinelError) {
console.log(err.status); // 400
console.log(err.message); // "Invalid email address"
console.log(err.body); // full JSON response
}
}Retry behavior
The SDK retries automatically on network errors, 429 Too Many Requests, and 5xx responses. Up to 3 attempts with exponential backoff (100ms, 200ms, 400ms). Respects Retry-After headers. 4xx errors (except 429) are not retried — they indicate a problem with the request.
Connections
Everything Sendinel can connect to — delivery providers, social platforms, billing adapters, webhooks, and AI clients.
Email Delivery Providers
Sendinel routes email through your provider of choice. Configure at Settings → Project → Email Provider. Credentials are stored AES-256-GCM encrypted.
SMS Providers
SMS delivery integrations share the same project-scoped connection model as email and MCP credentials.
Social Publishing Platforms
Sendinel publishes to 10 social platforms through the SocialPublisher abstraction (Upload Post by default). Connect accounts at Settings → Channels → Social → Connect Account. OAuth flow: GET /api/integrations/social/{{platform}}/connect.
Payment Providers
Billing and payment webhooks normalize into the same internal event model. The registry below is shared with the payment adapter layer.
Import Sources
Migrate contacts, lists, tags, templates, campaigns, and suppressions into Sendinel from your existing platform. Trigger at Settings → Import Contacts.
Outbound Webhooks
Sendinel fires signed HTTP POST webhooks to your endpoint on key events. Configure at Settings → Developer → Webhooks. Signature: X-Sendinel-Signature (HMAC-SHA256).
campaign.completedemail.bouncedemail.complainedcontact.unsubscribedbounce_rate.warningdomain.dns_lostAI Clients & MCP
Sendinel's 65-tool MCP server can be connected from any MCP-compatible AI client. Known integrations: Synchronex (primary orchestration), Claude Desktop, Cursor.
{
"mcpServers": {
"sendinel": {
"command": "npx",
"args": ["-y", "@sendinel/mcp-server@latest"],
"env": {
"SENDINEL_API_KEY": "snk_...",
"SENDINEL_PROJECT_ID": "uuid"
}
}
}
}Identify API
Create or update a contact profile. Safe to call on every user action — idempotent by email address. Authenticated via API key with write scope.
identify when a user signs up, logs in, or upgrades — same pattern as Segment and Customer.io. Sendinel merges properties, appends tags, and handles deduplication automatically.Request body
{
"email": "user@example.com", // required
"first_name": "Alex", // optional
"last_name": "Smith", // optional
"properties": { // optional — merged (patch semantics, never overwrites)
"plan": "pro",
"company": "Acme"
},
"tags": ["paying", "enterprise"], // optional — appended, no duplicates
"site_id": "uuid" // optional — subscribes to site + triggers signup campaigns
}Response — 201 Created (new contact)
{
"contact_id": "uuid",
"created": true
}Response — 200 OK (existing contact updated)
{
"contact_id": "uuid",
"created": false
}Merge semantics
| Field | Behavior on update |
|---|---|
| Primary key — not updatable | |
| first_name / last_name | Overwritten only if non-empty string provided |
| properties | Deep merge — new keys added, existing keys updated only if re-specified |
| tags | Appended — existing tags preserved, duplicates removed |
| site_id | Upserts site subscription, enrolls in signup campaigns (first create only) |
Examples
curl -X POST https://sendinel.ai/api/v1/identify \
-H "Authorization: Bearer snk_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"email": "alex@acme.com",
"first_name": "Alex",
"properties": { "plan": "pro", "source": "stripe" },
"tags": ["paying"],
"site_id": "your-site-uuid"
}'const { contact_id, created } = await sendinel.identify("alex@acme.com", {
firstName: "Alex",
properties: { plan: "pro", source: "stripe" },
tags: ["paying"],
siteId: "your-site-uuid",
});Events API
Fire a named event for a contact. Enrolls them in any active triggered campaigns that match the event name. Authenticated via API key with write scope.
source: "api") so the event can be recorded and campaigns can enroll. You don't need to call identify first — though calling both gives you richer contact data.Request body
{
"email": "user@example.com", // required
"event": "completed_onboarding", // required — must match a campaign trigger_event_name
"data": { "steps_completed": 5 } // optional — stored in event metadata
}Success response
{
"event": "completed_onboarding",
"contact_id": "uuid",
"contact_created": false, // true if contact was auto-created
"enrollments_created": 1,
"enrolled_campaigns": ["Onboarding Sequence"],
"skipped_campaigns": [] // already enrolled in these
}Examples
curl -X POST https://sendinel.ai/api/v1/events \
-H "Authorization: Bearer snk_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"email": "alex@acme.com",
"event": "completed_onboarding",
"data": { "steps_completed": 5 }
}'const result = await sendinel.track("alex@acme.com", "completed_onboarding", {
steps_completed: 5,
});
console.log(result.enrolled_campaigns); // ["Onboarding Sequence"]
console.log(result.contact_created); // falseHow campaign triggering works
When an event is received, Sendinel finds all active campaigns with type: "triggered"and a matching trigger_event_name. For each match, a new enrollment is created if the contact is not already enrolled (any non-exited status). The contact begins receiving the campaign sequence starting from the first step.
If no campaigns match the event name, a 200 is returned with enrollments_created: 0. You can fire test events from the dashboard: open a triggered campaign and click Send Test Event.
Transactional Email API
Send a single transactional email immediately. Checks plan limits, rate limits, and the suppression list before sending. The from address and name come from the site configuration. Authenticated via API key with write scope.
Request body
{
"to": "user@example.com", // required — recipient email
"site_id": "uuid", // required — identifies sender domain + from address
"subject": "Order confirmed", // required — max 998 characters
"html": "<p>Your order #123...</p>", // required — HTML body
"text": "Your order #123...", // optional — plain text fallback
"reply_to": "support@company.com", // optional
"headers": { "X-Custom": "value" }, // optional — custom headers
"tags": ["transactional", "order"], // optional — for filtering in email log
"idempotency_key": "order-123", // optional — prevents duplicate sends
"tracking": true // optional — default true (open + click tracking)
}Success response
{
"id": "uuid", // Sendinel email log ID
"provider_message_id": "re_xxxx", // Resend (or provider) message ID
"to": "user@example.com",
"status": "sent"
}Examples
curl -X POST https://sendinel.ai/api/v1/send \
-H "Authorization: Bearer snk_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"to": "alex@acme.com",
"site_id": "your-site-uuid",
"subject": "Your order is confirmed",
"html": "<h1>Order #123</h1><p>Ships tomorrow.</p>",
"idempotency_key": "order-123"
}'await sendinel.send({
to: "alex@acme.com",
siteId: "your-site-uuid",
subject: "Your order is confirmed",
html: "<h1>Order #123</h1><p>Ships tomorrow.</p>",
idempotencyKey: "order-123",
});Tracking: Open tracking (1x1 pixel) and click tracking (link wrapping) are automatically injected into the HTML body. Set tracking: false to disable.
Conversion Tracking
Track conversions and attribute revenue to email campaigns. Revenue is attributed to the most recent email sent to the contact within a 7-day window. Authenticated via API key with write scope.
Request body
{
"email": "user@example.com", // required
"event": "purchase", // optional — default: "purchase"
"revenue": 49.99, // optional
"currency": "USD", // optional — default: "USD"
"order_id": "order_123", // optional — deduplication key (409 on duplicate)
"metadata": { "plan": "pro" } // optional
}Success response
{
"id": "uuid",
"contact_id": "uuid",
"email_log_id": "uuid",
"campaign_id": "uuid",
"event": "purchase",
"revenue": 49.99,
"attributed_at": "2026-04-10T12:00:00Z"
}Attribution logic
Sendinel attributes revenue to the most recent email click within 72 hours, falling back to the most recent open within 72 hours. Revenue data appears in the dashboard overview automatically.
If order_id is provided, duplicate conversions with the same order ID are rejected with 409 Conflict.
Webhooks
Sendinel processes delivery events from your email provider to update status, log opens and clicks, and handle bounces and complaints automatically.
Setup
In your Resend dashboard, add a webhook pointing to:
https://sendinel.ai/api/webhooks/resendFor per-project routing (BYOD with multiple projects):
https://sendinel.ai/api/webhooks/resend/[projectId]All webhook payloads are verified using Svix signature verification. Set RESEND_WEBHOOK_SECRET in your environment. Per-project secrets are supported and stored encrypted.
Supported events
| Event | Description | Action |
|---|---|---|
| email.delivered | Email accepted by recipient server | Updates email_log status → 'delivered' |
| email.opened | Recipient opened the email | Updates status → 'opened', increments open count |
| email.clicked | Recipient clicked a tracked link | Updates status → 'clicked', logs URL in clicked_urls |
| email.bounced | Hard or soft bounce | Updates status → 'bounced', adds suppression record |
| email.complained | Recipient marked as spam | Updates status → 'complained', adds suppression, unsubscribes contact |
Bounces and complaints trigger Slack alerts if configured. Failed webhook processing is retried via a dead letter queue with exponential backoff (up to 5 attempts).
MCP Tools Reference
Agent Connect is Sendinel's managed MCP (Model Context Protocol) access layer. Every campaign, contact, segment, analytics, and template operation is accessible to AI agents — list campaigns, add contacts, generate emails, check deliverability, approve drafts, and more without writing a line of code. OAuth connections and API-key connections are revocable and usage is visible per connection in the dashboard.
Three connection methods
| Transport | Best for | How |
|---|---|---|
| Remote HTTP + OAuth | Claude.ai, Claude Code, Cursor, Windsurf — zero config | claude mcp add sendinel --url https://sendinel.ai/mcp — browser OAuth flow, no API key needed |
| stdio | Local clients with manual config — Claude Desktop | npx @sendinel/mcp-server — inject SENDINEL_API_KEY env var |
| REST/OpenAPI | Non-MCP agents, web apps, custom integrations | /api/mcp/tools + /api/mcp/openapi.json — Bearer API key auth |
OAuth discovery
| Endpoint | Purpose |
|---|---|
| /.well-known/oauth-authorization-server | RFC 8414 discovery — MCP clients find all OAuth endpoints automatically |
| /.well-known/oauth-protected-resource | Points clients to the authorization server |
| /oauth/authorize | Browser approval page — shows client name, project, and requested scopes |
| /oauth/token | Exchange auth code for access + refresh tokens (90 day / 1 year) |
| /oauth/register | Dynamic client registration (RFC 7591) — any MCP client can self-register |
| /oauth/revoke | Revoke a token immediately |
Available tool groups
| Group | Tools | Description |
|---|---|---|
| ab-testing | 4 | Available in the MCP catalog. |
| advisor | 6 | Available in the MCP catalog. |
| ai-work | 9 | Available in the MCP catalog. |
| analytics | 16 | Stats, domain health, engagement insights, deliverability checks, and reporting. |
| approvals | 2 | Available in the MCP catalog. |
| automations | 7 | List and preview automations, then trigger them when appropriate. |
| brand_kit | 4 | Available in the MCP catalog. |
| campaigns | 28 | Create, clone, enroll, launch, and generate campaign content. |
| compound | 4 | Available in the MCP catalog. |
| contacts | 26 | CRUD, import, merge, suppressions, email previews, and test sends. |
| content | 11 | Available in the MCP catalog. |
| data | 14 | Audit log, scoring rules, DMARC, exports, and explainability. |
| data-proposals | 3 | Available in the MCP catalog. |
| delivery-ops | 12 | Available in the MCP catalog. |
| drafts | 6 | Create, review, approve, and reject AI-generated drafts. |
| forms | 3 | Available in the MCP catalog. |
| gdpr | 2 | Delete contact data and inspect deletion logs. |
| newsletters | 12 | Available in the MCP catalog. |
| org | 10 | Available in the MCP catalog. |
| segments | 6 | Create, update, preview, and delete segments, including NL helpers. |
| short-links | 3 | Available in the MCP catalog. |
| sites | 5 | Create and update sending sites. |
| sms | 7 | Available in the MCP catalog. |
| social-posts | 6 | Available in the MCP catalog. |
| surveys | 5 | Available in the MCP catalog. |
| templates | 6 | List, create, update, and delete reusable templates. |
| warmup | 4 | Available in the MCP catalog. |
| webhooks | 4 | Available in the MCP catalog. |
API keys and OAuth connections can be scoped to specific tool groups. Destructive operations use a two-call confirmation pattern. Synchronex workers use the same stable MCP contract as third-party clients.
Tool catalog
| Tool | Group | Required arguments |
|---|---|---|
| abuse_monitor_override | analytics | siteId, reason, ttlHours |
| abuse_monitor_status | analytics | siteId |
| create_social_campaign | analytics | name, platform, destination_url |
| deliverability_check | analytics | site_id |
| get_domain_health | analytics | site_id |
| get_engagement_insights | analytics | - |
| get_portfolio_analytics | analytics | - |
| get_portfolio_stats | analytics | - |
| get_site_insights | analytics | site_id |
| get_sites | analytics | - |
| get_stats | analytics | site_id, start_date |
| list_event_log | analytics | - |
| list_projects | analytics | - |
| list_social_campaigns | analytics | - |
| performance_report | analytics | - |
| schedule_social_post | analytics | campaign_id, content, publisher |
| add_campaign_step | campaigns | campaign_id, step_order, subject |
| broadcast_to_segment | campaigns | site_id, segment_id, subject, body_html |
| check_content_consistency | campaigns | content |
| clone_campaign | campaigns | campaign_id, new_name |
| create_campaign | campaigns | site_id, name, type |
| create_campaign_with_content | campaigns | site_id, name, type, steps |
| delete_campaign | campaigns | campaign_id |
| delete_campaign_step | campaigns | step_id |
| enroll_contact | campaigns | campaign_id, contact_id |
| enroll_segment | campaigns | campaign_id, site_id |
| generate_email | campaigns | site_id, brief |
| get_campaign | campaigns | campaign_id |
| get_campaign_calendar | campaigns | year, month |
| get_enrollment_status | campaigns | campaign_id, contact_id |
| get_schedule | campaigns | site_id |
| launch_campaign | campaigns | name, site_id, emails |
| list_campaign_steps | campaigns | campaign_id |
| list_campaigns | campaigns | - |
| preview_campaign_audience | campaigns | campaign_id |
| preview_email | campaigns | - |
| review_campaign_portfolio | campaigns | - |
| send_test_email | campaigns | to_email, site_id |
| send_transactional | campaigns | to, site_id, subject |
| unenroll_contact | campaigns | campaign_id, contact_id |
| update_campaign | campaigns | campaign_id |
| update_campaign_status | campaigns | campaign_id, status |
| update_campaign_step | campaigns | step_id |
| validate_template | campaigns | html |
| add_subscriber | contacts | email, site_id |
| add_suppression | contacts | |
| enrich_subscriber | contacts | contact_id |
| estimate_segment_size | contacts | - |
| find_duplicates | contacts | - |
| get_contact_schema | contacts | site_id |
| get_hygiene_config | contacts | - |
| get_subscriber | contacts | - |
| get_subscriber_timeline | contacts | subscriber_id |
| identify_inactive | contacts | - |
| import_subscribers | contacts | contacts, site_id |
| list_companies | contacts | - |
| list_email_log | contacts | - |
| list_hygiene_audit | contacts | siteId |
| list_subscribers | contacts | - |
| list_subscribers_by_segment | contacts | - |
| list_suppressions | contacts | - |
| list_win_back_draft | contacts | siteId |
| merge_subscribers | contacts | target_id, source_ids |
| remove_suppression | contacts | |
| run_list_hygiene | contacts | - |
| set_subscriber_tags | contacts | contact_ids |
| subscriber_export | contacts | contactId |
| unsubscribe_subscriber | contacts | |
| update_hygiene_config | contacts | - |
| update_subscriber | contacts | contact_id |
| create_segment | segments | name, rules |
| create_segment_nl | segments | name, description |
| delete_segment | segments | segment_id |
| list_segments | segments | - |
| preview_segment | segments | - |
| update_segment | segments | segment_id |
| approve_draft | drafts | draft_id |
| create_draft | drafts | site_id, subject, body_html |
| get_draft | drafts | draft_id |
| list_drafts | drafts | - |
| promote_draft_to_campaign | drafts | draft_id |
| reject_draft | drafts | draft_id |
| create_sender | sites | name, slug, from_name |
| create_site | sites | name, slug, from_name |
| get_brand_voice | sites | site_id |
| update_brand_voice | sites | site_id, brand_voice |
| update_site | sites | site_id |
| delete_subscriber_data | gdpr | contact_id |
| list_deletion_log | gdpr | - |
| check_ab_significance | ab-testing | test_id |
| create_ab_test | ab-testing | campaign_id, name |
| list_ab_tests | ab-testing | - |
| promote_ab_winner | ab-testing | test_id |
| campaign_advisor | advisor | - |
| optimize_subject_lines | advisor | campaign_id, site_id |
| recovery_plan | advisor | site_id |
| run_portfolio_analysis | advisor | - |
| suggest_content_topics | advisor | site_id |
| suggest_send_time | advisor | site_id |
| delete_webhook_subscription | webhooks | subscription_id |
| list_webhook_subscriptions | webhooks | - |
| subscribe_webhook | webhooks | url, events |
| update_webhook_subscription | webhooks | subscription_id |
| check_approval | approvals | approval_id |
| list_pending_approvals | approvals | - |
| create_escalation_ladder | automations | campaign_id, steps |
| enroll_automation | automations | subscriber_id |
| enroll_in_escalation_ladder | automations | subscriber_id, campaign_id |
| list_automations | automations | - |
| list_escalation_ladder_status | automations | campaign_id |
| preview_automation | automations | source_id |
| trigger_automation | automations | source_id |
| create_template | templates | name, brief |
| delete_template | templates | id |
| get_template | templates | id |
| list_templates | templates | - |
| translate_template | templates | - |
| update_template | templates | id |
| dnsbl_check | delivery-ops | siteId |
| dnsbl_delisting_draft | delivery-ops | siteId, zone |
| explain_contact_score | delivery-ops | - |
| export_data | delivery-ops | resource |
| get_cron_runs | delivery-ops | - |
| get_queue_status | delivery-ops | - |
| get_scoring_rules | delivery-ops | - |
| get_send_dlq | delivery-ops | - |
| list_domains | delivery-ops | - |
| refresh_domain_dns | delivery-ops | domain_id |
| register_domain | delivery-ops | site_id, domain |
| update_scoring_rules | delivery-ops | - |
| create_template_from_brief | compound | site_id, name, brief |
| diagnose_delivery_issue | compound | - |
| onboard_new_site | compound | name, domain |
| setup_campaign_from_brief | compound | site_id, campaign_name, brief |
| configure_platform_trigger | data | platform, event_type, action |
| connect_platform | data | platform, credentials |
| create_api_key | data | name |
| disconnect_platform | data | - |
| list_api_keys | data | - |
| list_connected_platforms | data | - |
| list_import_history | data | - |
| list_notifications | data | - |
| list_platform_events | data | - |
| mark_notifications_read | data | - |
| migrate_campaigns_from | data | provider, oauth_token |
| revoke_api_key | data | key_id |
| start_platform_migration | data | connection_id |
| tenant_export_full | data | siteId |
| create_asset_link | content | title, slug, asset_url |
| create_blog_post | content | title |
| create_content_block | content | name, html |
| delete_content_block | content | block_id |
| get_asset_link_views | content | asset_link_id |
| list_asset_links | content | - |
| list_blog_destinations | content | - |
| list_blog_posts | content | - |
| list_content_blocks | content | - |
| list_content_sources | content | - |
| trigger_content_pipeline | content | - |
| get_warmup_status | warmup | site_id |
| pause_warmup | warmup | site_id |
| resume_warmup | warmup | site_id |
| update_warmup_schedule | warmup | site_id |
| create_form | forms | site_id, name, fields |
| get_form_stats | forms | form_id |
| list_forms | forms | - |
| add_white_label_domain | org | custom_domain |
| get_plan_limits | org | - |
| get_plan_usage | org | - |
| get_provider_switch_status | org | id |
| get_white_label_settings | org | - |
| invite_team_member | org | |
| list_team_members | org | - |
| rollback_provider_switch | org | id |
| set_white_label_settings | org | - |
| start_provider_switch | org | site_id, to_provider |
| get_sms_provider_status | sms | - |
| list_sms_inbound | sms | - |
| list_sms_log | sms | - |
| preview_sms_cost | sms | body |
| send_sms | sms | body |
| send_sms_campaign | sms | site_id, name, body |
| set_contact_sms_consent | sms | contact_id, status |
| adapt_social_post | social-posts | email_campaign_id, platforms |
| create_social_post | social-posts | title, content, platforms |
| delete_social_post | social-posts | post_id |
| get_social_post | social-posts | post_id |
| list_social_posts | social-posts | - |
| update_social_post | social-posts | post_id |
| approve_data_proposal | data-proposals | proposal_id |
| list_data_proposals | data-proposals | - |
| propose_campaign_from_data | data-proposals | - |
| create_survey | surveys | title, questions |
| get_survey | surveys | survey_id |
| get_survey_results | surveys | survey_id |
| list_surveys | surveys | - |
| update_survey | surveys | survey_id |
| create_short_link | short-links | destination_url |
| generate_qr_code | short-links | url |
| get_short_link_stats | short-links | short_link_id |
| dismiss_ai_work_item | ai-work | work_item_id |
| get_agent_proposal | ai-work | proposal_id |
| get_ai_work_item | ai-work | work_item_id |
| list_agent_proposals | ai-work | - |
| list_ai_work_items | ai-work | - |
| list_recommended_segments | ai-work | - |
| mark_ai_work_item_seen | ai-work | work_item_id |
| snooze_ai_work_item | ai-work | work_item_id, snoozed_until |
| submit_ai_context_event | ai-work | workspace_id, event_type, title, detail, idempotency_key |
| import_brand_kit_text | brand_kit | text |
| ingest_brand_assets_by_url | brand_kit | assets |
| list_brand_assets | brand_kit | - |
| upload_brand_asset | brand_kit | filename, content_base64 |
| create_newsletter | newsletters | name |
| create_newsletter_issue | newsletters | newsletter_id, subject |
| get_newsletter | newsletters | newsletter_id |
| get_newsletter_stats | newsletters | newsletter_id |
| list_newsletter_issues | newsletters | newsletter_id |
| list_newsletter_subscribers | newsletters | newsletter_id |
| list_newsletters | newsletters | - |
| schedule_newsletter_issue | newsletters | newsletter_id, issue_id, scheduled_at |
| send_newsletter_issue | newsletters | newsletter_id, issue_id |
| subscribe_to_newsletter | newsletters | newsletter_id, email |
| unsubscribe_from_newsletter | newsletters | newsletter_id |
| update_newsletter | newsletters | newsletter_id |
AI Clients
Connect Sendinel to any MCP-compatible AI client through Agent Connect. Once connected, your agent can manage your email operations conversationally through the MCP tool catalog.
Open Settings -> Developer -> Edit Config, then add this local MCP server. Use this when you normally work in Git Bash but the desktop AI client is launched by Windows.
{
"mcpServers": {
"sendinel": {
"command": "C:\\Program Files\\Git\\bin\\bash.exe",
"args": [
"-lc",
"npx -y @sendinel/mcp-server@latest"
],
"env": {
"SENDINEL_API_KEY": "snk_your_api_key"
}
}
}
}Example agent conversation
// Your prompt:
"List my active campaigns and tell me which ones have the lowest open rates."
// Agent calls:
list_campaigns({ "status": "active" })
get_stats({ "campaign_id": "uuid", "period": "30d" })
// Returns:
{
"campaigns": [
{ "name": "Welcome Series", "open_rate": 0.42, "enrolled": 142 },
{ "name": "Re-engagement", "open_rate": 0.11, "enrolled": 38 }
]
}Delivery Semantics
Email delivery is asynchronous infrastructure, not a synchronous HTTP side effect. Sendinel accepts API requests, applies safety policy, queues work, and then workers send through the configured provider. This section explains what the API response means and why a message may wait before leaving the queue.
| State | Meaning | Common reason |
|---|---|---|
| accepted | Sendinel validated the request and created queue/log records | Normal response from API or scheduler |
| queued | Message is waiting for an eligible worker slot | Batch limits, warmup cap, send window, blackout date |
| sent | Provider accepted the message for delivery | Resend/Mailgun/SES accepted the API call |
| delivered | Recipient server accepted the message | Provider webhook received |
| opened / clicked | Recipient engagement was tracked | Tracking pixel or wrapped link fired |
| bounced / complained | Recipient server rejected or user reported spam | Contact is suppressed automatically |
What “sent” means
sent means the sending provider accepted the message from Sendinel. It does not mean the recipient inbox accepted it. Final delivery, bounce, complaint, open, and click states arrive later through provider webhooks.
Why sends wait in queue
| Control | Effect |
|---|---|
| Warmup caps | New or recently migrated domains may be limited to a small daily volume until trust is established. |
| Provider warmup | If the email provider also reports a dedicated-IP warmup cap, Sendinel uses the lower of both limits. |
| Plan batch size | Worker batch sizes scale by plan tier; larger plans drain queues faster. |
| Send windows | Scheduled sends only fire inside the configured allowed days and times. |
| Blackout dates | Campaign sends pause on configured blackout days. |
| Suppression checks | Bounced, complained, unsubscribed, or manually suppressed contacts are skipped before provider send. |
| Idempotency | Duplicate transactional requests with the same idempotency key return the existing result instead of sending twice. |
Rate Limits & Plans
Rate limits are enforced per project using a sliding window. Limits vary by plan and operation type.
| Plan | Monthly | Included limits | Capabilities |
|---|---|---|---|
| Free | Free | Contacts 250 · Sends 500 · Projects 1 · Sites 1 | Read-only MCP · Agent CRM · No export · No A/B testing · No landing pages |
| BYOD | $19/mo | Contacts Unlimited · Sends Unlimited · Projects Unlimited · Sites 10 | Full MCP · Agent CRM · Export · A/B testing · Landing pages |
| Managed | $79/mo | Contacts 50,000 · Sends 50,000 · Projects 5 · Sites 10 | Full MCP · Agent CRM · Export · A/B testing · Landing pages |
| Agency Plus | $999/mo | Contacts Unlimited · Sends 500,000 · Projects Unlimited · Sites Unlimited | Full MCP · Agent CRM · Export · A/B testing · Landing pages |
| Enterprise | $499/mo | Contacts Unlimited · Sends 250,000 · Projects Unlimited · Sites 50 | Full MCP · Agent CRM · Export · A/B testing · Landing pages |
Rate limit headers
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 87
X-RateLimit-Reset: 1744329600When rate limited, the API returns 429 Too Many Requests with a Retry-After header. The SDK handles 429s automatically with backoff and retry.
Error Codes
All error responses return a JSON body with an error field describing the issue.
| Status | Meaning | Common causes |
|---|---|---|
| 400 | Bad Request | Missing required fields, invalid email format, body too large |
| 401 | Unauthorized | Missing or invalid API key, expired key |
| 403 | Forbidden | API key lacks required scope (write scope needed for mutations) |
| 404 | Not Found | Invalid site_id or resource ID |
| 409 | Conflict | Duplicate idempotency_key (email already sent), duplicate order_id |
| 422 | Unprocessable | Contact limit reached — cannot auto-create contact (track endpoint) |
| 429 | Rate Limited | Too many requests in the current window |
| 500 | Server Error | Internal error — retry with exponential backoff |
{
"error": "Descriptive error message",
"code": "SUPPRESSED_CONTACT", // optional — machine-readable code
"details": { ... } // optional — additional context
}Need help? support@sendinel.ai