Home
Developer Hub
Dashboard

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.

No DNS required to start. Every Sendinel account ships with access to 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)

bash
npm install @sendinel/sdk
TypeScript
import { 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
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

fetch — enroll a contact in a triggered sequence
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());
fetch — transactional email with tracking and idempotency
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.

ReferenceBest forURL
REST OpenAPISDK generation, endpoint discovery, typed REST clients/api/v1/openapi.json
MCP OpenAPINon-MCP agents that call tools over HTTP/api/mcp/openapi.json
Compact LLM referenceSmall prompt/context budgets/llms.txt
Full LLM referenceAgents 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.

Compact API reference for AI tools
Short reference for auth, REST endpoints, MCP URL, and common integration flows.
/llms.txt
Full MCP + REST reference for LLM consumption
Expanded reference with all tool groups, payload shapes, safety constraints, and examples.
/llms-full.txt

Agent 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.

StepAction
1Open Dashboard → Agent Connect and click "Add to Claude.ai" or copy the MCP URL for your client.
2For Claude Code: run the command below. Your browser opens the OAuth approval page.
3Approve access — tools appear automatically. No API key or config file needed.
Claude Code — one-command connect
claude mcp add sendinel --url https://sendinel.ai/mcp

Agent connection path — local stdio (advanced)

StepAction
1Create a project API key in Dashboard → Settings → Developer.
2Add @sendinel/mcp-server as a local stdio MCP server and inject SENDINEL_API_KEY.
3Use /llms.txt for compact instructions or /llms-full.txt for the complete tool reference.
claude_desktop_config.json — stdio MCP config
{
  "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.

EndpointSummaryResource
GET /api/v1/approvalsList approval requestsapprovals
GET /api/v1/approvals/{id}Get approval status without consuming itapprovals
GET /api/v1/platform-connections/{id}/eventsList events for a platform connectionplatform-connections
GET, POST /api/v1/platform-connectionsList platform connectionsplatform-connections
DELETE /api/v1/platform-connections/{id}Disconnect a platform connectionplatform-connections
POST /api/v1/platform-connections/{id}/importTrigger a contact/data import for a platform connectionplatform-connections
POST /api/v1/platform-connections/{id}/triggersConfigure platform trigger mappingsplatform-connections
GET, POST /api/v1/dmarc/monitorsList DMARC monitorsdmarc
GET, DELETE /api/v1/dmarc/monitors/{domain}Get DMARC monitor detaildmarc
POST /api/v1/migrations/campaignsStart a campaign migration import jobmigrations
GET /api/v1/migrationsList migration jobsmigrations
GET /api/v1/migrations/{id}Get a migration job and log historymigrations
GET /api/v1/org/usageGet organization usageorg
GET, POST /api/v1/org/membersList organization membersorg
DELETE /api/v1/org/members/{userId}Remove an organization memberorg
POST /api/v1/org/invitationsCreate or dedupe an organization invitationorg
GET /api/v1/org/plansList available plan limitsorg
GET, POST /api/v1/automationsList automation sourcesautomations
GET, PATCH, DELETE /api/v1/automations/{id}Get an automation sourceautomations
GET /api/v1/automations/{id}/historyList automation trigger historyautomations
GET /api/v1/automations/{id}/previewPreview recent automation source itemsautomations
POST /api/v1/automations/enrollEnroll a subscriber into a triggered campaignautomations
POST /api/v1/automations/{id}/pausePause an automation sourceautomations
POST /api/v1/automations/{id}/resumeResume an automation sourceautomations
POST /api/v1/automations/{id}/triggerTrigger automation draft generationautomations
POST /api/v1/social-posts/adaptAdapt content into social postssocial-posts
GET, POST /api/v1/social-postsList social postssocial-posts
GET, PATCH, DELETE /api/v1/social-posts/{id}Get a social postsocial-posts
POST /api/v1/advisor/subject-linesGenerate or create subject line variantsadvisor
GET /api/v1/advisor/content-topicsRecommend content topicsadvisor
GET /api/v1/advisor/campaign/{id}Get campaign advisor recommendationsadvisor
GET /api/v1/advisor/send-timeRecommend send timesadvisor
GET /api/v1/advisor/recovery-planRecommend deliverability recovery actionsadvisor
GET /api/v1/advisor/portfolio-analysisAnalyze portfolio email performanceadvisor
PATCH /api/v1/sites/{id}/warmupUpdate, pause, or resume site warmup schedulesites
GET /api/v1/sites/{id}/dnsblGet latest DNSBL snapshots for a sitesites
GET /api/v1/sites/{id}/send-queue/dlqList dead-letter queue entries for a site projectsites
GET /api/v1/sites/{id}/send-queueGet queued and scheduled send status for a sitesites
POST /api/v1/sites/{id}/send-queue/cancelCancel unclaimed queued sends for a sitesites
POST /api/v1/sites/{id}/send-queue/resendRequeue DLQ sends for a sitesites
POST /api/v1/sites/{id}/send-queue/throttleSet or clear a per-site send throttlesites
GET /api/v1/sites/{id}/smart-scheduleRecommend best send windows for a sitesites
GET, POST /api/v1/sitesList sitessites
GET, PATCH /api/v1/sites/{id}Get a sitesites
GET /api/v1/sites/{id}/deliverabilitySite deliverability status with DMARC monitor summarysites
GET /api/v1/sites/{id}/domain-healthSite domain health historysites
GET /api/v1/sites/{id}/insightsSite analytics insightssites
GET /api/v1/sites/{id}/abuse-monitorSite complaint and abuse monitorsites
POST /api/v1/gdprDelete a contact and related data under GDPR workflowgdpr
POST /api/v1/identifyIdentify or upsert a contact profileidentify
GET, POST /api/v1/eventsList recorded contact eventsevents
POST /api/v1/triggerTrigger automation evaluation for a contact eventtrigger
POST /api/v1/sendSend a transactional emailsend
POST /api/v1/sms/sendSend an SMS messagesms
POST /api/v1/sms/consentSet contact SMS consent (opt-in/opt-out)sms
POST /api/v1/smsSend an SMS message (alias of /sms/send)sms
POST /api/v1/sms/campaignsCreate an SMS campaignsms
GET /api/v1/sms/logList SMS delivery log rowssms
GET /api/v1/sms/inboundList inbound SMS repliessms
GET /api/v1/sms/providerGet default SMS provider statussms
GET /api/v1/sms/cost-previewEstimate SMS send costsms
POST /api/v1/provisionProvision an account-scoped messaging channelprovision
DELETE /api/v1/provision/{channel_handle}Deprovision an account-scoped messaging channelprovision
POST /api/v1/channels/sendSend an outbound channel message through the account-scoped transportchannels
POST /api/v1/channels/rosterSync a project channel rosterchannels
POST /api/v1/conversionRecord a conversion eventconversion
GET, POST /api/v1/domainsList sending domainsdomains
GET, DELETE /api/v1/domains/{id}Get sending domain detaildomains
GET /api/v1/domains/{id}/healthGet sending domain health historydomains
POST /api/v1/domains/{id}/verifyTrigger sending domain verificationdomains
POST, DELETE /api/v1/service/projects/provisionProvision a Synchronex-bundled Sendinel projectservice
GET /api/v1/service/projects/{projectId}Resolve a Sendinel project for Synchronex reconciliationservice
POST /api/v1/service/embed-sessionMint a service embed session tokenservice
GET, POST /api/v1/observer/connectionsList Observer Mode connections for a Synchronex accountobserver
GET, DELETE /api/v1/observer/connections/{connectionId}Get an Observer Mode connection's statusobserver
GET /api/v1/synchronex/content-opportunity/signalsExport content-opportunity signals for Synchronexsynchronex
POST /api/v1/synchronex/recommendations/approveApply a Synchronex CEO-approved campaign recommendationsynchronex
GET /api/v1/synchronex/signalsReplay Synchronex signal webhookssynchronex
GET /api/v1/synchronex/signals/summarySummarize recent Synchronex signal activitysynchronex
GET, POST /api/v1/contactsList contactscontacts
POST /api/v1/contacts/importBulk import contactscontacts
POST /api/v1/contacts/tagsBulk add or remove contact tagscontacts
POST /api/v1/contacts/estimateEstimate audience size from contact filterscontacts
GET, PATCH, DELETE /api/v1/contacts/{id}Get a contactcontacts
POST /api/v1/contacts/{id}/unsubscribeGlobally unsubscribe a contactcontacts
GET /api/v1/contacts/{id}/timelineGet a contact activity timelinecontacts
POST /api/v1/contacts/{id}/enrichEnrich a contact from connected providerscontacts
PUT /api/v1/contacts/{id}/tagsReplace or update contact tagscontacts
GET, POST /api/v1/draftsList AI draft artifactsdrafts
GET /api/v1/drafts/{id}Get an AI draft and its latest reviewdrafts
POST /api/v1/drafts/{id}/approveApprove an AI draftdrafts
POST /api/v1/drafts/{id}/regenerateRegenerate an AI draft reviewdrafts
POST /api/v1/drafts/{id}/rejectReject an AI draftdrafts
GET, POST /api/v1/formsList signup formsforms
GET, PATCH, DELETE /api/v1/forms/{id}Get signup form detailforms
GET /api/v1/forms/{id}/submissionsList signup form submissionsforms
GET /api/v1/email-logList delivery eventsemail-log
GET /api/v1/email-log/exportExport delivery eventsemail-log
GET, POST /api/v1/content-blocksList reusable content blockscontent-blocks
GET, PATCH, DELETE /api/v1/content-blocks/{id}Get a reusable content blockcontent-blocks
GET, POST /api/v1/pagesList landing pagespages
GET, PATCH, DELETE /api/v1/pages/{id}Get a landing pagepages
GET, POST /api/v1/asset-linksList gated asset linksasset-links
GET, PATCH, DELETE /api/v1/asset-links/{id}Get a gated asset linkasset-links
GET /api/v1/asset-links/{id}/viewsList gated asset link viewsasset-links
GET, POST /api/v1/signaturesList signature blockssignatures
GET, PATCH, DELETE /api/v1/signatures/{id}Get a signature blocksignatures
GET, POST /api/v1/sending-domainsList managed sending domainssending-domains
GET, DELETE /api/v1/sending-domains/{id}Get a managed sending domainsending-domains
GET /api/v1/templates/{id}/previewPreview a template with seeded sample contenttemplates
GET, POST /api/v1/templatesList templatestemplates
GET, PATCH, DELETE /api/v1/templates/{id}Get a templatetemplates
GET, PUT /api/v1/scoring-rulesGet project scoring rulesscoring-rules
GET /api/v1/social-inboxList inbound social engagementssocial-inbox
GET /api/v1/social-analytics/campaignsGet social campaign analyticssocial-analytics
GET /api/v1/social-analytics/portfolioGet aggregate social portfolio analyticssocial-analytics
GET /api/v1/social-analytics/engagementsList inbound social engagements for worker contextsocial-analytics
POST /api/v1/embed/tokenIssue an embed tokenembed
GET /api/v1/embed/token/validateVerify a Sendinel-issued embed token (server-to-server, for embedding platforms)embed
GET /api/v1/dmarc-reportsList DMARC report summariesdmarc-reports
GET /api/v1/recipients/{id}/exportExport recipient datarecipients
GET /api/v1/exportExport tenant dataexport
GET, PATCH /api/v1/brand-brainGet project brand brain settingsbrand-brain
GET /api/v1/brand-kitGet a project's assembled brand kit (assets, templates, campaigns, brand settings)brand-kit
GET /api/v1/brand-kit/assetsList a project's confirmed brand assets across ALL brand kits (not scoped to a single kit)brand-kit
POST /api/v1/brand-kit/import-textImport raw Brand Core / Brand Brain / Content Library / Brand Kit companion markdown textbrand-kit
POST /api/v1/brand-kit/ingest-assetsIngest brand assets from public HTTPS URLs into the project media librarybrand-kit
GET, POST /api/v1/blogList blog postsblog
GET, POST /api/v1/newslettersList newslettersnewsletters
GET, PATCH /api/v1/newsletters/{id}Get a newsletternewsletters
GET, POST /api/v1/newsletters/{id}/issuesList newsletter issuesnewsletters
POST /api/v1/newsletters/{id}/issues/{issueId}/scheduleSchedule a newsletter issuenewsletters
POST /api/v1/newsletters/{id}/issues/{issueId}/sendSend a newsletter issue nownewsletters
POST, DELETE /api/v1/newsletters/{id}/subscribeAdd or reactivate a newsletter subscriptionnewsletters
GET /api/v1/newsletters/{id}/subscribersList newsletter subscribersnewsletters
GET /api/v1/newsletters/{id}/statsGet newsletter statsnewsletters
GET /api/v1/content-sourcesList content sources available to worker contextcontent-sources
GET /api/v1/surveysList surveys available to worker contextsurveys
GET /api/v1/blackout-datesList campaign scheduling blackout datesblackout-dates
GET /api/v1/revenueList conversion revenue records and summaryrevenue
GET, PATCH, DELETE /api/v1/campaigns/{id}/steps/{stepId}Get a campaign stepcampaigns
GET, POST /api/v1/campaignsList campaignscampaigns
GET, PATCH, DELETE /api/v1/campaigns/{id}Get a campaigncampaigns
GET, PATCH /api/v1/campaigns/{id}/statusCampaign delivery status with per-channel breakdowncampaigns
POST /api/v1/campaigns/{id}/launchLaunch a draft campaigncampaigns
POST /api/v1/campaigns/{id}/cloneClone a campaigncampaigns
POST /api/v1/campaigns/reviewRun the campaign portfolio reviewcampaigns
GET, POST /api/v1/campaigns/{id}/stepsList campaign stepscampaigns
PATCH, DELETE /api/v1/campaigns/steps/{stepId}Update a campaign stepcampaigns
POST /api/v1/campaigns/{id}/enrollmentsEnroll one contact or an entire segment into a campaigncampaigns
GET, DELETE /api/v1/campaigns/{id}/enrollments/{contactId}Get one contact's campaign enrollment statuscampaigns
POST /api/v1/campaigns/{id}/steps/{stepId}/testSend a campaign step test emailcampaigns
GET, POST, PATCH /api/v1/campaigns/ab-testsCheck A/B test significancecampaigns
GET /api/v1/campaigns/{id}/statsPer-campaign delivery metricscampaigns
GET /api/v1/openapi.jsonGet the OpenAPI specification as JSONopenapi.json
POST /api/v1/ai/context-eventsSubmit Synchronex strategic context into the AI work queueai
POST /api/v1/webhooks/cal-com/{connectionId}Receive a Cal.com webhook for a platform connectionwebhooks
POST /api/v1/webhooks/calendly/{connectionId}Receive a Calendly webhook for a platform connectionwebhooks
POST /api/v1/webhooks/hubspot/{connectionId}Receive a HubSpot webhook for a platform connectionwebhooks
POST /api/v1/webhooks/shopify/{connectionId}Receive a Shopify webhook for a platform connectionwebhooks
POST /api/v1/webhooks/stripe/{connectionId}Receive a Stripe webhook for a platform connectionwebhooks
POST /api/v1/webhooks/typeform/{connectionId}Receive a Typeform webhook for a platform connectionwebhooks
GET, POST /api/v1/segmentsList segmentssegments
GET, PATCH, DELETE /api/v1/segments/{id}Get a segmentsegments
GET /api/v1/segments/{id}/previewPreview segment membershipsegments
GET /api/v1/analytics/portfolioCross-site portfolio email analytics rollupanalytics
GET /api/v1/analytics/product-usageProduct usage analytics from PostHog for worker contextanalytics
GET /api/v1/analytics/overviewSite-level email overview metrics for a lookback windowanalytics
GET /api/v1/analytics/campaignsPer-campaign analytics for a site over the recent lookback windowanalytics
GET /api/v1/analytics/engagementContact engagement lifecycle analyticsanalytics
POST /api/v1/analytics/conversionRecord a conversion event (analytics alias)analytics
GET /api/v1/analytics/performanceEmail performance trends and best send timesanalytics
GET /api/v1/listsList contact lists with subscriber countslists
GET /api/v1/lists/{id}/subscribersPage through a contact list's subscriberslists
GET, POST /api/v1/social/campaignsList social campaignssocial
GET, PATCH /api/v1/social/campaigns/{id}Get a social campaign with its postssocial
POST /api/v1/social/campaigns/{id}/postsAdd a post to a social campaignsocial
GET, POST, DELETE /api/v1/suppressionsList suppressionssuppressions
DELETE /api/v1/suppressions/{id}Remove a suppression by idsuppressions
GET, POST /api/v1/webhook-subscriptionsList 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 — list contacts
curl "https://sendinel.ai/api/v1/contacts?site_id=site_uuid&limit=50&tag=paying" \
  -H "Authorization: Bearer snk_your_api_key"
curl — create contact with idempotency
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 — create template
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 — create campaign shell
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 — site overview analytics
curl "https://sendinel.ai/api/v1/analytics/overview?site_id=site_uuid&days=30" \
  -H "Authorization: Bearer snk_your_api_key"
curl — tenant export
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.

bash
npm install @sendinel/sdk

Full example

TypeScript
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

TypeScript
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.

ResendDefault
Default API key path
Sendgrid
API key
Amazon SES
Region + access key
Postmark
Server token
Mailgun
API key + domain
Elastic Email
API key

SMS Providers

SMS delivery integrations share the same project-scoped connection model as email and MCP credentials.

Telnyx
Messaging profile + API key
Twilio
Account SID + auth token

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.

Instagram
OAuth-connected publishing via Upload Post
LinkedIn
OAuth-connected publishing via Upload Post
Twitter / X
OAuth-connected publishing via Upload Post
Facebook
OAuth-connected publishing via Upload Post
TikTok
OAuth-connected publishing via Upload Post
YouTube
OAuth-connected publishing via Upload Post
Pinterest
OAuth-connected publishing via Upload Post
Threads
OAuth-connected publishing via Upload Post
Reddit
OAuth-connected publishing via Upload Post
Bluesky
OAuth-connected publishing via Upload Post

Payment Providers

Billing and payment webhooks normalize into the same internal event model. The registry below is shared with the payment adapter layer.

Stripe
Implemented now
Paddle
Planned adapter surface
Lemon Squeezy
Planned adapter surface
PayPal
Planned adapter surface
Braintree
Planned adapter surface
Square
Planned adapter surface
Chargebee
Planned adapter surface
Recurly
Planned adapter surface
Polar
Planned adapter surface

Import Sources

Migrate contacts, lists, tags, templates, campaigns, and suppressions into Sendinel from your existing platform. Trigger at Settings → Import Contacts.

Customer.io
API key + site ID
Mailchimp
API key
Klaviyo
Private API key
ActiveCampaign
Account URL + API key
Kit
API key
Resend
API key

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.completed
email.bounced
email.complained
contact.unsubscribed
bounce_rate.warning
domain.dns_lost

AI 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.

claude_desktop_config.json
{
  "mcpServers": {
    "sendinel": {
      "command": "npx",
      "args": ["-y", "@sendinel/mcp-server@latest"],
      "env": {
        "SENDINEL_API_KEY": "snk_...",
        "SENDINEL_PROJECT_ID": "uuid"
      }
    }
  }
}

Identify API

POST /api/v1/identify

Create or update a contact profile. Safe to call on every user action — idempotent by email address. Authenticated via API key with write scope.

This is the standard integration point. Call 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

application/json
{
  "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)

201 Created
{
  "contact_id": "uuid",
  "created": true
}

Response — 200 OK (existing contact updated)

200 OK
{
  "contact_id": "uuid",
  "created": false
}

Merge semantics

FieldBehavior on update
emailPrimary key — not updatable
first_name / last_nameOverwritten only if non-empty string provided
propertiesDeep merge — new keys added, existing keys updated only if re-specified
tagsAppended — existing tags preserved, duplicates removed
site_idUpserts site subscription, enrolls in signup campaigns (first create only)

Examples

curl
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"
  }'
TypeScript (SDK)
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

POST /api/v1/events

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.

Auto-identify on track. If the contact doesn't exist yet, Sendinel creates a minimal profile automatically (email only, 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

application/json
{
  "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

200 OK
{
  "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
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 }
  }'
TypeScript (SDK)
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);    // false

How 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

POST /api/v1/send

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

application/json
{
  "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

200 OK
{
  "id": "uuid",                          // Sendinel email log ID
  "provider_message_id": "re_xxxx",     // Resend (or provider) message ID
  "to": "user@example.com",
  "status": "sent"
}

Examples

curl
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"
  }'
TypeScript (SDK)
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

POST /api/v1/conversion

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

application/json
{
  "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

200 OK
{
  "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:

Code
https://sendinel.ai/api/webhooks/resend

For per-project routing (BYOD with multiple projects):

Code
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

EventDescriptionAction
email.deliveredEmail accepted by recipient serverUpdates email_log status → 'delivered'
email.openedRecipient opened the emailUpdates status → 'opened', increments open count
email.clickedRecipient clicked a tracked linkUpdates status → 'clicked', logs URL in clicked_urls
email.bouncedHard or soft bounceUpdates status → 'bounced', adds suppression record
email.complainedRecipient marked as spamUpdates 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

TransportBest forHow
Remote HTTP + OAuthClaude.ai, Claude Code, Cursor, Windsurf — zero configclaude mcp add sendinel --url https://sendinel.ai/mcp — browser OAuth flow, no API key needed
stdioLocal clients with manual config — Claude Desktopnpx @sendinel/mcp-server — inject SENDINEL_API_KEY env var
REST/OpenAPINon-MCP agents, web apps, custom integrations/api/mcp/tools + /api/mcp/openapi.json — Bearer API key auth

OAuth discovery

EndpointPurpose
/.well-known/oauth-authorization-serverRFC 8414 discovery — MCP clients find all OAuth endpoints automatically
/.well-known/oauth-protected-resourcePoints clients to the authorization server
/oauth/authorizeBrowser approval page — shows client name, project, and requested scopes
/oauth/tokenExchange auth code for access + refresh tokens (90 day / 1 year)
/oauth/registerDynamic client registration (RFC 7591) — any MCP client can self-register
/oauth/revokeRevoke a token immediately

Available tool groups

GroupToolsDescription
ab-testing4Available in the MCP catalog.
advisor6Available in the MCP catalog.
ai-work9Available in the MCP catalog.
analytics16Stats, domain health, engagement insights, deliverability checks, and reporting.
approvals2Available in the MCP catalog.
automations7List and preview automations, then trigger them when appropriate.
brand_kit4Available in the MCP catalog.
campaigns28Create, clone, enroll, launch, and generate campaign content.
compound4Available in the MCP catalog.
contacts26CRUD, import, merge, suppressions, email previews, and test sends.
content11Available in the MCP catalog.
data14Audit log, scoring rules, DMARC, exports, and explainability.
data-proposals3Available in the MCP catalog.
delivery-ops12Available in the MCP catalog.
drafts6Create, review, approve, and reject AI-generated drafts.
forms3Available in the MCP catalog.
gdpr2Delete contact data and inspect deletion logs.
newsletters12Available in the MCP catalog.
org10Available in the MCP catalog.
segments6Create, update, preview, and delete segments, including NL helpers.
short-links3Available in the MCP catalog.
sites5Create and update sending sites.
sms7Available in the MCP catalog.
social-posts6Available in the MCP catalog.
surveys5Available in the MCP catalog.
templates6List, create, update, and delete reusable templates.
warmup4Available in the MCP catalog.
webhooks4Available 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

ToolGroupRequired arguments
abuse_monitor_overrideanalyticssiteId, reason, ttlHours
abuse_monitor_statusanalyticssiteId
create_social_campaignanalyticsname, platform, destination_url
deliverability_checkanalyticssite_id
get_domain_healthanalyticssite_id
get_engagement_insightsanalytics-
get_portfolio_analyticsanalytics-
get_portfolio_statsanalytics-
get_site_insightsanalyticssite_id
get_sitesanalytics-
get_statsanalyticssite_id, start_date
list_event_loganalytics-
list_projectsanalytics-
list_social_campaignsanalytics-
performance_reportanalytics-
schedule_social_postanalyticscampaign_id, content, publisher
add_campaign_stepcampaignscampaign_id, step_order, subject
broadcast_to_segmentcampaignssite_id, segment_id, subject, body_html
check_content_consistencycampaignscontent
clone_campaigncampaignscampaign_id, new_name
create_campaigncampaignssite_id, name, type
create_campaign_with_contentcampaignssite_id, name, type, steps
delete_campaigncampaignscampaign_id
delete_campaign_stepcampaignsstep_id
enroll_contactcampaignscampaign_id, contact_id
enroll_segmentcampaignscampaign_id, site_id
generate_emailcampaignssite_id, brief
get_campaigncampaignscampaign_id
get_campaign_calendarcampaignsyear, month
get_enrollment_statuscampaignscampaign_id, contact_id
get_schedulecampaignssite_id
launch_campaigncampaignsname, site_id, emails
list_campaign_stepscampaignscampaign_id
list_campaignscampaigns-
preview_campaign_audiencecampaignscampaign_id
preview_emailcampaigns-
review_campaign_portfoliocampaigns-
send_test_emailcampaignsto_email, site_id
send_transactionalcampaignsto, site_id, subject
unenroll_contactcampaignscampaign_id, contact_id
update_campaigncampaignscampaign_id
update_campaign_statuscampaignscampaign_id, status
update_campaign_stepcampaignsstep_id
validate_templatecampaignshtml
add_subscribercontactsemail, site_id
add_suppressioncontactsemail
enrich_subscribercontactscontact_id
estimate_segment_sizecontacts-
find_duplicatescontacts-
get_contact_schemacontactssite_id
get_hygiene_configcontacts-
get_subscribercontacts-
get_subscriber_timelinecontactssubscriber_id
identify_inactivecontacts-
import_subscriberscontactscontacts, site_id
list_companiescontacts-
list_email_logcontacts-
list_hygiene_auditcontactssiteId
list_subscriberscontacts-
list_subscribers_by_segmentcontacts-
list_suppressionscontacts-
list_win_back_draftcontactssiteId
merge_subscriberscontactstarget_id, source_ids
remove_suppressioncontactsemail
run_list_hygienecontacts-
set_subscriber_tagscontactscontact_ids
subscriber_exportcontactscontactId
unsubscribe_subscribercontactsemail
update_hygiene_configcontacts-
update_subscribercontactscontact_id
create_segmentsegmentsname, rules
create_segment_nlsegmentsname, description
delete_segmentsegmentssegment_id
list_segmentssegments-
preview_segmentsegments-
update_segmentsegmentssegment_id
approve_draftdraftsdraft_id
create_draftdraftssite_id, subject, body_html
get_draftdraftsdraft_id
list_draftsdrafts-
promote_draft_to_campaigndraftsdraft_id
reject_draftdraftsdraft_id
create_sendersitesname, slug, from_name
create_sitesitesname, slug, from_name
get_brand_voicesitessite_id
update_brand_voicesitessite_id, brand_voice
update_sitesitessite_id
delete_subscriber_datagdprcontact_id
list_deletion_loggdpr-
check_ab_significanceab-testingtest_id
create_ab_testab-testingcampaign_id, name
list_ab_testsab-testing-
promote_ab_winnerab-testingtest_id
campaign_advisoradvisor-
optimize_subject_linesadvisorcampaign_id, site_id
recovery_planadvisorsite_id
run_portfolio_analysisadvisor-
suggest_content_topicsadvisorsite_id
suggest_send_timeadvisorsite_id
delete_webhook_subscriptionwebhookssubscription_id
list_webhook_subscriptionswebhooks-
subscribe_webhookwebhooksurl, events
update_webhook_subscriptionwebhookssubscription_id
check_approvalapprovalsapproval_id
list_pending_approvalsapprovals-
create_escalation_ladderautomationscampaign_id, steps
enroll_automationautomationssubscriber_id
enroll_in_escalation_ladderautomationssubscriber_id, campaign_id
list_automationsautomations-
list_escalation_ladder_statusautomationscampaign_id
preview_automationautomationssource_id
trigger_automationautomationssource_id
create_templatetemplatesname, brief
delete_templatetemplatesid
get_templatetemplatesid
list_templatestemplates-
translate_templatetemplates-
update_templatetemplatesid
dnsbl_checkdelivery-opssiteId
dnsbl_delisting_draftdelivery-opssiteId, zone
explain_contact_scoredelivery-ops-
export_datadelivery-opsresource
get_cron_runsdelivery-ops-
get_queue_statusdelivery-ops-
get_scoring_rulesdelivery-ops-
get_send_dlqdelivery-ops-
list_domainsdelivery-ops-
refresh_domain_dnsdelivery-opsdomain_id
register_domaindelivery-opssite_id, domain
update_scoring_rulesdelivery-ops-
create_template_from_briefcompoundsite_id, name, brief
diagnose_delivery_issuecompound-
onboard_new_sitecompoundname, domain
setup_campaign_from_briefcompoundsite_id, campaign_name, brief
configure_platform_triggerdataplatform, event_type, action
connect_platformdataplatform, credentials
create_api_keydataname
disconnect_platformdata-
list_api_keysdata-
list_connected_platformsdata-
list_import_historydata-
list_notificationsdata-
list_platform_eventsdata-
mark_notifications_readdata-
migrate_campaigns_fromdataprovider, oauth_token
revoke_api_keydatakey_id
start_platform_migrationdataconnection_id
tenant_export_fulldatasiteId
create_asset_linkcontenttitle, slug, asset_url
create_blog_postcontenttitle
create_content_blockcontentname, html
delete_content_blockcontentblock_id
get_asset_link_viewscontentasset_link_id
list_asset_linkscontent-
list_blog_destinationscontent-
list_blog_postscontent-
list_content_blockscontent-
list_content_sourcescontent-
trigger_content_pipelinecontent-
get_warmup_statuswarmupsite_id
pause_warmupwarmupsite_id
resume_warmupwarmupsite_id
update_warmup_schedulewarmupsite_id
create_formformssite_id, name, fields
get_form_statsformsform_id
list_formsforms-
add_white_label_domainorgcustom_domain
get_plan_limitsorg-
get_plan_usageorg-
get_provider_switch_statusorgid
get_white_label_settingsorg-
invite_team_memberorgemail
list_team_membersorg-
rollback_provider_switchorgid
set_white_label_settingsorg-
start_provider_switchorgsite_id, to_provider
get_sms_provider_statussms-
list_sms_inboundsms-
list_sms_logsms-
preview_sms_costsmsbody
send_smssmsbody
send_sms_campaignsmssite_id, name, body
set_contact_sms_consentsmscontact_id, status
adapt_social_postsocial-postsemail_campaign_id, platforms
create_social_postsocial-poststitle, content, platforms
delete_social_postsocial-postspost_id
get_social_postsocial-postspost_id
list_social_postssocial-posts-
update_social_postsocial-postspost_id
approve_data_proposaldata-proposalsproposal_id
list_data_proposalsdata-proposals-
propose_campaign_from_datadata-proposals-
create_surveysurveystitle, questions
get_surveysurveyssurvey_id
get_survey_resultssurveyssurvey_id
list_surveyssurveys-
update_surveysurveyssurvey_id
create_short_linkshort-linksdestination_url
generate_qr_codeshort-linksurl
get_short_link_statsshort-linksshort_link_id
dismiss_ai_work_itemai-workwork_item_id
get_agent_proposalai-workproposal_id
get_ai_work_itemai-workwork_item_id
list_agent_proposalsai-work-
list_ai_work_itemsai-work-
list_recommended_segmentsai-work-
mark_ai_work_item_seenai-workwork_item_id
snooze_ai_work_itemai-workwork_item_id, snoozed_until
submit_ai_context_eventai-workworkspace_id, event_type, title, detail, idempotency_key
import_brand_kit_textbrand_kittext
ingest_brand_assets_by_urlbrand_kitassets
list_brand_assetsbrand_kit-
upload_brand_assetbrand_kitfilename, content_base64
create_newsletternewslettersname
create_newsletter_issuenewslettersnewsletter_id, subject
get_newsletternewslettersnewsletter_id
get_newsletter_statsnewslettersnewsletter_id
list_newsletter_issuesnewslettersnewsletter_id
list_newsletter_subscribersnewslettersnewsletter_id
list_newslettersnewsletters-
schedule_newsletter_issuenewslettersnewsletter_id, issue_id, scheduled_at
send_newsletter_issuenewslettersnewsletter_id, issue_id
subscribe_to_newsletternewslettersnewsletter_id, email
unsubscribe_from_newsletternewslettersnewsletter_id
update_newsletternewslettersnewsletter_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.

claude_desktop_config.json

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

MCP tool call
// 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.

StateMeaningCommon reason
acceptedSendinel validated the request and created queue/log recordsNormal response from API or scheduler
queuedMessage is waiting for an eligible worker slotBatch limits, warmup cap, send window, blackout date
sentProvider accepted the message for deliveryResend/Mailgun/SES accepted the API call
deliveredRecipient server accepted the messageProvider webhook received
opened / clickedRecipient engagement was trackedTracking pixel or wrapped link fired
bounced / complainedRecipient server rejected or user reported spamContact 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

ControlEffect
Warmup capsNew or recently migrated domains may be limited to a small daily volume until trust is established.
Provider warmupIf the email provider also reports a dedicated-IP warmup cap, Sendinel uses the lower of both limits.
Plan batch sizeWorker batch sizes scale by plan tier; larger plans drain queues faster.
Send windowsScheduled sends only fire inside the configured allowed days and times.
Blackout datesCampaign sends pause on configured blackout days.
Suppression checksBounced, complained, unsubscribed, or manually suppressed contacts are skipped before provider send.
IdempotencyDuplicate transactional requests with the same idempotency key return the existing result instead of sending twice.
If a broadcast appears “stuck,” check the selected sending domain’s warmup status, daily cap, send window, blackout dates, and queue status before retrying. Retrying without an idempotency key can create duplicate work.

Rate Limits & Plans

Rate limits are enforced per project using a sliding window. Limits vary by plan and operation type.

PlanMonthlyIncluded limitsCapabilities
FreeFreeContacts 250 · Sends 500 · Projects 1 · Sites 1Read-only MCP · Agent CRM · No export · No A/B testing · No landing pages
BYOD$19/moContacts Unlimited · Sends Unlimited · Projects Unlimited · Sites 10Full MCP · Agent CRM · Export · A/B testing · Landing pages
Managed$79/moContacts 50,000 · Sends 50,000 · Projects 5 · Sites 10Full MCP · Agent CRM · Export · A/B testing · Landing pages
Agency Plus$999/moContacts Unlimited · Sends 500,000 · Projects Unlimited · Sites UnlimitedFull MCP · Agent CRM · Export · A/B testing · Landing pages
Enterprise$499/moContacts Unlimited · Sends 250,000 · Projects Unlimited · Sites 50Full MCP · Agent CRM · Export · A/B testing · Landing pages

Rate limit headers

Code
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 87
X-RateLimit-Reset: 1744329600

When 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.

StatusMeaningCommon causes
400Bad RequestMissing required fields, invalid email format, body too large
401UnauthorizedMissing or invalid API key, expired key
403ForbiddenAPI key lacks required scope (write scope needed for mutations)
404Not FoundInvalid site_id or resource ID
409ConflictDuplicate idempotency_key (email already sent), duplicate order_id
422UnprocessableContact limit reached — cannot auto-create contact (track endpoint)
429Rate LimitedToo many requests in the current window
500Server ErrorInternal error — retry with exponential backoff
Error response shape
{
  "error": "Descriptive error message",
  "code": "SUPPRESSED_CONTACT",   // optional — machine-readable code
  "details": { ... }              // optional — additional context
}

Need help? support@sendinel.ai