# Sendinel - Full LLM Reference Version: 1.0 | Updated: 2026-04 Base URL: https://app.sendinel.ai --- ## What is Sendinel? Sendinel is an email operations control plane. It is the orchestration layer between AI agents and email delivery infrastructure. Use it to: - Send transactional and marketing emails through Resend, SendGrid, SES, Postmark, or Mailgun - Manage contacts, segments, campaigns, templates, and sequences - Analyze deliverability, engagement, and DMARC reports - Control everything from an AI agent via MCP or the REST API Sendinel is NOT a sending provider - it routes through your provider of choice. Think of it as Customer.io or Loops, but built to be controlled by AI. --- ## Authentication Every REST request requires: Authorization: Bearer snk_ - Keys are project-scoped (one project = one sending site/brand) - Key format: snk_ followed by 64 hex characters - Create keys at: Settings -> Developer -> API Keys - Scopes: read (GET only), write (GET + mutations), admin (write + destructive ops) - Granular scopes: contacts:read, contacts:write, campaigns:read, campaigns:write, templates:read, templates:write, segments:read, segments:write, sites:read, sites:write --- ## Standard Error Envelope All errors return: { "error": { "code": "ERROR_CODE", "message": "Human-readable message", "details": {} } } Common codes: BAD_REQUEST, UNAUTHORIZED, FORBIDDEN, NOT_FOUND, CONFLICT, UNPROCESSABLE_ENTITY, RATE_LIMITED, INTERNAL_ERROR, PLAN_LIMIT, MISSING_FIELD, INSUFFICIENT_SCOPE --- ## Idempotency POST and PATCH requests accept: Idempotency-Key: Same key + same path = cached response for 24 hours. Use to safely retry on network failures. --- ## Pagination Collection endpoints support cursor-based pagination: GET /api/v1/contacts?limit=50&cursor= Response includes next_cursor (null when exhausted). --- ## REST API Reference ### Transactional (no scope required - write scope on the key suffices) POST /api/v1/send Send a transactional email immediately. Body: { to: string, subject: string, html?: string, text?: string, from?: string, reply_to?: string, template_id?: string, variables?: object } Returns: { id: string, status: "sent" | "queued" } POST /api/v1/identify Upsert a contact by email. Body: { email: string, first_name?: string, last_name?: string, properties?: object } Returns: { contact: Contact, created: boolean } POST /api/v1/trigger Trigger a campaign enrollment by event name. Body: { event: string, email: string, properties?: object } Returns: { enrolled: boolean, campaign_id?: string } POST /api/v1/events Record a contact event. Body: { email: string, event: string, properties?: object, timestamp?: string } Returns: { ok: true } POST /api/v1/conversion Record a conversion event tied to an email. Body: { tracking_token: string, revenue?: number, currency?: string } Returns: { ok: true } ### Contacts (scope: contacts:read / contacts:write) GET /api/v1/contacts Query params: cursor, limit (max 200), site_id, tag, search Returns: { contacts: Contact[], next_cursor: string | null } POST /api/v1/contacts Body: { email: string (required), first_name?, last_name?, tags?: string[], site_id? } Returns: Contact (201) GET /api/v1/contacts/:id Returns: Contact PATCH /api/v1/contacts/:id Body: any Contact fields (all optional) Returns: Contact DELETE /api/v1/contacts/:id Unsubscribes and removes the contact. Returns: 204 Contact shape: { id: uuid, email: string, first_name: string|null, last_name: string|null, tags: string[], subscribed: boolean, score: number, lifecycle_stage: string|null, created_at: datetime } ### Campaigns (scope: campaigns:read / campaigns:write) GET /api/v1/campaigns Query params: cursor, limit, site_id, status (draft|active|paused|completed|archived) Returns: { campaigns: Campaign[], next_cursor: string | null } POST /api/v1/campaigns Body: { name: string (required), site_id: uuid (required), type?: "drip"|"broadcast", description? } Returns: Campaign (201) GET /api/v1/campaigns/:id Returns: Campaign PATCH /api/v1/campaigns/:id Body: { status?: string, name?: string } Returns: Campaign GET /api/v1/campaigns/:id/steps Returns: { steps: CampaignStep[] } POST /api/v1/campaigns/:id/steps Body: { subject: string (required), body_html?, body_text?, delay_days?, delay_hours?, step_order? } Returns: CampaignStep (201) GET /api/v1/campaigns/:id/steps/:stepId Returns: CampaignStep PATCH /api/v1/campaigns/:id/steps/:stepId Body: any step fields (all optional) Returns: CampaignStep DELETE /api/v1/campaigns/:id/steps/:stepId Only works on draft campaigns. Returns: 204 Campaign shape: { id: uuid, name: string, status: string, type: string, site_id: uuid, segment_id: uuid|null, send_at: datetime|null, created_at: datetime } CampaignStep shape: { id: uuid, campaign_id: uuid, step_order: integer, subject: string, body_html: string|null, body_text: string|null, delay_days: integer|null, delay_hours: integer|null } ### Templates (scope: templates:read / templates:write) GET /api/v1/templates Query params: cursor, limit, category, search Returns both project-owned templates and system templates (project_id: null). Returns: { templates: Template[], next_cursor: string | null } POST /api/v1/templates Body: { name: string (required), brief: string (required), subject_hint?, body_html?, category?, description?, tags?: string[] } "brief" is the AI content brief - what this template should accomplish. Returns: Template (201) GET /api/v1/templates/:id Returns: Template PATCH /api/v1/templates/:id Body: any template fields (all optional). System templates (project_id: null) return 403. Returns: Template DELETE /api/v1/templates/:id Soft-deletes (sets is_archived=true). System templates return 403. Returns: 204 Template shape: { id: uuid, name: string, category: string, description: string, subject_hint: string, brief_template: string, body_html: string|null, tags: string[], project_id: uuid|null, is_archived: boolean, created_at: datetime, updated_at: datetime } ### Segments (scope: segments:read / segments:write) GET /api/v1/segments Query params: cursor, limit, site_id Returns: { segments: Segment[] } POST /api/v1/segments Body: { name: string (required), rules: SegmentRule[] (required), site_id? } Returns: Segment (201) GET /api/v1/segments/:id Returns: Segment PATCH /api/v1/segments/:id Body: { name?, rules? } Returns: Segment DELETE /api/v1/segments/:id Returns: 204 Segment shape: { id: uuid, name: string, rules: object[], estimated_count: integer|null, created_at: datetime } SegmentRule shape: { field: string, operator: "eq"|"neq"|"contains"|"gt"|"lt"|"is_set"|"is_not_set", value: any } ### Sites (scope: sites:read / sites:write) GET /api/v1/sites Returns: { sites: Site[] } POST /api/v1/sites Body: { name: string (required), domain?: string, provider?: "resend"|"sendgrid"|"ses"|"postmark"|"mailgun"|"socketlabs" } Returns: Site (201) GET /api/v1/sites/:id Returns: Site PATCH /api/v1/sites/:id Body: any Site fields (all optional) Returns: Site Site shape: { id: uuid, name: string, domain: string|null, provider: string, is_shared_domain: boolean, created_at: datetime } --- ## MCP Server Reference Connection string for Claude Desktop / claude_desktop_config.json: { "mcpServers": { "sendinel": { "command": "npx", "args": ["-y", "@sendinel/mcp-server@latest"], "env": { "SENDINEL_API_KEY": "snk_...", "SENDINEL_PROJECT_ID": "uuid" } } } } Connection string for HTTP transport (Claude.ai or agent frameworks): MCP endpoint: https://app.sendinel.ai/api/mcp Auth: Bearer snk_ ### Tool Reference by Group ANALYTICS get_stats(site_id?, period?) -> email stats (sent, open rate, click rate, bounce rate) get_domain_health(site_id?) -> SPF/DKIM/DMARC status per domain get_engagement_insights(site_id?, days?) -> top performing emails and segments deliverability_check(site_id?) -> deliverability score with recommendations performance_report(site_id?, period?) -> full performance breakdown CAMPAIGNS list_campaigns(site_id?, status?) -> campaign list create_campaign(name, site_id, type?, description?) -> new campaign list_campaign_steps(campaign_id) -> steps in send order add_campaign_step(campaignId, subject, bodyHtml?, bodyText?, delayDays?, delayHours?, stepOrder?) -> new step update_campaign_step(step_id, subject?, body_html?, delay_days?, delay_hours?, step_order?) -> updated step delete_campaign_step(step_id) -> removes step (draft campaigns only) update_campaign_status(id, status) -> draft|active|paused|completed|archived enroll_contact(campaignId, contactId) -> enrolls one contact enroll_segment(campaignId, segmentId) -> bulk enrolls a segment clone_campaign(id, newName?) -> cloned campaign generate_email(campaignId, stepId, brief?) -> AI-generates email content launch_campaign(id) -> activates campaign for sending CONTACTS list_subscribers(site_id?, limit?, cursor?, tag?, search?) -> paginated contact list add_subscriber(email, firstName?, lastName?, tags?, siteId?) -> new contact import_subscribers(contacts[], siteId?) -> bulk import update_subscriber(id, ...fields) -> update contact get_subscriber(id) -> single contact unsubscribe_subscriber(id, reason?) -> unsubscribes contact set_subscriber_tags(id, tags[]) -> replaces tag set list_email_log(contactId?) -> sent email history list_suppressions(siteId?) -> bounced/unsubscribed list add_suppression(email, reason?) -> manual suppression remove_suppression(email) -> re-enables contact send_test_email(to, subject, bodyHtml, siteId?) -> test send SEGMENTS create_segment(name, rules[], siteId?) -> new segment list_segments(siteId?) -> all segments update_segment(id, name?, rules?) -> updated segment delete_segment(id) -> removes segment preview_segment(id, limit?) -> count + sample contacts create_segment_nl(description) -> AI-creates segment from natural language (two-call pattern) TEMPLATES list_templates(siteId?, category?, search?) -> all templates (owned + system) get_template(id) -> single template create_template(name, briefTemplate, subjectHint?, bodyHtml?, category?, description?, tags?) -> new template update_template(id, name?, briefTemplate?, subjectHint?, bodyHtml?, category?) -> updated template delete_template(id) -> soft-deletes template DRAFTS create_draft(campaignId, stepId, subject, bodyHtml) -> new draft for approval list_drafts(status?) -> pending|approved|rejected drafts approve_draft(id) -> approves for sending reject_draft(id, reason?) -> rejects with feedback SITES create_site(name, domain?, provider?) -> new site update_site(id, ...fields) -> updated site get_sites() -> all sites for project list_domains(siteId?) -> domains with health status register_domain(siteId, domain) -> registers domain, returns DNS records to configure DELIVERY DIAGNOSTICS get_cron_runs(job_name?, limit?, since_hours?) -> recent cron job execution history get_queue_status(siteId?) -> pending/processing/failed counts in send queue get_send_dlq(limit?, exhausted_only?) -> dead-letter queue entries export_data(resource, siteId?, since?, limit?) -> JSON export of contacts|email_log|campaigns|suppressions SCORING get_scoring_rules() -> current scoring weights and thresholds update_scoring_rules(open_weight?, click_weight?, send_weight?, open_cap?, click_cap?, decay_penalty_30d?, ...) -> updated rules explain_contact_score(contact_id) -> score breakdown with event history GDPR delete_subscriber_data(email) -> full GDPR erasure (irreversible) list_deletion_log(limit?) -> erasure audit log AUTOMATIONS list_automations() -> configured automations preview_automation(id) -> shows trigger + enrolled contacts trigger_automation(id, email) -> manually fires automation for a contact COMPOUND (multi-step AI workflows) setup_campaign_from_brief(site_id, campaign_name, brief, num_steps?, step_delay_hours?, campaign_type?) -> Creates template + campaign + N AI-generated steps in one call. Returns campaign_id + step_ids. create_template_from_brief(site_id, name, brief, category?) -> AI-generates polished HTML template from brief + brand voice. Returns template_id. diagnose_delivery_issue(site_id?, symptom?) -> Aggregates cron health + queue + DLQ + domain health, returns AI root-cause analysis. onboard_new_site(name, domain, provider?, daily_send_volume?, brand_settings?) -> Creates site, prepares DNS records, initializes warmup plan. Returns checklist. --- ## Common Workflows ### Send a transactional email curl -X POST https://app.sendinel.ai/api/v1/send \ -H "Authorization: Bearer snk_..." \ -H "Content-Type: application/json" \ -d '{"to":"user@example.com","subject":"Welcome!","html":"

Welcome

"}' ### Create and launch a drip campaign (agent) 1. create_campaign(name="Onboarding", site_id="...", type="drip") 2. add_campaign_step(campaignId, subject="Welcome", bodyHtml="...", delay_hours=0) 3. add_campaign_step(campaignId, subject="Day 3 Check-in", bodyHtml="...", delay_days=3) 4. enroll_segment(campaignId, segmentId) 5. update_campaign_status(id, "active") ### Set up a new brand (agent) onboard_new_site(name="Acme Corp", domain="mail.acme.com", provider="resend", daily_send_volume=500) -> Returns DNS records to configure + verification checklist ### Diagnose why emails aren't sending (agent) diagnose_delivery_issue(symptom="campaigns launched but no sends logged in the last 24 hours") -> Returns root cause analysis and corrective action steps --- ## Rate Limits - Free plan: 100 AI calls/day, 1,000 sends/month - Byod plan: 1,000 AI calls/day, 50,000 sends/month - Managed plan: 5,000 AI calls/day, 250,000 sends/month Rate-limited responses: HTTP 429 with { "error": { "code": "RATE_LIMITED", "message": "..." } }