# Nitrosend The AI-native email marketing platform — manage your email from Claude, GPT, Gemini, Codex, or any AI tool ## Documentation ### Introduction Path: `/introduction` The AI-native email marketing platform — manage your email from Claude, GPT, Gemini, Codex, or any AI tool Nitrosend The AI-native email marketing platform. Built for builders and marketers who want to run their email marketing from the AI tools they already use. Every ESP on the market was designed for humans to operate manually. You log in, click through menus, drag blocks, write copy, schedule sends. Nitrosend is different. It's built from the ground up so that AI does the work. Info All plans, including free plans, have full access to Nitrosend MCP/API/CLI. Plan limits still apply to send volume, seats, AI action allowances, and paid add-ons. The Free plan includes 8,000 free emails up front, then 500 free emails every month. How Nitrosend works Instead of logging into a dashboard, you stay in your AI tool — Claude, ChatGPT, Codex, Gemini, Cursor — and tell it what you want. Nitrosend connects via MCP (Model Context Protocol) and a REST API so your AI assistant has full access to your email marketing. Copy You: "Create a 5-email welcome series for new subscribers" Claude: Done. I've built the flow with a welcome email, a 3-day follow-up, a product highlight on day 7, a case study on day 10, and a special offer on day 14. Want me to send a test? No dashboard. No drag-and-drop. Just tell AI what you need and it happens. What you can do Send emails & campaigns Compose, preview, test, and send — all through natural language prompts in your AI tool. Build automation flows Welcome series, abandoned cart, re-engagement — describe the flow, AI builds it. Manage your audience Import contacts, create segments, tag and organize — through conversation. Connect via MCP Connect Nitrosend to Claude, ChatGPT, Cursor, Codex, Gemini CLI, VS Code, Windsurf, Zed, and more. What makes Nitrosend different AI-native, not AI-bolted-on Other ESPs added GPT wrappers for subject lines and called it "AI features." Nitrosend was built so AI is the primary interface. MCP integration means Claude, GPT, and Gemini can access every feature — campaigns, flows, templates, contacts, analytics — natively. MCP-first architecture Nitrosend speaks MCP (Model Context Protocol), the open standard for connecting AI tools to external services. Connect once, and your AI assistant can manage your entire email operation. No coding, no middleware. Intelligence that compounds Set up your Brand Kit and AI Memory , and every email Nitrosend sends gets smarter. Your AI learns your tone, your audience, and your goals — then applies that context to every campaign, flow, and template it creates. Works with every major AI tool Claude Desktop, Claude Code, Claude Cowork, ChatGPT, Cursor, Codex CLI, Gemini CLI, VS Code, Windsurf, Zed — Nitrosend connects anywhere the client supports MCP. Use the REST API for custom apps, automation platforms, and server-side integrations. Get started 1 Sign up Create your account at app.nitrosend.com 2 Connect your AI tool Add Nitrosend as an MCP server or connector — no API key needed for the standard setup. Follow the guide for your tool: Claude Desktop · Claude Cowork · Claude Code · Cursor · ChatGPT · Codex · Gemini CLI · VS Code · Windsurf · Zed 3 Sign in when prompted Most clients open a Nitrosend sign-in window on first use. If you are using a terminal client and the prompt does not appear immediately, open the client's MCP manager and finish OAuth there. 4 Start prompting Try this to get a full setup in one go: Copy Connect into Nitrosend and based on my brand, give me a suggested email flow to set up and guide me through set up. Make the email design modern and on brand against my website. Your AI will scrape your brand, suggest a flow, build it, and walk you through sending a test. ### Quickstart Path: `/quickstart` Send your first email in under 5 minutes — connect your AI tool via MCP and start prompting Get up and running with Nitrosend in under 5 minutes. Choose your path: Info All plans, including free plans, have full access to Nitrosend MCP/API/CLI. Plan limits still apply to send volume, seats, AI action allowances, and paid add-ons. The Free plan includes 8,000 free emails up front, then 500 free emails every month. Tip Are you an AI agent? Read the Agent Onboarding page for the full tool surface, send modes, and the headless signup-to-send path, or fetch the machine-readable skill at nitrosend.com/skill.md . Quickstart with AI (recommended) The fastest way to get started. Connect your AI tool and let it handle the setup. 1. Create your account Sign up at app.nitrosend.com . 2. Connect your AI tool Add Nitrosend as an MCP server, then sign in. Pick your tool: Claude Code Claude Desktop / Claude.ai ChatGPT Codex CLI Cursor Gemini CLI Run one command in your terminal: Copy claude mcp add --transport http nitrosend https://api.nitrosend.com/mcp Then run /mcp inside Claude Code and complete the Nitrosend sign-in. Or point your agent at the install skill, nitrosend.com/SKILL.md , and it connects itself. Enable the official Nitrosend connector, not the CLI. In Claude Desktop (or Cowork), go to Settings → Connectors , search for Nitrosend and click Connect . On Claude.ai, open claude.ai/directory/nitrosend and click Connect . Sign in when prompted. No API key, no URL to paste. Install the official Nitrosend app from the ChatGPT app directory, click Connect , and sign in — no config, no API key. Install the Nitrosend app in ChatGPT → Don't see the direct link? Open Settings → Apps in ChatGPT, search Nitrosend , and connect. Full walkthrough in the ChatGPT guide . Add the server, then sign in: Copy codex mcp add nitrosend --url https://api.nitrosend.com/mcp codex mcp login nitrosend Codex CLI connects directly to the remote MCP server. For ChatGPT, use the ChatGPT tab. Nitrosend is an official plugin on the Cursor Marketplace. In Cursor's Agent chat, run: Copy /add-plugin nitrosend Or click Add to Cursor on the listing: cursor.com/marketplace/nitrosend . The first Nitrosend tool call opens the sign-in window. No API key, no config file. Prefer to manage MCP servers yourself? Add the same server to ~/.cursor/mcp.json (all projects) or .cursor/mcp.json (this repo): Copy { "mcpServers" : { "nitrosend" : { "url" : "https://api.nitrosend.com/mcp" } } } Full walkthrough in the Cursor guide . Add Nitrosend to ~/.gemini/settings.json : Copy { "mcpServers" : { "nitrosend" : { "httpUrl" : "https://api.nitrosend.com/mcp" } } } Restart Gemini CLI and sign in when prompted. Using VS Code , Windsurf , Zed , or another client? See the full integration guides . 3. Start prompting Try this to get a full setup in one go: Copy Connect into Nitrosend and based on my brand, give me a suggested email flow to set up and guide me through set up. Make the email design modern and on brand against my website. Your AI will scrape your brand, suggest a flow, build it, and walk you through sending a test. Tip Other great first prompts: "Check my account status and tell me what I need to set up" "Create a 5-email welcome series for new subscribers" "Send a test email to me@example.com " Quickstart with the dashboard Prefer to set things up manually? Here's the traditional path. 1. Create your account Sign up at app.nitrosend.com and complete the onboarding wizard. Confirming your company identity reserves a protected hello@ .nitrosend.net address derived from the Brand Kit company name—not the account owner's name. 2. Activate a sender In Settings > Domains ( app.nitrosend.com/my/settings/domains ), activate the protected Nitrosend address reserved for your brand, or add the DNS records for a customer-owned sending subdomain. Activation is an immediate local database operation under Nitrosend's already verified shared sender root; it creates no per-brand DNS, DKIM, provider identity, tenant resource, or background job. If a custom sender is already selected, Nitrosend preserves it until you explicitly choose another sender. 3. Import contacts Go to Contacts > Import ( app.nitrosend.com/my/contacts/import ) and upload a CSV, or add contacts manually. Each contact needs at least an email address. 4. Create a template Head to Templates ( app.nitrosend.com/my/templates ) and use the drag-and-drop editor to design your email. Your brand colors and logo are applied automatically. 5. Send a campaign Go to Campaigns > New ( app.nitrosend.com/my/campaigns ), select your template and audience, and hit send. Using the REST API If you prefer to integrate programmatically, grab your API key from Settings > API Keys ( app.nitrosend.com/my/settings/api-keys ) and check the API Reference . Copy curl -X POST https://api.nitrosend.com/v1/my/messages \ -H "Authorization: Bearer nskey_live_..." \ -H "Content-Type: application/json" \ -H "Idempotency-Key: welcome-user-123" \ -d '{ "channel": "email", "to": "user@example.com", "subject": "Hello from Nitrosend", "html": " Welcome! ", "body": "Welcome!" }' Live email requires one ready, selected sender. Materialize the reserved Nitrosend brand-subdomain sender with POST /v1/my/brands/{sid}/prepare_sending , or verify a domain you own. Send {"subdomain": " "} to choose the name; with no body the call derives it from the company name and needs the complete Brand Kit. The operation completes synchronously and selects the new sender only when the brand has no existing selection. An eligible first API request can perform the same local materialization before admission. If the shared root is unavailable, the API returns retryable hosted_sender_root_unavailable , stores no message, and the same idempotency key is safe to retry. ### Agent Onboarding Path: `/agent-onboarding` How an AI agent connects to Nitrosend and sends its first email — the MCP path, the headless REST path, and the machine-readable skill file Nitrosend is built MCP-first, so an AI agent can run the whole email stack — campaigns, flows, contacts, transactional sends, deliverability — as native tools. This page is the human-readable overview. The full, machine-readable onboarding skill lives in a single file your agent can fetch and follow: nitrosend.com/SKILL.md The canonical agent onboarding skill: vocabulary, every public nitro_* tool, send modes, the response envelope, the delivery lifecycle, and the headless signup-to-send path. Point your agent here. Two ways in MCP (recommended) Connect the MCP server and the agent gets the full public tool set. Best for Claude, ChatGPT, Cursor, Codex, Gemini, and any MCP client. Headless REST For builder stacks with no MCP client (Replit, Lovable, v0, Bolt) or CI runners: sign up, mint a key, and send over plain HTTP. MCP path One command (or, in Claude Desktop and Claude.ai, the official Nitrosend connector under Settings → Connectors ) plus OAuth sign-in: Copy claude mcp add --transport http nitrosend https://api.nitrosend.com/mcp Call nitro_get_status first for account context, then read the nitro://guide resource. Full per-client setup is in the Quickstart and MCP integrations . Headless path (no dashboard, no MCP client) Request bodies on this path are top-level JSON fields; only signup nests its fields, under user . PATCH /v1/my/brands/{sid} with { "company_name": "Acme" } sets the company name, while { "brand": { "company_name": "Acme" } } returns 200 and changes nothing. 1 Create an account POST /v1/signup with { "user": { "email", "password" } } . Returns a 15-day JWT in the Authorization response header. 2 Mint an API key POST /v1/my/api_keys with that JWT. The response includes the full nskey_live_... secret once, in secret_key . Store it as a secret then. Later reads mask it; reveal it again with GET /v1/my/api_keys/{id}?reveal=true . 3 Activate the reserved sender Get the brand SID from GET /v1/my/brands , then call POST /v1/my/brands/{sid}/prepare_sending with { "subdomain": " " } (optionally "local_part" , which defaults to hello ). It reserves .nitrosend.net for the brand, permanently, and materializes its local Domain and sending-identity rows synchronously under Nitrosend's verified shared root. The brand's brand_subdomain.suggested_subdomain , when present, is a safe starting name; if the call returns subdomain_taken , choose another. Without a subdomain , the call derives the name from the company name instead and refuses with brand_identity_required until the whole Brand Kit is complete. Once a name is reserved, repeat calls return it and ignore the body. The sender can send when the response status is ready . It creates no per-brand DNS, DKIM, provider identity, tenant resource, or background job. When no sender is selected, the same transaction selects this sender; otherwise it preserves the existing selection. 4 Send once ready POST /v1/my/messages with { "channel": "email", "to", "subject", "html" } and an Idempotency-Key header. If the previous step was skipped, an eligible first request performs the company-derived materialization before admission, which needs the complete Brand Kit. If the shared root is unavailable, the API returns retryable hosted_sender_root_unavailable , stores no message, reservation, charge, or provider capability, and the same request is safe to retry. 5 Choose optional production upgrades A customer-owned sending subdomain builds portable reputation and never consumes the reserved Nitrosend brand-subdomain sender. Paid plans and checkout remain optional; $0 plans need no card. Info Node and Python fetch / requests calls work as-is. Only the bare Python-urllib user agent is blocked by the WAF. To drive the CLI from a runner with no browser, export the key as NITROSEND_API_KEY or store it with nitrosend login --api-key . Plain nitrosend login opens a browser for OAuth. For the complete reference, always defer to nitrosend.com/skill.md and the REST API docs . ### Authentication Path: `/authentication` How to authenticate with the Nitrosend API All API requests require authentication via an API key. API Keys Generate an API key from Settings > API Keys in the dashboard ( app.nitrosend.com/my/settings/api-keys ). Keys are prefixed with nskey_live_ . There is no test mode: every key sends real mail. Info All plans, including free plans, have full access to Nitrosend MCP/API/CLI. You can create API keys on the Free plan; plan limits apply to usage volume and paid add-ons. The Free plan includes 8,000 free emails up front, then 500 free emails every month. Using your key Pass your API key in the Authorization header: Copy Authorization: Bearer your-nitrosend-api-ke y > Example Copy curl https://api.nitrosend.com/v1/my/account \ -H "Authorization: Bearer nskey_live_..." Warning Keep your secret keys ( nskey_live_… ) secret. Never expose them in client-side code or commit them to version control. Public keys for website forms There is a second, restricted key type prefixed wpkey_live_… . Unlike a secret key, a public key can only create contacts and add them to your lists, so it is safe to embed directly in website source. Use it to collect signups from your site with no server and no exposed secret. See Website Signup Forms for the full setup. ### Pricing Path: `/pricing` Nitrosend plans at a glance, plus the machine-readable pricing endpoint for agents Nitrosend is pay per send, never per contact — every plan includes unlimited contacts. MCP, REST API, CLI, AI copywriting, campaigns, flows, transactional, templates, and analytics are available on every plan, including Free. The Free plan includes 8,000 free emails up front , then 500 free emails every month . No credit card required. Info Looking for full, programmatic pricing (enterprise volume tiers, overage rates, and add-ons)? Read the machine-readable pricing document at nitrosend.com/pricing.md . It is the source of truth for programmatic pricing, including details not shown on the human pricing page . Plans Plan Price Emails / month Brands Customer domains Rolling 24h recipients: Probation / Standard / Trusted Seats Free $0 8,000 starter budget, then 500/mo 3 1 100 / 500 / 5,000 1 Pro $20/mo 20,000 5 3 1,000 / 10,000 / 100,000 3 Ultra $100/mo 125,000 10 10 1,000 / 62,500 / 625,000 10 Enterprise from $300/mo 500K–4M+ (tiered) Unlimited Unlimited 1,000 for new unproven accounts / Contracted / Contracted Unlimited Every brand can prepare one reserved Nitrosend brand-subdomain sender. It does not consume the customer-domain allowance. Hosted brand subdomains follow the same plan and standing allowances; they do not have an extra 1,000/day ceiling. Daily usage is pooled across the account's brands and includes capacity held for queued or unresolved sends. Email credits are separate from daily capacity. Provider limits, sender health and pacing still govern actual dispatch. Standing combines attributable delivery history, current positive list verification, domain strength and account signals. Sufficient evidence can qualify an account on day one; payment alone does not earn trust. Trusted receives its full plan allowance without a second volume ramp. New sender contexts still require their first-send safety review, and Probation reviews each changed content identity. Existing reviewed Enterprise terms remain intact. Email validation has no included allowance on any plan. It is an explicit prepaid operation and is never started automatically during import, scheduling, or sending. Use Contacts → Verify list ( app.nitrosend.com/my/contacts ) to choose all contacts in the Brand, a contact list, a segment, or selected contacts. Review the exact audience and maximum charge before confirming. Each operation accepts up to 100,000 candidates; larger audiences must be split into bounded lists or segments. Valid results can raise standing; invalid, catch-all and unknown results do not prove list quality. Buying prepaid credits alone does not raise standing. The dashboard warns at 80% and at exhaustion. Free accounts receive the valid plan-upgrade action; accounts whose list evidence can improve receive Verify your list . API and MCP capacity responses expose the same reasons and actions. Queued campaigns wait for capacity; API callers should follow the returned retry time rather than repeatedly retrying or treating the refusal as delivered. Pro and above add BYO sending/AI keys, frontier AI models, removable branding, and priority support. Ultra and Enterprise add dedicated IPs as a paid add-on and more brands, seats, and domains. Enterprise adds SSO / SAML, a 99.9% SLA, a dedicated account manager, and tiered volume pricing for higher-volume senders. See the full feature comparison on the pricing page . For agents If you are an AI agent or automation, fetch pricing programmatically instead of scraping the marketing page: Machine-readable pricing Plans, tiers, overage rates, and add-ons in Markdown — the source of truth. Full LLM reference Complete product, pricing, and MCP-tool reference for LLMs. Plans and prepaid balance are separate purchase paths: Use a plan checkout to change the recurring subscription and entitlements. Add prepaid funds for explicitly metered services such as email validation. The nitro_manage_billing MCP tool supports both paths without paying on the agent's behalf. Billing mutations require an account-scoped API key or an authenticated owner/admin handoff. The tool returns an approval URL when required and a purchase ID for verified status polling. Follow Payments for agents for MCP, CLI, and REST examples, or read Purchase lifecycle for the state and authority model. Links Pricing page: nitrosend.com/pricing Machine-readable pricing: nitrosend.com/pricing.md Start free: app.nitrosend.com/join Contact sales: contact@nitrosend.com ### FAQ Path: `/faq` Answers to common questions about connecting agents, domains, contacts, sending, teams and pricing Frequently asked questions Quick answers to what people ask most about Nitrosend. Where an agent prompt helps, it's included so you can paste it straight into Claude, ChatGPT, Cursor or any agent connected to Nitrosend. Getting connected How do I connect Claude or another agent to Nitrosend? In Claude, open Settings, then Connectors, search for Nitrosend and choose Connect. For Claude Code, install the Nitrosend plugin from the marketplace. For ChatGPT, Cursor or a custom connection, follow the instructions in your Setup Center . Then ask your agent to check your Nitrosend status. Copy Ask your agent: "Using Nitrosend, check my account status and tell me whether setup is complete." How do I connect Nitrosend in a managed Claude workspace? Your workspace administrator controls which connectors are available. If Nitrosend needs approval or Add custom connector is greyed out, ask your administrator to approve the connection or enable custom connectors. Once access is enabled, follow the connection steps again. Do I need to understand MCP? No. MCP is just the plug standard that lets your agent talk to Nitrosend. If you can paste a URL, you're qualified. Which agents work? Claude (app and Code), ChatGPT, Codex, Cursor, and anything else that speaks MCP. There's also a plain REST API and a CLI if you'd rather script it. Do I need an agent subscription to use Nitrosend? You can manage campaigns, flows, imports and analytics in Nitrosend's dashboard without an agent. If you connect an agent, its availability, plan requirements and usage charges depend on that provider. Does it work in languages other than English? Yes. Prompt your agent in whatever language you work in and write emails in any language your audience reads. We've got customers running it in French and Spanish today. How do I make sure my agent uses Nitrosend? Start your request with "Using Nitrosend" or "In my Nitrosend account" so the intended connection is clear. If the agent still selects another tool, check that the Nitrosend connection is available. Copy Ask your agent: "Using Nitrosend, draft a campaign for [audience] about [topic] and leave it ready for review." My agent says "Tool not found: nitro_set_brand" (or another nitro_ tool it swears exists). The tool was renamed: the Brand Kit action is nitro_set_brand_kit , and the arguments are unchanged, so the identical call works under the new name. Agents (ChatGPT especially) sometimes reach for an old name from their training rather than the live tool list. Tell it the correct name, or start a fresh chat so it re-reads the tools. Nothing is disabled on your account when this happens. Copy Ask your agent: "Sync my Nitrosend brand kit using the nitro_set_brand_kit tool with my website URL." Will my agent send emails without asking me? By default, campaigns and flows are created for review, and new flows land as drafts. An agent can send without another prompt only when an account owner has granted explicit, revocable authority within the account's limits. Suppressions, unsubscribes, trust checks and delivery controls still apply to every send. Domains and deliverability What should I check if my sending domain is unverified? Enter each record's Host, type and value exactly as shown on the Domains screen. Some DNS providers append your domain automatically, so using the supplied Host value avoids duplicating it. Allow time for DNS changes to appear, then ask your agent to compare the published records with the required values. If a previously verified domain becomes unverified, check whether a record has changed or been removed. Copy Ask your agent: "Check my Nitrosend sending domain and compare each published DNS record with the required Host, type and value." What should I do if my domain is already in use? A domain can belong to one Nitrosend account, so check whether you previously registered with another email address. Sign in to that account or contact support for help resolving duplicate accounts and domain access. Invite colleagues through the Team page to share the existing account. Why can I receive a spam-complaint warning when my dashboard shows few complaints? Inbox providers report complaints differently, so the dashboard does not represent every spam report. A low displayed count alone does not establish a healthy sending audience. Focus on subscribed contacts who have engaged recently, hold back never-engaged contacts and make unsubscribing easy. Copy Ask your agent: "Show my recorded Nitrosend complaints from the last 30 days, then build a segment of subscribed contacts who opened or clicked in the last 90 days." Security and compliance Everything below is covered in depth at nitrosend.com/security , which is the page to send prospects and vendor reviews to. How does Nitrosend support GDPR requirements, and where is my data hosted? Nitrosend offers Data Processing Agreements covering GDPR and Australian Privacy Act obligations, with Standard Contractual Clauses for EU data transfers. Customer data is hosted in the United States and encrypted in transit and at rest. See the security page for data-handling information, subprocessors and vendor review details. Is Nitrosend SOC 2 certified? Our SOC 2 audit is under way with an independent third-party auditor, and we'll publish the report at nitrosend.com/security/soc-2 once it's issued. In the meantime we complete vendor security questionnaires and sign Data Processing Agreements, and the security page covers our controls, data handling and sub-processors. If your review needs more, email security@nitrosend.com . Can I send patient information through Nitrosend? Use Nitrosend only for email that does not involve protected health information (PHI). Do not import patient lists or include PHI in messages. Contact support before importing contacts or sending if you're unsure whether your healthcare use case is suitable. Is there really one-click DNS setup? If your DNS is on one of the big hosts, yes. Hit one-click DNS setup, authorise, and we write the records for you. Works with GoDaddy, Namecheap, Cloudflare, Route 53 and more. Anyone else gets the exact records to paste. Should I send from my main domain? Use a dedicated sending subdomain such as send.yourdomain.com . Set it up under Brand, then Sender. Can I email my whole list on day one? Start with your most engaged subscribers and build volume gradually over the first few weeks. New accounts have an initial sending allowance while they establish sending history. Ask your agent to plan the rollout around your current allowance. Copy Ask your agent: "Plan a gradual Nitrosend rollout for my subscribed contacts, starting with the most engaged and staying within my current sending allowance." Contacts and migration How many contacts can I store? Unlimited, on every plan. You pay for sends, not list size. How do I migrate from Mailchimp / Klaviyo / MailerLite / ActiveCampaign? Create API keys at your existing platform and in Nitrosend under Settings, then API Keys, and connect your agent to both. Ask it to migrate contacts, lists and custom fields, then use the old platform's suppression records to mark opted-out contacts unsubscribed in Nitrosend before any sending or flow activation. Have it confirm subscription status after migration. It can also recreate your templates and flows. For Mailchimp, the audience-migration connector under Integrations transfers the audience and its opt-out state. Copy Ask your agent: "Migrate my contacts, lists and custom fields to Nitrosend. Apply the old platform's opt-outs and confirm subscription status before sending anything or activating flows." How do I get a signup form on my website? Ask your agent: "create a Nitrosend signup form for my [Newsletter] list and give me the embed snippet for my site." Paste the snippet, and new subscribers flow straight into the list (and any welcome flow attached to it). What's the difference between a list and a segment? Lists are buckets you put contacts into (newsletter, customers). Segments are saved filters that update themselves (opened in the last 90 days). When sending, you choose who to send to and who to exclude. Where is my suppression list? Ask your agent or use the API to view suppressed addresses and their reasons. Suppressions prevent sends to unsubscribed addresses, hard bounces and spam complainants, even when those contacts remain in your lists. Your agent can also unsubscribe addresses in batches, including invalid addresses identified by list verification. Copy Ask your agent: "Show my Nitrosend suppression list with the reason for each address." How do I export all my contacts as a CSV? Ask your agent to create a contacts export containing names, subscription status, list membership and custom fields. Through the REST API, start it with POST /v1/my/exports and {"resource":"contacts"} , then check GET /v1/my/exports/{id} until the download is ready. Copy Ask your agent: "Create a full contacts export in Nitrosend, wait for it to finish and give me the download link." Sending Does Nitrosend do transactional email as well as marketing? Yes, both, through one place with one set of analytics. If your product already sends through Resend, Postmark or SendGrid, connect that API key under Integrations (Pro and up) and your provider becomes Nitrosend's delivery engine: point your transactional sends at Nitrosend, delivery keeps riding your own infrastructure, and everything you send shows on one timeline in Activity. You don't have to migrate everything on day one. What should I check before sending with a transactional template? Approve the template before using it for transactional sends. Check that its event-data merge tags use the documented syntax, then test the message. Copy Ask your agent: "Check that my Nitrosend transactional template is approved and its event-data merge tags use the documented syntax before I test it." How should I use idempotency keys for transactional sends? Give each logical send a stable key using the Idempotency-Key header or the idempotency_key body field on POST /v1/my/messages . Reuse that key when retrying the same send with the same payload. Reusing it with a different payload returns 409 idempotency_conflict . Copy Ask your agent: "Add one stable idempotency key per logical Nitrosend send, reuse it for retries of that send and handle 409 idempotency_conflict." How do I check my plan after upgrading? Sign out and back in to refresh your account view after upgrading. If the displayed plan still does not match your purchase, contact support with your account email. Does Nitrosend do cold outreach? Yes, through Outreach, our outbound feature, which is in private beta: join the waitlist from Outreach in the app ( how Outreach works ). Campaigns and flows are for people who have opted in to hear from you, so cold email goes through Outreach only. Every outreach send must comply with the laws that cover your recipients, including GDPR, CAN-SPAM and the Australian Spam Act: contact people only where those laws allow it, say who you are, include a working unsubscribe link and honour every opt-out. Verify your list before the first send and start small. Can I still use the visual editor? Yes, properly. Campaigns have a full WYSIWYG editor, flows have a visual builder with branching, and everything your agent creates is editable there. Plenty of customers drive with the agent and finish by hand, or skip the agent entirely for a given job. Both are first-class. How do plain-text emails work in Nitrosend? Campaigns and flows include an HTML email and a plain-text version, which you can write yourself using custom plain-text mode. For transactional messages, the API accepts a subject and body and sends your words with a minimal HTML version alongside the text version. Use campaigns or flows for marketing emails. Can I use a PO Box as my mailing address? Yes, you can use a registered PO Box instead of your home address. Add it to your Brand Kit so Nitrosend includes it in campaign and flow footers. For US CAN-SPAM requirements, use a USPS-registered PO Box or another qualifying address described in the FTC guidance . Send marketing through campaigns or flows with your sender details and unsubscribe link. Copy Ask your agent: "Set my Nitrosend brand's mailing address to [address], then check my campaign's footer and sending readiness." How do I turn off open or click tracking? Go to Brand, then Sender, then Engagement tracking, where open and click tracking have separate switches. Turning off open tracking removes the tracking pixel; turning off click tracking leaves links pointing directly to their destinations. The settings apply to emails rendered after you save. Unsubscribe links are never rewritten, and transactional messages skip click tracking. With an API key, you can also update email_track_opens and email_track_clicks through PATCH /v1/my/brands/{sid} . What should I check before a flow sends? Check that the flow is live, the contact is subscribed and the event matches its trigger. Each email step must use a From address authorised by a domain verified for that brand. Update any mismatched sender and republish the flow so the live version uses the change. Copy Ask your agent: "Check flow [name] in Nitrosend: its live status, the contact's subscription status, the matching trigger event and the From address on each email step." I imported contacts but my flow didn't start for them. Imports don't start flows: a CSV import, or an import through your agent, adds contacts and list memberships without enrolling anyone, so a big import can't fire your welcome series at your whole back catalogue. To put imported people into a flow, give it a "Contact added to list" trigger, then add them to that list after the import (select them in Contacts and use Add to list, or ask your agent); only people who weren't already on the list are added, and each of them enters the flow. If they're already on the list and the flow is still a draft, switch on "Send to contacts already in this list on first publish" in the trigger before you go live (it only applies the first time a flow goes live). Copy Ask your agent: "In Nitrosend, add the contacts I imported today to the [list name] list so they enter the [flow name] flow, and tell me how many were newly added." The brand scan grabbed the wrong logo or colours. Run the scan again (logos on some sites take a second attempt) or edit any field by hand in Brand. Your agent can also fix it: "update my brand kit, the primary colour should be #0A0A0A." How do I prepare my email for dark mode? Email apps can display colours differently in dark mode, so test your email in the apps and on the devices your audience uses. Choose a logo that remains readable on both light and dark backgrounds. You can set a logo for an individual email in its Header section. Can I use GIFs in my emails? Yes, host the GIF on your website or another image host, then open the editor's Image section, choose Use direct URL and paste the .gif link. If your agent is building the email, have it use a still image that you can replace with the GIF in the editor. Aim for about 600 pixels wide and under 1 MB. Put the key message in the first frame so it is visible in email apps that do not play the animation. Copy Ask your agent: "Build this Nitrosend email with a still image and tell me which Image section to replace with my hosted GIF in the editor." Sends that pause, hold or stall How do I check why a send is waiting? Ask your agent to check the send's current status and explain the next step. A send may be waiting for plan allowance or a review. For "Waiting for plan allowance", remaining messages continue automatically as capacity becomes available; follow the review instructions for other notices. Copy Ask your agent: "Check why Nitrosend campaign [name] is waiting. Explain the current status, any retry guidance and what action I need to take." What does "Waiting for plan allowance" mean? Your account has a monthly email allowance and a rolling 24-hour recipient allowance based on your plan and sender standing. When a campaign reaches its rolling allowance, remaining messages continue automatically as capacity becomes available. Ask your agent for your current allowance, or see the published plan limits . Paid monthly allowances reset on your billing date, while the Free monthly allowance resets at the start of each calendar month in UTC. If you are migrating an established list, ask support to review your sender standing. Copy Ask your agent: "Show my remaining Nitrosend monthly and rolling 24-hour allowances, and explain when campaign [name] can continue." What should I do when sending is paused for a high bounce rate? Nitrosend can pause sending when bounce rates are high to protect your sender reputation. Verify the list you plan to send to next and unsubscribe invalid addresses; hard-bounced addresses are already suppressed. Contacts, then Verify list, provides prepaid verification on any plan and shows the maximum charge before you confirm, or you can supply a NeverBounce or ZeroBounce report. Use mailbox verification rather than relying only on MX, role-address or webmail checks. Once the list is ready, ask support to review your account with the results. Copy Ask your agent: "Show the bounce reasons from my last Nitrosend send and quote verification for the list I plan to send to next. Wait for my approval before any paid verification." How do I check the progress of a campaign? Check the current status before starting another send. A campaign marked live may still be processing, and "Waiting for plan allowance" means remaining messages continue as capacity becomes available. Ask your agent how many messages have sent and what is holding the remainder. Copy Ask your agent: "Check Nitrosend campaign [name]: how many messages have sent, how many remain and what does the current status require?" What should I do if a campaign shows 0 sent or "Unknown" recipients? Check the send's status before retrying, because those figures alone do not confirm whether messages were sent. Keep the campaign unchanged and ask support to confirm the outcome of the previous attempt. Include the campaign ID and any message shown on screen. Copy Ask your agent: "Check Nitrosend campaign [ID] and raise a support ticket asking the team to confirm the previous send outcome before any retry." Should I resend a message marked "Delivery outcome is ambiguous after provider dispatch"? Wait until the delivery outcome has been checked before resending. Give support the message IDs so the team can confirm which messages, if any, need another send. Copy Ask your agent: "Find my Nitrosend messages with this delivery status and raise a support ticket with their IDs. Ask the team to confirm their outcomes before any resend." What should I do if I see "delivery_evidence_pending"? A delivery check is still pending. Ask your agent to check the current delivery status and any retry guidance before trying again. If the status persists, contact support with the affected campaign or message ID. Copy Ask your agent: "Check the Nitrosend delivery status for [campaign or message ID] and explain the next step. If the same status persists, raise a support ticket with the details." What should I do if approval returns "content_vetting_degraded"? This status means the content review has not produced a decision; it does not establish that the content was rejected. Ask support to check the flow or campaign before making further approval attempts, including its ID, revision and the exact message. Copy Ask your agent: "Raise a Nitrosend support ticket for content_vetting_degraded on [flow or campaign ID]. Include the revision and exact message, and do not keep retrying approval." What should I check before moving from a test email to a campaign or flow? Confirm that your sending domain is verified and that the campaign or flow uses a matching From address. Test emails help check the setup, while live sends also need to meet the account's sending requirements. If a test or live send cannot proceed, include the exact message in a support request. Copy Ask your agent: "Check the From address and verified domain for Nitrosend [campaign or flow], then explain any remaining sending requirements." What should I do when Nitrosend says sending is paused for an account review? Ask support to review the account and include what you send, who receives it and whether the messages are transactional or marketing. An account-review pause needs the team's review, so repeating domain verification will not resolve it. If the notice identifies bounces or complaints, include your list-cleaning or verification results. Copy Ask your agent: "Raise a Nitrosend support ticket about my sending_paused notice. Include the exact notice, my intended audience and whether the messages are transactional or marketing." How can I finish editing an email? Open the existing draft in the app's editor to make your changes. For a long email, you can also ask your agent to rebuild the affected section or recreate the email. If you need help, include the draft details and any exact error message in a support request. How do I remove the "sent with Nitrosend" badge from my emails? Emails sent on the Free plan include the Nitrosend badge. On Pro and above, turn it off under Brand, then Sender. Teams and agencies Can my team share an account? Yes, add team members in Settings, Team. Multiple seats are part of the paid plan. How can an agency set up and hand over a client account? Use the Clients page to create the client's account, and ask support if you need access to that page. Build their brand kit, domain and flows before choosing Invite owner to invite them. The client owns and pays for the account and can remove your access at any time. Choose Release when your work ends. Give clients who need their own login a separate account, because team members invited to your agency account can see all its brands. How do I add another brand? Open the dashboard's brand switcher and choose Add brand, then enter the website to build its Brand Kit. Once created, your agent can select the brand and configure its domain and sender. With an API key, the REST API supports creation through POST /v1/my/brands , renaming through PATCH /v1/my/brands/{sid} and archiving through DELETE /v1/my/brands/{sid} . To get a key for the new brand, select it in the dashboard and open Settings, then API Keys. See pricing for your plan's brand allowance. Do I need a separate connection per client? One MCP connection can access the brands available to your login. Work on one client at a time across chats and scheduled agent tasks using the same login in the same agent app, and select and confirm the intended brand immediately before each task. A campaign already scheduled retains its own brand when you switch to another client. Copy Ask your agent: "Select Nitrosend brand [name], confirm its name and ID, then prepare [task] for review." Pricing What do I actually get free? The Free plan includes 8,000 free emails up front, then 500 free emails every month. You also get unlimited contacts, 3 brands and full MCP, API and CLI access. No credit card is required. See pricing for plan details, or the pricing reference for agents . How do I get an invoice or update my billing details? Ask support for your hosted invoice link, which includes a Download PDF option. Include your company name, address and VAT or tax number if you want them added to future invoices. Billing-detail updates apply from your next payment; details on invoices already issued remain unchanged. Copy Ask your agent: "Raise a Nitrosend support ticket requesting my latest invoice link and these billing details for future invoices: [company name, address, VAT or tax number]." Help Where do I get help? Three places, fastest first: ask NitroLLM (the chat in the app's right rail), ask your own agent (it can search our docs and raise a support ticket for you), or email us. And join the Slack, the team and other customers are in there daily. Copy Ask your agent: "Search the Nitrosend docs for [topic] and if you can't solve it, raise a support ticket describing what we tried." How do I delete my account? Ask your agent or the in-app chat to raise an account-deletion request. The team handles the request, cancels any paid plan and confirms the outcome in writing. If you are removing a duplicate account, specify which login you want to keep. Copy Ask your agent: "Raise a Nitrosend account-deletion request for [account email] and request written confirmation, including cancellation of any paid plan." ### Connect Your AI Tool Path: `/integrations/overview` Connect Nitrosend to Claude, GPT, Gemini, Codex, or any AI assistant via MCP or REST API Nitrosend is designed to be controlled by AI. Instead of clicking through dashboards, you connect your AI tool and manage everything — campaigns, flows, contacts, templates, analytics — through natural language. Info All plans, including free plans, have full access to Nitrosend MCP/API/CLI. Plan limits still apply to send volume, seats, AI action allowances, and paid add-ons. MCP: The Fastest Way to Connect MCP (Model Context Protocol) is an open standard that lets AI assistants connect directly to external tools. Nitrosend has a native MCP server, which means Claude, ChatGPT, Cursor, Codex, Gemini CLI, VS Code, Windsurf, Zed, and other MCP-compatible clients can access your full email marketing platform with zero middleware. What MCP gives your AI When you connect via MCP, your AI assistant gets: Purpose-built tools — compose emails, build flows, manage contacts, switch brand context, check analytics, control delivery, and more Live resources — your Brand Kit, account state, platform guide, and email design schema Full context — your AI knows your brand colors, tone of voice, and sending history before it writes a single email Two-way control — create, preview, test, approve, send, pause, and analyze, all from conversation MCP vs REST API MCP REST API Best for Claude, ChatGPT, Cursor, Codex, Gemini CLI, VS Code, Windsurf, Zed Custom apps, Zapier, n8n, Make, server-side integrations Setup 1 click, 1 command, or a short JSON config + OAuth sign-in API key + define function calls or curl AI context Full — Brand Kit, schema, guide, account state You provide context manually Tools Purpose-built tools with validation Raw HTTP endpoints Coding required No Minimal to moderate Tip Use MCP whenever possible. It gives your AI far more context about your account, Brand Kit, and email best practices than the REST API alone. The result is better emails with less prompting. Brands MCP always operates on one current brand. nitro_get_status shows current_brand and available_brands ; OAuth users can switch with nitro_select_brand , and that selection persists per user, OAuth application, and account. API key connections are pinned to the API key's brand. nitro_set_brand_kit is different: it edits the selected brand's Brand Kit, such as colors, logo, company details, and voice. Choose your platform MCP connections (recommended) Use the per-client MCP path below for chat and editor clients. Do not install the Nitrosend CLI as a shortcut for Claude Desktop, Claude.ai, Cowork, ChatGPT, Cursor, or similar clients; the CLI is a separate terminal/CI surface. Claude Desktop Enable the official Nitrosend connector under Settings → Connectors. Claude Cowork / Chat Connect Nitrosend to Claude for collaborative email operations. Claude Code Add Nitrosend in one command for terminal-based email work. Cursor Install the official Nitrosend plugin from the Cursor Marketplace. ChatGPT Connect the official Nitrosend app from the ChatGPT app marketplace. OpenAI Codex CLI Connect Codex CLI with codex mcp add and OAuth login. Gemini CLI Add Nitrosend to Gemini CLI with a short settings.json entry. VS Code Use Nitrosend from GitHub Copilot via VS Code's MCP support. Windsurf Connect Cascade directly to Nitrosend over remote HTTP MCP. Zed Add Nitrosend as a remote custom server in Zed with OAuth. REST API connections REST API (Any Platform) Use from any language, tool, or automation platform — Zapier, n8n, Make, custom code. Audience imports & commerce Mailchimp Connect Mailchimp to import audiences, members, merge fields, and list memberships while preserving consent. Attio Sync Attio people and list membership into Nitrosend. HubSpot Sync HubSpot CRM contacts and lists into Nitrosend. Shopify Import Shopify customers and receive store commerce events for segments and flows. Bring your own email provider Email providers (BYO) Send through your own SendGrid, Postmark, Resend, Mailgun, or Amazon SES account while Nitrosend handles everything else. Supercharge your AI with Brand Kit & Memory Once connected, set up Brand Kit and AI Memory to give your AI deep context about your business. This means every email it creates automatically matches your brand voice, follows your strategy, and improves over time. Brand Kit & AI Memory Teach your AI your brand voice, colors, goals, and strategy so every email it creates is on-brand and on-strategy. ### Claude Desktop Path: `/integrations/claude-desktop` Enable the official Nitrosend connector in Claude Desktop and run your email marketing through conversation Claude Desktop + Nitrosend MCP Nitrosend is an official connector in the Claude directory. Enable it once under Settings → Connectors , sign in, and Claude can run your email marketing through natural conversation. No coding, no config files, no URL to paste. Info Claude Desktop, Claude.ai and Claude Cowork users should enable the Nitrosend connector. Do not install @nitrosend/cli or run nitrosend login for the standard Claude setup; the CLI is for terminal and CI workflows, not connector onboarding. What is MCP? MCP (Model Context Protocol) is an open standard that lets Claude connect directly to external services. When Nitrosend is connected as an MCP server, Claude gets native access to your email marketing — campaigns, flows, contacts, templates, analytics — all through conversation. Setup: Enable the Nitrosend connector 1 Open Connectors In the Claude Desktop app, go to Settings → Connectors . 2 Find Nitrosend and connect Search for Nitrosend and click Connect . The same listing is at claude.ai/directory/nitrosend : open that link in Claude.ai (web) to add the connector there, and it is available in Claude Desktop and Cowork as well. 3 Sign in Claude will open a Nitrosend sign-in window. Log in or create a free account — no API key needed. 4 Start using Nitrosend Open a new conversation and ask Claude anything about your email marketing: Copy What's my Nitrosend account status? Claude will use the Nitrosend MCP tools to check your account and respond with your status, recommendations, and next steps. Tip No API key and no server URL required. The connector handles authentication through a secure sign-in flow: log in when prompted and you're connected. What Claude can do with Nitrosend connected Once connected via MCP, Claude has access to a full suite of specialized tools and live resources about your account. Here's what that means in practice: Draft and test campaigns You : Draft a welcome campaign for my Newsletter list Claude : I'll compose a welcome campaign using your brand theme. Here's a preview — want me to send a test before approval? Build automation flows You : Create a 3-email welcome series that triggers when someone joins my newsletter list Claude : Done. I've created a flow with: (1) Welcome email sent immediately, (2) Product highlights after 3 days, (3) Special offer after 7 days. Want me to activate it? Check analytics You : How did my last campaign perform? Claude : Your "Spring Reading List" campaign had a 42% open rate and 8.3% click rate — both above industry benchmarks. The top-clicked link was... Manage contacts You : Import these contacts to my VIP list: jane@example.com , bob@example.com Claude : I've added both contacts to your VIP list with email subscription enabled. MCP resources Claude can access Beyond tools, Claude can read live resources for deeper context: Resource What it provides nitro://guide Platform vocabulary, workflows, and tool usage guidance nitro://schema Email component types, flow steps, and filter definitions nitro://brand-kit Your Brand Kit identity — colors, fonts, tone, logo nitro://account Account status, tier, and sending limits nitro://config Sender defaults and verified domains Tips for best results Set up your Brand Kit first. Go to Brand Kit ( app.nitrosend.com/my/brand ) and configure your brand colors, fonts, and logo. Claude uses this automatically when composing emails. Use AI Memory. Go to AI Memory ( app.nitrosend.com/my/settings/memory ) to tell Nitrosend about your business goals, target audience, and email strategy. Claude reads this to make smarter decisions. Be specific about your audience. Say "send to the newsletter list" rather than just "send an email." Ask for previews. Say "send me a test first" before going live with any campaign. Brand Kit & AI Memory Learn how to configure Brand Kit and AI Memory to make every email smarter. Fallback: custom connector If your Claude organisation restricts directory connectors, ask an organisation owner to enable Nitrosend, or to add it as a custom connector: Settings → Connectors → Add custom connector , name Nitrosend , remote MCP URL https://api.nitrosend.com/mcp . Same tools, same sign-in flow. Legacy fallback: Manual MCP configuration If neither the directory listing nor custom connectors are available in your version of Claude Desktop, update Claude Desktop first. Only use this local stdio fallback when you cannot use connectors at all and you understand that it stores an API key in your local Claude config: Manual config file setup Open your config file: macOS : ~/Library/Application Support/Claude/claude_desktop_config.json Windows : %APPDATA%\Claude\claude_desktop_config.json Add Nitrosend to the mcpServers section: Copy { "mcpServers" : { "nitro" : { "command" : "npx" , "args" : [ "-y" , "@nitrosend/mcp" ], "env" : { "NITROSEND_API_KEY" : "nskey_live_your_key_here" } } } } Restart Claude Desktop. You should see a hammer icon indicating MCP tools are available. Info Requires Node.js 18 or later for the manual config method. The directory connector has no local dependencies and remains the recommended path. ### Claude Cowork Path: `/integrations/claude-cowork` Connect Nitrosend to Claude Cowork for team-based AI email marketing Claude Cowork + Nitrosend MCP Claude Cowork is Anthropic's collaborative AI workspace. Connect Nitrosend via MCP and your team can manage email marketing through conversation — create campaigns, build flows, check analytics, and more, all within Cowork. What is MCP? MCP (Model Context Protocol) is an open standard that lets Claude connect directly to external services. When Nitrosend is connected, Claude gets native access to the full public set of email marketing tools plus live resources about your brand, account, and email best practices. Setup: Enable the Nitrosend connector Cowork runs inside the Claude Desktop app and shares its connectors, so enabling Nitrosend once covers both. 1 Open Connectors In the Claude Desktop app, go to Settings → Connectors . 2 Find Nitrosend and connect Search for Nitrosend and click Connect . The same listing is at claude.ai/directory/nitrosend ; connecting there makes Nitrosend available in Cowork too. 3 Sign in Claude will open a Nitrosend sign-in window. Log in or create a free account — no API key needed. 4 Start using Nitrosend Nitrosend is now available as a connector in your Cowork workspace. Open a new conversation and start prompting: Copy What's my Nitrosend account status? Why Cowork for email marketing? Cowork's collaborative workspace is particularly powerful for email marketing because: Team context — share conversations about campaigns, flows, and strategy with your team Persistent projects — keep email marketing as an ongoing project where Claude remembers your goals, brand, and past campaigns Multi-step workflows — complex tasks like "plan our Q2 email strategy, then build the flows" work naturally in Cowork's project format Example workflows in Cowork Plan and execute a campaign Copy Let's plan our March newsletter. Check my recent campaign performance, then create a new campaign targeting subscribers who opened the last email. Include a section about our new features. Audit your email setup Copy Review my Nitrosend account setup. Check my Brand Kit, sending domain health, and active flows. Are there any improvements you'd recommend? Build a complete flow Copy I need a complete abandoned cart recovery flow. Build a 3-email sequence: first email 1 hour after cart abandonment, second after 24 hours, third after 3 days with a discount offer. Supercharge with Brand Kit & Memory For the best results in Cowork, make sure you've configured: Brand Kit ( app.nitrosend.com/my/brand ) — your brand colors, fonts, logo, and company description AI Memory ( app.nitrosend.com/my/settings/memory ) — your marketing goals, target audience, and email strategy Claude reads these automatically through MCP, so every email it creates matches your brand and follows your strategy. Brand Kit & AI Memory Learn how to configure Brand Kit and AI Memory for smarter AI-generated emails. All MCP tools available in Cowork The full public set of Nitrosend MCP tools is available in Cowork — the same tools available in Claude Desktop and Claude Code. See the Claude Code page for the complete tool reference. Info The connector requires no local software installation. Everything runs server-side through Nitrosend's MCP endpoint at api.nitrosend.com/mcp . If your Claude organisation restricts directory connectors, an organisation owner can enable Nitrosend or add it as a custom connector with that URL; see the Claude Desktop guide . ### Claude Code Path: `/integrations/claude-code` Connect Nitrosend to Claude Code via MCP for terminal-based AI email marketing Claude Code + Nitrosend MCP Claude Code is Anthropic's CLI tool for working with Claude in your terminal. Connect Nitrosend via MCP and Claude Code becomes a full email marketing command center. What is MCP? MCP (Model Context Protocol) is an open standard that lets AI tools connect to external services. When you add Nitrosend as an MCP server to Claude Code, Claude gets native access to the full public set of email marketing tools — no API wrappers, no middleware, no boilerplate. Setup Step 1: Add the MCP server Run this single command in your terminal: Copy claude mcp add --transport http nitrosend https://api.nitrosend.com/mcp That's it — no API key needed. Or point Claude Code at the install skill, nitrosend.com/SKILL.md , and it runs the setup itself. Step 2: Sign in Start a Claude Code session and either ask Claude to use Nitrosend or run /mcp . Claude Code will open a Nitrosend sign-in flow or show you the login URL. Log in or create a free account. Step 3: Verify the connection Ask Claude: Copy What's my Nitrosend account status? Claude will call the nitro_get_status tool and show your current account, sender identity, dashboard links, readiness, onboarding progress, blockers, and recommendations. How MCP works Claude Code connects directly to the Nitrosend MCP endpoint over HTTP: Claude Code sends tool calls via MCP protocol to api.nitrosend.com/mcp Authentication is handled via OAuth — you sign in once and Claude Code stores the token for you Structured results come back to Claude with full context No data is stored locally. All state lives in your Nitrosend account. MCP tools available Once connected, Claude Code has access to these tools: Tool What it does nitro_get_status Account context, sender identity, readiness, and recommendations nitro_search_docs Search the Nitrosend product documentation nitro_select_account Switch the current account for OAuth MCP sessions nitro_select_brand Switch the current brand for OAuth MCP sessions nitro_ingest Ingest assets. V1 supports image via small inline data, public URL, upload reservation, or signed_id nitro_manage_template Create, update, or clone reusable email templates nitro_compose_campaign Create email or SMS campaigns with audience targeting nitro_compose_flow Build automation flows with triggers, steps, and branches nitro_manage_audience Create contacts, manage lists, record events, tag contacts nitro_import_contacts Bulk import contacts (up to 100 inline) nitro_search_contacts Search contacts by email, name, or phone nitro_query Query campaigns, flows, templates, segments, messages, suppressions, and history nitro_define_segment Create dynamic segments with filters nitro_set_brand_kit Set up Brand Kit identity — colors, fonts, logo, voice nitro_set_memory Read and update your AI memory document nitro_manage_domains Add, verify, and manage sending domains nitro_control_delivery Approve, send, pause, or schedule campaigns and flows nitro_review_delivery Review email/SMS content and delivery readiness nitro_send_test_message Send test email or SMS messages nitro_send_message Send immediate single-recipient transactional email or SMS nitro_configure_account Sender defaults and test email recipients nitro_get_insights Email analytics with trends and benchmarks nitro_manage_billing Plan and prepaid status; purchases use an admin or API-key handoff nitro_configure_providers Configure a BYO email provider or check provider status nitro_request_support Submit a support request to the Nitrosend team For accounts with multiple brands, ask Claude to check nitro_get_status first. OAuth sessions can use nitro_select_brand to persist the selected brand for this user, OAuth application, and account. API keys are pinned to their key's brand, and nitro_set_brand_kit edits Brand Kit identity rather than switching context. MCP resources Claude can also read live resources for deeper context: Resource What it provides nitro://guide Platform vocabulary, workflows, and tool descriptions nitro://schema Email component types, flow step types, filter definitions nitro://brand-kit Your Brand Kit identity — colors, fonts, tone, logo nitro://account Current account context, sender identity, dashboard links, tier, and limits nitro://config Sender defaults and verified domains Example workflows Create a welcome flow Copy Create a welcome email flow. When a new contact is added, send a welcome email immediately, wait 3 days, then send a follow-up with our top recommendations. Send a campaign Copy Create a campaign called "March Newsletter" and send it to my subscriber list. Use my brand colors and include a section about our new features. Check performance Copy Show me insights for my last campaign. How did it compare to benchmarks? Set up Brand Kit and memory Copy Set up my Brand Kit from https://mysite.com and then update my AI memory with our Q1 marketing goals. Scope-based configuration Add the MCP server at different scopes depending on your needs: Copy # User scope — available in all Claude Code sessions (recommended) claude mcp add --transport http -s user nitrosend https://api.nitrosend.com/mcp # Local scope — private to your current project on this machine claude mcp add --transport http -s local nitrosend https://api.nitrosend.com/mcp # Project scope — shared with the project via .mcp.json claude mcp add --transport http -s project nitrosend https://api.nitrosend.com/mcp Troubleshooting Connection failed Make sure api.nitrosend.com is reachable from your network. Inspect protected-resource metadata with curl -i https://api.nitrosend.com/.well-known/oauth-protected-resource/mcp ; it should return 200 OK and name https://api.nitrosend.com/mcp as the protected resource. Separately, unauthenticated GET /mcp should return 401 Unauthorized with WWW-Authenticate: Bearer resource_metadata="https://api.nitrosend.com/.well-known/oauth-protected-resource/mcp" . Authenticated MCP application traffic uses POST /mcp ; because Nitrosend does not offer a GET/SSE listener, authenticated GET /mcp returns 405 Method Not Allowed . Authentication failed Run /mcp inside Claude Code to trigger the OAuth flow explicitly. If that still fails, remove and re-add the server: claude mcp remove nitrosend then re-run the add command. Tool call timed out If calls consistently time out, check your network connection. You can also try removing and re-adding the MCP server to refresh the session. Info No API key or local dependencies required. Authentication is handled via OAuth sign-in when you first connect. ### Cursor Path: `/integrations/cursor` Install the official Nitrosend plugin from the Cursor Marketplace and run your email marketing from Cursor's agent Cursor + Nitrosend Nitrosend is an official plugin on the Cursor Marketplace . Install it once and Cursor's agent gets the full Nitrosend tool set: compose emails, build automation flows, send campaigns, manage contacts and check analytics, all through conversation. Setup Option 1: Install the official plugin (recommended) In Cursor's Agent chat, run: Copy /add-plugin nitrosend Or install from the listing: Add Nitrosend to Cursor Click Add to Cursor on the listing (Cursor asks you to sign in to your Cursor account first if you are not already). You can also open Customize in the Cursor sidebar, find Nitrosend and choose Install , then pick project or user scope. The plugin bundles one remote MCP server, nitrosend , pointed at https://api.nitrosend.com/mcp . Nothing runs on your machine, so it works in the Cursor desktop app and Cursor on the web. Cloud agents keep their own MCP list: add the same URL as an HTTP server from the MCP dropdown at cursor.com/agents , or have a team admin share it under Dashboard, Plugins & MCPs, then sign in once (OAuth is per user). Option 2: Add the MCP server manually Prefer to manage MCP servers yourself? Add the same server to ~/.cursor/mcp.json for all projects, or .cursor/mcp.json inside the current repo for a project-local setup: Copy { "mcpServers" : { "nitrosend" : { "url" : "https://api.nitrosend.com/mcp" } } } Restart Cursor if the server does not appear straight away. Tip Pick one path. The plugin and the manual entry add the same server, so keep one or the other and Cursor lists a single nitrosend server. Do not use mcp-remote or the local @nitrosend/mcp package for the standard Cursor setup. Older Cursor builds without remote MCP support If your Cursor build cannot connect to a remote MCP server directly, route through the stdio bridge instead. It needs Node on the machine: Copy { "mcpServers" : { "nitrosend" : { "command" : "npx" , "args" : [ "-y" , "mcp-remote" , "https://api.nitrosend.com/mcp" ] } } } Sign in The first Nitrosend tool call opens a Nitrosend sign-in window. Log in or create a free account there. OAuth handles the rest: no API key, nothing to paste. Verify the connection Open Cursor's Agent chat and ask: Copy What's my Nitrosend account status? Cursor calls nitro_get_status and shows your account, sender identity, readiness, blockers and recommendations. If the sign-in prompt does not appear after the first tool call, open Customize in the sidebar, confirm the nitrosend MCP server is enabled, and re-run the prompt. Example workflows Compose a campaign Copy Create a campaign called "April Newsletter" using my brand theme. Target the active subscribers segment and schedule it for Tuesday at 10am. Build an automation flow Copy Build a cart abandonment flow: email at 1 hour, 24 hours, then 72 hours with a 10% discount code. Check performance Copy Show me insights for my last 3 campaigns. How are they tracking against benchmarks? Send a test email Copy Send a test of my latest campaign to user@example.com MCP tools available Tool Description nitro_get_status Account context, sender identity, readiness, and recommendations nitro_search_docs Search the Nitrosend product documentation nitro_select_account Switch the current account for OAuth MCP sessions nitro_select_brand Switch the current brand for OAuth MCP sessions nitro_set_brand_kit Brand Kit identity from URL or manual fields nitro_manage_domains Add and verify sending domains nitro_configure_account Sender defaults and test recipients nitro_ingest Ingest assets. V1 supports image via small inline data, public URL, upload reservation, or signed_id nitro_manage_template Create, update, or clone reusable email templates nitro_compose_campaign Create email or SMS campaigns nitro_compose_flow Build automation flows nitro_control_delivery Approve, schedule, pause, or cancel delivery nitro_review_delivery Review email/SMS content and delivery readiness nitro_send_test_message Send test email or SMS messages nitro_send_message Send immediate single-recipient transactional email or SMS nitro_manage_audience Create contacts, manage lists, tags, events nitro_import_contacts Bulk import contact records nitro_define_segment Build segments with filters and preview nitro_search_contacts Find contacts by email, name, or phone nitro_query Query campaigns, flows, templates, segments, messages, suppressions, and history nitro_get_insights Analytics with trends and benchmarks nitro_manage_billing Plan and prepaid status; purchases use an admin or API-key handoff nitro_configure_providers Configure a BYO email provider or check provider status nitro_set_memory Persistent AI memory across sessions nitro_request_support Submit a support request Troubleshooting Sign-in window didn't open Open Customize in the Cursor sidebar and confirm the nitrosend MCP server is enabled. Trigger a Nitrosend tool call again. If Cursor still does not prompt for OAuth, remove and re-add the plugin (or the nitrosend entry in mcp.json ) and restart Cursor once. Tools not appearing in Cursor Chat If you installed the plugin, check it is enabled for the scope you are working in (project or user) under Customize . If you edited mcp.json manually, make sure Cursor picked up the new config; a restart is sometimes required after adding a new server. Inspect protected-resource metadata with curl -i https://api.nitrosend.com/.well-known/oauth-protected-resource/mcp ; it should return 200 OK and name https://api.nitrosend.com/mcp as the protected resource. Separately, unauthenticated GET /mcp should return 401 Unauthorized with WWW-Authenticate: Bearer resource_metadata="https://api.nitrosend.com/.well-known/oauth-protected-resource/mcp" . Authenticated MCP application traffic uses POST /mcp ; because Nitrosend does not offer a GET/SSE listener, authenticated GET /mcp returns 405 Method Not Allowed . Two nitrosend servers listed You have both the plugin and a manual mcp.json entry. Remove one. Either path works; the plugin is the one Cursor keeps up to date for you. Info No API key or local dependencies required. Authentication is handled via OAuth sign-in when you first connect. The plugin is free and MIT licensed; source at github.com/nitrosend/cursor . ### ChatGPT / GPT Path: `/integrations/chatgpt` Connect Nitrosend to ChatGPT through the ChatGPT app marketplace Connect Nitrosend to ChatGPT through the ChatGPT app marketplace. ChatGPT handles the MCP connection and Nitrosend authenticates your account — no API key or local setup required. Option 1: Install the official app (Recommended) The official Nitrosend app is in the ChatGPT app directory. One click, no config files, no API key. 1. Install the official ChatGPT app Install the Nitrosend app in ChatGPT Clicking opens the Nitrosend app inside ChatGPT. Hit Connect , then sign in or create a free account — no API key needed. 2. Can't install directly? Search for it Some ChatGPT plans or regions don't surface the direct link yet. If the button above doesn't open the app, add it from the marketplace instead: In ChatGPT, click your name, open Settings → Apps , then open the app marketplace. In the Search apps field, enter Nitrosend . Click Nitrosend - AI Native Email , then click Connect . ChatGPT opens the Nitrosend sign-in flow — log in or create a free account. No API key needed. 3. Use Nitrosend in a conversation Ask ChatGPT anything about your email marketing: Copy What's my Nitrosend account status? Copy Create a welcome email flow for new subscribers Info No API key or local dependencies required. Authentication is handled through your Nitrosend account when you connect Nitrosend — whether you install via the button or the marketplace search. Option 2: Custom GPT with Actions Create a custom GPT in ChatGPT that can manage your email marketing. 1. Get your API key Go to Settings > API Keys in the Nitrosend dashboard ( app.nitrosend.com/my/settings/api-keys ) and copy your key. 2. Create a custom GPT Go to ChatGPT → Explore GPTs → Create a GPT . 3. Add the Nitrosend action In the GPT editor, go to Configure → Actions → Create new action . Set the Authentication to: Type: API Key Auth Type: Bearer Key: your nskey_live_... key Import the OpenAPI schema from: Copy https://api.nitrosend.com/openapi.yaml This automatically imports all available Nitrosend endpoints as GPT actions. 4. Add instructions In the GPT's Instructions field, add context about your use case: Copy You are an email marketing assistant connected to Nitrosend. You can create campaigns, manage contacts, build automation flows, and check analytics. Always confirm before sending campaigns. Ask for a preview or test send before going live. 5. Save and use Save your GPT. You can now ask it things like: "Create an email campaign about our summer sale" "How many subscribers do I have?" "Show me the performance of my last campaign" Option 3: OpenAI API with tool calling For programmatic integration, call the Nitrosend REST API from your own OpenAI-powered app. Define your tools Copy from openai import OpenAI client = OpenAI() tools = [ { "type" : "function" , "function" : { "name" : "send_email" , "description" : "Send a transactional email to a single recipient" , "parameters" : { "type" : "object" , "properties" : { "to" : { "type" : "string" , "description" : "Recipient email address" }, "subject" : { "type" : "string" , "description" : "Email subject line" }, "body" : { "type" : "string" , "description" : "Email body (HTML or plain text)" } }, "required" : [ "to" , "subject" , "body" ] } } }, { "type" : "function" , "function" : { "name" : "list_campaigns" , "description" : "List all email campaigns" , "parameters" : { "type" : "object" , "properties" : { "status" : { "type" : "string" , "enum" : [ "draft" , "active" , "paused" , "completed" ], "description" : "Filter by campaign status" } } } } } ] response = client.responses.create( model = "gpt-5" , input = "Send a transactional email to jane@example.com welcoming her to the newsletter." , tools = tools ) Handle function calls When your app receives a function call, execute the corresponding Nitrosend API request: Copy import requests NITROSEND_API_KEY = "nskey_live_your_key_here" BASE_URL = "https://api.nitrosend.com/v1/my" HEADERS = { "Authorization" : f "Bearer { NITROSEND_API_KEY } " , "Content-Type" : "application/json" } def handle_function_call ( name , arguments ): if name == "send_email" : response = requests.post( f " { BASE_URL } /messages" , headers = HEADERS , json = { "channel" : "email" , "to" : arguments[ "to" ], "subject" : arguments[ "subject" ], "body" : arguments[ "body" ] } ) return response.json() elif name == "list_campaigns" : params = {} if "status" in arguments: params[ "status" ] = arguments[ "status" ] response = requests.get( f " { BASE_URL } /campaigns" , headers = HEADERS , params = params ) return response.json() Key API endpoints These are the most useful endpoints for GPT integrations: Action Method Endpoint Send a message POST /v1/my/messages List campaigns GET /v1/my/campaigns Create a campaign POST /v1/my/campaigns List contacts GET /v1/my/contacts Import contacts POST /v1/my/imports Poll import status GET /v1/my/imports/{id} List templates GET /v1/my/templates Get account info GET /v1/my/account See the full API Reference for all available endpoints. Info The OpenAPI spec at https://api.nitrosend.com/openapi.yaml is always up to date and can be imported directly into any tool that supports OpenAPI 3.1. ### OpenAI Codex CLI Path: `/integrations/codex` Give Codex CLI access to Nitrosend via MCP for AI-powered email automation from your terminal Codex CLI is OpenAI's terminal-based coding agent. Connect it to Nitrosend via remote MCP for access to every public Nitrosend tool, or use the REST API with a helper script for non-OAuth automation. Info This guide is for Codex CLI. ChatGPT has a separate official Nitrosend app; see the ChatGPT guide . Option 1: Codex CLI (MCP server, recommended) Codex CLI supports Nitrosend as a remote HTTP MCP server. 1. Add the MCP server Copy codex mcp add nitrosend --url https://api.nitrosend.com/mcp 2. Sign in with OAuth Copy codex mcp login nitrosend Codex opens the Nitrosend sign-in flow in your browser. Log in or create a free account. 3. Use Nitrosend from Codex Copy codex "Check my Nitrosend account status" codex "Create a welcome email flow for new subscribers" codex "Send our newsletter to the active subscribers list" Info No API key is required for the standard Codex MCP setup. If you prefer a non-OAuth flow for automation, Codex also supports --bearer-token-env-var with a Nitrosend API key. Option 2: REST API with helper script How it works Codex CLI executes shell commands and reads files. By giving it a helper script and API documentation, Codex can call the Nitrosend REST API using curl to manage campaigns, contacts, templates, and more. Setup 1. Get your API key Go to Settings > API Keys in the Nitrosend dashboard ( app.nitrosend.com/my/settings/api-keys ) and copy your key. 2. Set the environment variable Add your API key to your shell profile ( ~/.zshrc , ~/.bashrc , etc.): Copy export NITROSEND_API_KEY = "nskey_live_your_key_here" Reload your shell: Copy source ~/.zshrc 3. Create a helper script Save this as nitrosend.sh in your project or home directory: Copy #!/bin/bash # Nitrosend API helper for Codex CLI # Usage: ./nitrosend.sh [json_body] METHOD = " ${1 :- GET } " ENDPOINT = " $2 " BODY = " $3 " BASE_URL = "https://api.nitrosend.com/v1/my" if [ -z " $NITROSEND_API_KEY " ]; then echo "Error: NITROSEND_API_KEY not set" exit 1 fi CURL_ARGS = ( -s -X " $METHOD " -H "Authorization: Bearer $NITROSEND_API_KEY " -H "Content-Type: application/json" ) if [ -n " $BODY " ]; then CURL_ARGS += ( -d " $BODY " ) fi curl "${ CURL_ARGS [ @ ]}" "${ BASE_URL }${ ENDPOINT }" | python3 -m json.tool Make it executable: Copy chmod +x nitrosend.sh 4. Add a Codex instructions file Create a codex.md (or add to your existing one) so Codex knows how to use Nitrosend: Copy ## Nitrosend API Access You can manage email marketing via the Nitrosend REST API. Helper script: `./nitrosend.sh [json_body]` Common operations: - Check account: `./nitrosend.sh GET /account` - List campaigns: `./nitrosend.sh GET /campaigns` - List contacts: `./nitrosend.sh GET /contacts` - Send email: `./nitrosend.sh POST /messages '{"channel":"email","to":"user@example.com","subject":"Hello","body":"Hi there"}'` - Create campaign: `./nitrosend.sh POST /campaigns '{"name":"My Campaign","channel_type":"email"}'` - List templates: `./nitrosend.sh GET /templates` API base: https://api.nitrosend.com/v1/my Auth: Bearer token via $NITROSEND_API_KEY environment variable 5. Use with Codex Start Codex CLI and ask it to manage your email marketing: Copy codex "Check my Nitrosend account status and list my recent campaigns" Copy codex "Send a test email to george@nitrosend.com with the subject 'Hello from Codex'" Copy codex "Import these contacts: jane@example.com (Jane Doe), bob@example.com (Bob Smith)" Direct curl usage You can also use the API directly without the helper script: Copy # Check account status curl -s https://api.nitrosend.com/v1/my/account \ -H "Authorization: Bearer $NITROSEND_API_KEY " | python3 -m json.tool # Send a transactional email curl -s -X POST https://api.nitrosend.com/v1/my/messages \ -H "Authorization: Bearer $NITROSEND_API_KEY " \ -H "Content-Type: application/json" \ -d '{ "channel": "email", "to": "user@example.com", "subject": "Hello", "body": " Welcome! " }' | python3 -m json.tool API reference Action Command Account status ./nitrosend.sh GET /account List campaigns ./nitrosend.sh GET /campaigns Create campaign ./nitrosend.sh POST /campaigns '{"name":"...","channel_type":"email"}' List contacts ./nitrosend.sh GET /contacts Send email ./nitrosend.sh POST /messages '{"channel":"email","to":"...","subject":"...","body":"..."}' List templates ./nitrosend.sh GET /templates List segments ./nitrosend.sh GET /segments See the full API Reference for all available endpoints. Info Codex CLI runs in your terminal with access to your environment variables. Make sure NITROSEND_API_KEY is set in your shell before starting Codex. ### Google Gemini Path: `/integrations/gemini` Connect Nitrosend to Gemini CLI via MCP for AI-powered email marketing Google Gemini + Nitrosend MCP Gemini CLI has native MCP support, giving you the full Nitrosend toolkit — every public tool, live Brand Kit context, and account state — directly in your terminal. Setup Step 1: Add the MCP server Add to your Gemini CLI settings ( ~/.gemini/settings.json ): Copy { "mcpServers" : { "nitrosend" : { "httpUrl" : "https://api.nitrosend.com/mcp" } } } Step 2: Sign in On first use, Gemini opens your browser for Nitrosend sign-in. Log in or create a free account — no API key needed. Step 3: Use it Copy gemini "Check my Nitrosend account status and draft a welcome email" Copy gemini "Create a campaign for our product launch and target my VIP segment" Gemini now has access to the full public set of Nitrosend MCP tools — composing emails, managing contacts, building flows, checking analytics, and more. Tip MCP gives Gemini your full Brand Kit context — colors, fonts, tone of voice — so emails it creates are on-brand without extra prompting. Example workflows Set up Brand Kit + compose Copy gemini "Scrape my Brand Kit from https://mysite.com and draft a product announcement email" Build a flow Copy gemini "Create a welcome flow that sends 3 emails over a week when someone subscribes" Check performance Copy gemini "Show me insights for my last campaign compared to benchmarks" Function Calling via Google AI SDK (Alternative) For programmatic integration or Google AI Studio, you can define Nitrosend operations as function declarations and call the REST API directly. This requires an API key from Settings > API Keys in the Nitrosend dashboard ( app.nitrosend.com/my/settings/api-keys ). Define function declarations Copy from google import genai from google.genai import types client = genai.Client() nitrosend_tools = types.Tool( function_declarations = [ types.FunctionDeclaration( name = "send_email" , description = "Send a transactional email to one recipient immediately" , parameters = types.Schema( type = types.Type. OBJECT , properties = { "to" : types.Schema( type = types.Type. STRING , description = "Recipient email" ), "subject" : types.Schema( type = types.Type. STRING , description = "Subject line" ), "html" : types.Schema( type = types.Type. STRING , description = "Email body as HTML" ) }, required = [ "to" , "subject" , "html" ] ) ), types.FunctionDeclaration( name = "list_campaigns" , description = "List all email campaigns with their status" , parameters = types.Schema( type = types.Type. OBJECT , properties = { "status" : types.Schema( type = types.Type. STRING , description = "Filter by status: draft, active, paused, completed" ) } ) ) ] ) Handle function calls Copy import requests NITROSEND_API_KEY = "nskey_live_your_key_here" # From Settings > API Keys: https://app.nitrosend.com/my/settings/api-keys BASE_URL = "https://api.nitrosend.com/v1/my" HEADERS = { "Authorization" : f "Bearer { NITROSEND_API_KEY } " , "Content-Type" : "application/json" } def execute_nitrosend_function ( name , args ): if name == "send_email" : resp = requests.post( f " { BASE_URL } /messages" , headers = HEADERS , json = { "channel" : "email" , "to" : args[ "to" ], "subject" : args[ "subject" ], "html" : args[ "html" ] }) return resp.json() elif name == "list_campaigns" : params = { "status" : args[ "status" ]} if "status" in args else {} resp = requests.get( f " { BASE_URL } /campaigns" , headers = HEADERS , params = params) return resp.json() Key API endpoints Function name API call What it does send_email POST /v1/my/messages Send a single email immediately list_campaigns GET /v1/my/campaigns List all campaigns create_campaign POST /v1/my/campaigns Create a new campaign list_contacts GET /v1/my/contacts List contacts in your audience get_account GET /v1/my/account Check account status and limits See the full API Reference for all available endpoints. Info MCP via Gemini CLI is the recommended setup — OAuth sign-in, no API keys to manage. The function calling method is for programmatic use where you need direct API access. ### VS Code (Copilot) Path: `/integrations/vscode` Connect Nitrosend to VS Code via MCP for AI-powered email marketing with GitHub Copilot VS Code + Nitrosend MCP VS Code with GitHub Copilot supports MCP servers natively. Connect Nitrosend and Copilot becomes your email marketing assistant — compose campaigns, manage contacts, and check analytics from your editor. Setup Step 1: Add the MCP server Add to .vscode/mcp.json in your workspace: Copy { "servers" : { "nitrosend" : { "type" : "http" , "url" : "https://api.nitrosend.com/mcp" } } } Or use the command palette: MCP: Add Server → select HTTP type → enter https://api.nitrosend.com/mcp . For global access across all workspaces, use the MCP: Open User Configuration command instead. Step 2: Sign in VS Code handles OAuth automatically. On first tool call, your browser opens for Nitrosend sign-in. Log in or create a free account — no API key needed. Step 3: Verify In Copilot chat, ask: Copy What's my Nitrosend account status? Copilot calls nitro_get_status and shows your account health. Managing tools Click the Configure Tools button in the Copilot chat input to see every available Nitrosend tool. You can toggle specific tools on or off. Example workflows Brand setup + first email Copy Set up my Brand Kit from https://mysite.com and draft a welcome email for new subscribers. Campaign creation Copy Create a campaign called "Weekly Update" for my newsletter list with a text section and a CTA button linking to our blog. Performance check Copy Show me insights for my most recent campaign. Troubleshooting OAuth window doesn't open Make sure you're running VS Code 1.99 or later — MCP support was added in this version. Check that GitHub Copilot extension is installed and active. Server not detected Verify the mcp.json file is valid JSON. Use the command palette MCP: List Servers to check if Nitrosend appears. Tool calls time out Brand Kit scraping can take 20-30 seconds. If other calls time out, check connectivity to api.nitrosend.com . Info VS Code supports OAuth 2.1 natively for MCP servers — no API keys or middleware needed. ### Windsurf Path: `/integrations/windsurf` Connect Nitrosend to Windsurf via MCP for AI-powered email marketing with Cascade Windsurf + Nitrosend MCP Windsurf is the AI-powered editor by Codeium. Windsurf supports MCP servers natively with OAuth across all transport types. Connect Nitrosend and Cascade becomes your email marketing assistant. Setup Step 1: Add the MCP server Add to ~/.codeium/windsurf/mcp_config.json : Copy { "mcpServers" : { "nitrosend" : { "serverUrl" : "https://api.nitrosend.com/mcp" } } } Step 2: Sign in Restart Windsurf. On first tool call, Windsurf opens your browser for Nitrosend sign-in. Log in or create a free account — no API key needed. Step 3: Verify In Cascade, ask: Copy What's my Nitrosend account status? Cascade calls nitro_get_status and shows your account health. Example workflows Brand Kit + email in one prompt Copy Scrape my Brand Kit from https://mysite.com and compose a launch announcement email. Build a flow Copy Create a welcome flow: when a contact is added, send a welcome email, wait 3 days, then send a follow-up. Check performance Copy Show me open rates and click rates for my last campaign. Troubleshooting Server doesn't connect Make sure the config file is valid JSON and located at ~/.codeium/windsurf/mcp_config.json . Restart Windsurf after editing. OAuth sign-in fails Check that your browser can reach api.nitrosend.com . If sign-in keeps failing, try clearing Windsurf's MCP auth cache. Tool calls time out Brand Kit scraping takes 20-30 seconds (one-time). Other calls should complete in 1-3 seconds. Check your network if timeouts persist. Info Windsurf supports OAuth natively for all MCP transport types — no API keys or bridge tools needed. ### Zed Path: `/integrations/zed` Connect Nitrosend to Zed via MCP for AI-powered email marketing Zed + Nitrosend MCP Zed is a high-performance code editor with AI assistant support. Zed supports remote MCP custom servers directly, including OAuth-based authentication. Setup Step 1: Add the MCP server Add Nitrosend to your Zed settings (Settings → Open Settings), or use Agent Panel → Settings → Add Custom Server : Copy { "context_servers" : { "nitrosend" : { "url" : "https://api.nitrosend.com/mcp" } } } Step 2: Sign in On first tool call, Zed will prompt you to authenticate against Nitrosend using the standard MCP OAuth flow. Log in or create a free account — no API key needed. Step 3: Verify In Zed's AI assistant, ask: Copy What's my Nitrosend account status? Example workflows Copy Set up my Brand Kit from https://mysite.com and draft a welcome email. Copy Create a campaign called "Product Update" targeting my main list. Troubleshooting Sign-in prompt didn't open Open Zed's Agent Panel settings and confirm the nitrosend server is active. If you configured the server with a plain url and no Authorization header, Zed should prompt for OAuth on first tool use. Server stays inactive Check the indicator in Zed's Agent Panel settings. If it is not green, verify the url is exactly https://api.nitrosend.com/mcp and that Zed can reach api.nitrosend.com from your network. Info Nitrosend works with Zed's native remote MCP support. No bridge package or local Node process is required for the standard setup. ### Brand Kit & AI Memory Path: `/integrations/mcp-brand-kit-memory` Teach your AI your brand voice, goals, and strategy so every email it creates is on-brand Brand Kit & AI Memory Nitrosend's two most powerful MCP features are Brand Kit and AI Memory . Together, they give your AI assistant deep context about your business — so every email it creates automatically matches your brand, follows your strategy, and improves over time. Brand Kit Brand Kit defines how your emails look and who they come from. When your AI creates an email via MCP, these settings are applied automatically — the right colors, fonts, logo, and tone, every time. Edit it under Brand > Brand Kit ( app.nitrosend.com/my/brand ). In accounts with multiple brands, MCP has a current brand. Use nitro_get_status to inspect current_brand and available_brands , then use nitro_select_brand to switch the current brand for OAuth sessions. nitro_set_brand_kit does not switch context; it updates the Brand Kit for the currently selected brand. What Brand Kit includes Setting What it controls Company name Used in email headers, footers, and subject lines Company description Gives AI context about what you do Brand color Primary color for buttons, links, and accents Background color Email body background Text color Default text color Heading font Font family for headings Body font Font family for body text Logo Light and dark versions, applied to email headers Physical address Required for CAN-SPAM compliance in email footers How to configure Brand Kit Dashboard Via MCP (AI prompt) Via API Go to Brand > Brand Kit in the Nitrosend dashboard ( app.nitrosend.com/my/brand ). Fill in your Brand Kit details and click save. Ask your connected AI assistant: Copy Set up my Brand Kit from https://mywebsite.com Claude will scrape your website for brand colors, fonts, and logo, then configure everything automatically. You can also set fields directly: Copy Update my brand color to #6366F1 and company name to "Acme Inc" Copy # Auto-scrape from website curl -X POST https://api.nitrosend.com/mcp \ -H "Authorization: Bearer $NITROSEND_API_KEY " \ -H "Content-Type: application/json" \ -d '{"method": "nitro_set_brand_kit", "params": {"url": "https://mywebsite.com"}}' Why Brand Kit matters for AI Without Brand Kit, your AI has to guess your brand colors, tone, and style for every email. With Brand Kit configured: Emails automatically use your brand colors, fonts, and logo Subject lines and copy match your company's tone Footer compliance (CAN-SPAM address) is handled automatically Every email is consistent, whether created by you, your team, or AI AI Memory AI Memory is a persistent document that your AI reads every time it interacts with your Nitrosend account via MCP. It's where you tell the AI about your business goals, target audience, email strategy, and anything else it should know. Edit it under Settings > AI Memory ( app.nitrosend.com/my/settings/memory ). Think of it as giving your AI marketing assistant a briefing document that it reads before every task. What to put in AI Memory AI Memory is free-form markdown. Here are the most impactful things to include: Copy ## Brand Voice We're friendly but professional. We write in second person ("you") and keep sentences short. No corporate jargon. We use humor sparingly but effectively. ## Target Audience Small business owners and indie creators who are tech-savvy but time-poor. They value efficiency and straight talk over flashy design. ## Email Strategy - Weekly newsletter every Tuesday at 10am EST - Welcome series: 5 emails over 2 weeks - Re-engagement flow for contacts inactive 30+ days - Always include one clear CTA per email ## Current Goals - Grow newsletter to 5,000 subscribers by Q2 2026 - Improve open rate from 35% to 45% - Launch product announcement campaign in April ## What NOT to do - Never use clickbait subject lines - Don't send more than 3 emails per week to any contact - Avoid discount-heavy messaging — we compete on value, not price How to configure AI Memory Dashboard Via MCP (AI prompt) Go to Settings > AI Memory in the Nitrosend dashboard ( app.nitrosend.com/my/settings/memory ). Edit the markdown document and save. Ask your connected AI assistant: Copy Update my AI memory with our Q2 marketing goals: grow to 5000 subscribers, improve open rate to 45%, launch product campaign in April Claude will read the current memory, add the new information, and save it. You can also ask Claude to read it: Copy What's in my Nitrosend AI memory? How AI Memory works with MCP When your AI connects to Nitrosend via MCP, it receives your AI Memory as context. This means: Every email Claude creates reflects your stated brand voice and strategy Flow design follows your documented email cadence and rules Campaign targeting considers your audience description and goals Recommendations are aligned with your stated priorities The more context you put in AI Memory, the less you need to explain in each prompt. Brand + Memory in action Here's the difference these settings make: Without Brand Kit & Memory You : Create a campaign about our new feature Claude : What's your brand color? What tone should I use? Who's the audience? What's the call to action? What's your company name for the footer? With Brand Kit & Memory configured You : Create a campaign about our new feature Claude : Done. I've created a campaign using your indigo brand theme, written in your friendly-but-professional tone, targeting your active subscribers. The CTA links to your product page. Here's a preview — want me to send a test? Tip Start with Brand Kit, then add AI Memory. Brand Kit handles the visual and compliance basics. AI Memory adds the strategic layer that makes your AI truly effective. MCP tools for Brand Kit & Memory Tool What it does nitro_select_brand Switch the current brand for OAuth MCP sessions nitro_set_brand_kit Set brand identity from a website URL or direct fields nitro_set_memory Read, update, patch, or append to AI Memory Resource What it provides nitro://brand-kit Current Brand Kit identity (read-only snapshot) ### REST API Path: `/integrations/rest-api` Use the Nitrosend REST API from any language, tool, or automation platform The Nitrosend REST API works with any HTTP client. Use it to integrate email marketing into your app, connect to automation platforms like Zapier or n8n, or build custom tools in any programming language. Info All plans, including free plans, have full access to Nitrosend MCP/API/CLI. REST API access is not restricted to paid plans; plan limits apply to usage volume and paid add-ons. Base URL Copy https://api.nitrosend.com/v1/my All endpoints are prefixed with /v1/my and scoped to your account. Authentication Every request requires a Bearer token in the Authorization header: Copy Authorization: Bearer your-nitrosend-api-ke y > Get your API key from Settings > API Keys in the Nitrosend dashboard ( app.nitrosend.com/my/settings/api-keys ). Quick examples Send an email Copy curl -X POST https://api.nitrosend.com/v1/my/messages \ -H "Authorization: Bearer $NITROSEND_API_KEY " \ -H "Content-Type: application/json" \ -d '{ "channel": "email", "to": "user@example.com", "subject": "Welcome to our platform", "html": " Welcome! Thanks for signing up. ", "body": "Welcome! Thanks for signing up." }' List campaigns Copy curl https://api.nitrosend.com/v1/my/campaigns \ -H "Authorization: Bearer $NITROSEND_API_KEY " Create a contact Copy curl -X POST https://api.nitrosend.com/v1/my/contacts \ -H "Authorization: Bearer $NITROSEND_API_KEY " \ -H "Content-Type: application/json" \ -d '{ "email": "jane@example.com", "first_name": "Jane", "last_name": "Doe", "opt_in": true }' Get account status Copy curl https://api.nitrosend.com/v1/my/account \ -H "Authorization: Bearer $NITROSEND_API_KEY " Language examples Python Copy import requests API_KEY = "nskey_live_your_key_here" BASE_URL = "https://api.nitrosend.com/v1/my" HEADERS = { "Authorization" : f "Bearer { API_KEY } " , "Content-Type" : "application/json" } # Send an email response = requests.post( f " { BASE_URL } /messages" , headers = HEADERS , json = { "channel" : "email" , "to" : "user@example.com" , "subject" : "Hello from Python" , "html" : " Hi! " , "body" : "Hi!" }) print (response.json()) # List campaigns campaigns = requests.get( f " { BASE_URL } /campaigns" , headers = HEADERS ) print (campaigns.json()) JavaScript / Node.js Copy const API_KEY = "nskey_live_your_key_here" ; const BASE_URL = "https://api.nitrosend.com/v1/my" ; const headers = { Authorization: `Bearer ${ API_KEY } ` , "Content-Type" : "application/json" , }; // Send an email const response = await fetch ( ` ${ BASE_URL } /messages` , { method: "POST" , headers , body: JSON . stringify ({ channel: "email" , to: "user@example.com" , subject: "Hello from Node.js" , html: " Hi! " , body: "Hi!" , }), }); console . log ( await response . json ()); // List campaigns const campaigns = await fetch ( ` ${ BASE_URL } /campaigns` , { headers }); console . log ( await campaigns . json ()); Ruby Copy require "net/http" require "json" API_KEY = "nskey_live_your_key_here" BASE_URL = "https://api.nitrosend.com/v1/my" def nitrosend_request ( method , path , body = nil ) uri = URI ( " #{ BASE_URL }#{ path } " ) http = Net :: HTTP . new (uri. host , uri. port ) http. use_ssl = true request = case method when :get then Net :: HTTP :: Get . new (uri) when :post then Net :: HTTP :: Post . new (uri) end request[ "Authorization" ] = "Bearer #{ API_KEY } " request[ "Content-Type" ] = "application/json" request. body = body. to_json if body JSON . parse (http. request (request). body ) end # Send an email result = nitrosend_request ( :post , "/messages" , { channel: "email" , to: "user@example.com" , subject: "Hello from Ruby" , html: " Hi! " , body: "Hi!" }) puts result Transactional messages POST /v1/my/messages queues a single transactional email or SMS and returns the message resource. Delivery happens through Nitrosend's normal background sender, so the response means "accepted for sending", not "delivered". For email, pass channel: "email" , to , subject , and one of html , body , or template_id . body is plain text; HTML belongs in html . Optional per-message delivery options are available for email only: Copy { "channel" : "email" , "from" : "Acme " , "to" : "user@example.com" , "subject" : "Your receipt" , "html" : " Thanks for your purchase. " , "body" : "Thanks for your purchase." , "reply_to" : "support@example.com" , "headers" : { "X-Entity-Ref-ID" : "order_123" }, "tags" : { "kind" : "receipt" , "plan" : "pro" } } from / from_email must resolve to a verified sender identity for the brand. Reserved provider headers and reserved Nitrosend tag keys are rejected. System tags such as account_id , brand_id , and message_id are always controlled by Nitrosend. Pass an Idempotency-Key header on retries. Reusing the same key with the same payload returns the original message. Reusing the key with a different payload returns 409 idempotency_conflict . To learn what happened to a message after it is accepted (sent, delivered, bounced, failed and more) without polling, register a webhook . Suppressions GET /v1/my/suppressions lists the account's active email suppressions by default. Use it to inspect hard bounces, soft-bounce suppressions, complaints, manual suppressions, and the bounded provider diagnostic text retained from the source feedback event. Common filters include email , reason , source_provider , and active=false for expired suppressions. Delivery capacity GET /v1/my/delivery/status returns the current Brand's read-only delivery projection: the account's fixed plan/standing allowance, plus pacing and a bounded public reason/remediation. This endpoint never authorizes or reserves a send. The actual send still passes through the single delivery authority, so clients must not calculate headroom from a plan name. Plan checkout and prepaid funding Plan purchase and prepaid balance are separate account-scoped lifecycles. Send a stable Idempotency-Key when creating either purchase. For a plan, call POST /v1/my/subscription/checkout , complete provider approval when requested, then poll GET /v1/my/subscription/checkout_status?purchase_id=... until the requested plan is active. For prepaid balance, inspect GET /v1/my/billing/funding first. Create a funding purchase with POST /v1/my/billing/funding/purchases , passing amount_cents , currency , and a stable Idempotency-Key . Poll GET /v1/my/billing/funding/purchases/{id} until the purchase is credited. Checkout creation, browser redirect, and webhook delivery alone are not completion proof. Payments for agents owns the full MCP, CLI, and REST journey; Purchase lifecycle defines state and authority. Use the generated API reference for exact fields. Explicit prepaid validation Email validation is never included or triggered by import, schedule, or send. Quote exactly one current-Brand audience first: Copy curl -X POST https://api.nitrosend.com/v1/my/validation_operations/quote \ -H "Authorization: Bearer $NITROSEND_API_KEY " \ -H "Content-Type: application/json" \ -d '{"list_id": 42}' After inspecting the candidate counts, funding state, and maximum prepaid charge, start the durable operation with a stable idempotency key: Copy curl -X POST https://api.nitrosend.com/v1/my/validation_operations \ -H "Authorization: Bearer $NITROSEND_API_KEY " \ -H "Content-Type: application/json" \ -H "Idempotency-Key: validation-list-42-2026-08" \ -d '{"list_id": 42}' The request must contain exactly one of contact_channel_ids , contact_ids , list_id , or segment_id . Reusing the key with the same audience returns or resumes the same operation; changing the audience conflicts. Poll the operation and list its retained per-candidate items. Validation does not prove consent; qualifying current results contribute to the one standing rank that selects the plan allowance. Endpoint reference Action Method Endpoint Billing Create or reuse plan checkout POST /subscription/checkout Reconcile plan checkout GET /subscription/checkout_status Get prepaid funding status GET /billing/funding Create or reuse funding purchase POST /billing/funding/purchases Reconcile funding purchase GET /billing/funding/purchases/{id} Messages Send a message POST /messages List messages GET /messages Webhooks List webhooks GET /webhooks Create webhook POST /webhooks Send test event POST /webhooks/{id}/test List deliveries GET /webhooks/{id}/deliveries Suppressions List suppressions GET /suppressions Delivery Get capacity and pacing status GET /delivery/status Email validation Quote validation POST /validation_operations/quote Start or resume validation POST /validation_operations Get validation operation GET /validation_operations/{id} List validation results GET /validation_operations/{id}/items Events Track a contact event POST /events List events GET /events Campaigns List campaigns GET /campaigns Create campaign POST /campaigns Get campaign GET /campaigns/{id} Update campaign PATCH /campaigns/{id} Send campaign POST /campaigns/{id}/send Contacts List contacts GET /contacts Create contact POST /contacts Bulk import contacts POST /imports Poll import status GET /imports/{id} Lists List contact lists GET /lists Create list POST /lists Add/remove contacts by email POST /lists/{id}/contacts/bulk Templates List templates GET /templates Create template POST /templates Get template GET /templates/{id} Update template PATCH /templates/{id} Send test email POST /templates/{id}/send_test Flows List flows GET /flows Create flow POST /flows Segments List segments GET /segments Account Get account info GET /account See the full API Reference for detailed request/response schemas. Bulk imports Use POST /v1/my/imports with multipart/form-data to upload a CSV. The response includes an import id ; poll GET /v1/my/imports/{id} for status, row counts, and row-level errors. For contact imports, pass options with list_ids so imported contacts are assigned to the target list: Copy curl -X POST https://api.nitrosend.com/v1/my/imports \ -H "Authorization: Bearer $NITROSEND_API_KEY " \ -F "file=@contacts.csv" \ -F "resource=contacts" \ -F 'options={"list_ids":[88]}' Pagination List endpoints return paginated results. Pagination headers are included in the response: Header Description X-Total-Count Total number of records X-Total-Pages Total number of pages X-Page-Number Current page number Use ?page=2&limit=25 query parameters to navigate pages. Error handling Errors return JSON with a consistent structure: Copy { "error" : true , "code" : "validation_error" , "message" : "Email is required" , "validation_errors" : { "email" : [ "can't be blank" ] } } Status code Meaning 200 Success 201 Created 401 Invalid or missing API key 404 Resource not found 422 Validation error 429 Rate limited OpenAPI spec The full API specification is available as OpenAPI 3.1 YAML: Copy https://api.nitrosend.com/openapi.yaml Import this into any tool that supports OpenAPI — Postman, Insomnia, Swagger UI, or API clients in any language. ### Events API Path: `/integrations/events-api` Send product events to Nitrosend and use their data in automated flows Use POST /v1/my/events when an action in your product should start or personalize a Nitrosend flow. Your application remains the source of truth: Nitrosend does not inspect your database or infer an event schema from Supabase, Postgres, or another data store. Choose the event contract For a custom event, you choose the name and the JSON fields in data . Nitrosend accepts nested JSON objects and arrays; it does not require you to map custom attributes to a fixed Klaviyo-style schema. Keep each event contract stable once a flow uses it. For example, if a flow expects event.order.total , continue sending that path with the same meaning and value type. Event names must start with a lowercase letter, contain only lowercase letters, numbers, and underscores, and be at most 64 characters. The data object can be at most 32 KB. Some built-in events have required fields because their supplied flow recipes use them. For example: password_reset requires data.reset_url . order_shipped requires data.tracking_url ; data.carrier and data.tracking_number are optional. Custom events remain open: their fields are defined by your integration. Send an event The contact must already exist in the current Nitrosend brand. Identify it by contact_id or contact_email , and always supply a stable idempotency key so a retry cannot start the same flow twice. Copy curl -X POST https://api.nitrosend.com/v1/my/events \ -H "Authorization: Bearer $NITROSEND_API_KEY " \ -H "Content-Type: application/json" \ -H "Idempotency-Key: order_8472_paid" \ -d '{ "event": "order_paid", "contact_email": "jane@example.com", "data": { "order": { "id": "8472", "total": 129.00, "currency": "AUD" }, "plan": "pro" } }' On the first request Nitrosend returns 201 Created . Replaying the same event name and idempotency key for the brand returns the existing event with 200 OK . Use an immutable source event ID for the key; do not reuse a key for changed data. Use event data in a flow A flow with the order_paid trigger can read the payload under event : Copy Order {{ event.order.id }} for {{ event.order.total }} {{ event.order.currency }} When a field may be absent, give the email a safe default: Copy Plan: {{ event.plan | default: "standard" }} Create and test the event contract before activating the flow. This keeps the producer and the email content aligned without requiring Nitrosend to own your application schema. Supabase and other data stores Send the event from trusted server-side code after your application commits the underlying change. With Supabase, this can be an Edge Function, database webhook consumer, or application backend. Keep the Nitrosend API key on the server; do not expose it in browser code. The integration boundary is one explicit envelope: Copy { "event" : "your_event_name" , "contact_id" : 123 , "data" : { "your" : "schema" } } Use contact_email instead of contact_id when your system stores email as the shared identifier. If the contact does not exist in the selected brand, create or import it before sending the event. See the API Reference for list, lookup, and filter operations on recorded events. ### Webhooks Path: `/integrations/webhooks` Receive signed email, reply and account events at your own HTTPS endpoint A webhook tells your application what happened without polling. Nitrosend POSTs a signed event to your HTTPS endpoint when a transactional email is accepted, delivered, bounced, complained about, opened, clicked or fails for good, when a reply reaches one of your brand's inboxes, and when something changes on the account: a campaign finishes, sending pauses, a domain is verified. Email events cover transactional email: single messages your brand sends, not campaigns or flows. SMS and test sends do not emit email events. Email events Event Sent when email.sent The provider accepted the message. email.delivered The recipient's mail server accepted it. email.bounced It bounced, hard or soft. email.complained The recipient marked it as spam. email.opened The first time it was opened. Security scanners and bots do not count. email.clicked The first human click of each link in it. email.failed It failed for good. A failure Nitrosend is still retrying is not reported, and a send whose provider outcome is unknown reports once that outcome is known. email.received A message reached one of the brand's inboxes and was not quarantined: a reply, an automatic reply, or new mail to the inbox address. See Replies . Opens and clicks come from Nitrosend's own tracking, so a brand that turns open or click tracking off stops sending those two events. Account events Event Sent when campaign.completed A campaign finished sending to its whole audience. campaign.failed A campaign send ended before reaching any recipient. campaign.stopped Nitrosend paused a campaign send because its bounce or spam-complaint rate crossed the stop threshold. flow.paused Nitrosend paused a flow because its bounce or spam-complaint rate crossed the pause threshold. sending.paused Nitrosend paused all sending on the account. sending.limit_approaching The account has used most of its rolling 24-hour sending allowance. sending.limit_reached The account has used its whole rolling 24-hour allowance; sends wait for capacity. import.completed A contact import finished. import.failed A contact import failed. domain.verified A sending domain passed DNS verification and can send. domain.verification_lost A verified sending domain failed its DNS checks and is no longer verified. domain.dns_degraded A verified sending domain's DNS records could not be confirmed; it stays verified while Nitrosend rechecks. webhook.disabled Nitrosend turned off one of the brand's webhooks after three days of failed deliveries. The three sending.* events reach the webhooks of every brand in the account; the others reach the webhooks of the brand they concern. The API Reference lists each event's fields. Add an endpoint In the dashboard, open Settings > Webhooks and choose New Webhook ( app.nitrosend.com/my/settings/webhooks ). Enter the endpoint URL and pick the events it should receive. Settings lists the email events; add account events through the API. Or create it with the API: Copy curl -X POST https://api.nitrosend.com/v1/my/webhooks \ -H "Authorization: Bearer $NITROSEND_API_KEY " \ -H "Content-Type: application/json" \ -d '{ "url": "https://example.com/webhooks/nitrosend", "events": ["email.delivered", "email.bounced", "email.complained", "email.failed"] }' The response includes the endpoint's signing secret ( whsec_... ) in full. Store it with your other secrets. Later reads mask it; reveal it again from the dashboard or with GET /v1/my/webhooks/{id}?reveal=true . The URL must use HTTPS, resolve to a public internet address, and carry no credentials. A brand can have up to 10 webhooks. Webhooks appear in Settings only for people who can manage API keys. What a request looks like Each event is one POST with a JSON body: Copy POST /webhooks/nitrosend HTTP/1.1 Content-Type: application/json User-Agent: Nitrosend-Webhooks/1.0 webhook-id: evt_0192f3a4-7b1c-7d2e-9f10-2a3b4c5d6e7f webhook-timestamp: 1790740800 webhook-signature: v1,K5oZfzN95Z9UVu1EsfQmfVNQhnkZ2pj9o9NDN/H/pI4= { "type": "email.bounced", "timestamp": "2026-09-30T10:00:00.000Z", "data": { "message_id": 48213, "to": "ada@example.com", "subject": "Reset your password", "idempotency_key": "reset-8841", "tags": { "user_id": "42" }, "bounce": { "type": "hard", "subtype": "NoEmail" } } } message_id is the id that POST /v1/my/messages returned. idempotency_key is the Idempotency-Key you sent the message with. It is left out when you sent none. tags are the tags you sent the message with. timestamp is when the event happened. Some events add detail, such as bounce on email.bounced or failure on email.failed . The API Reference lists every event's fields. Replies email.received reports mail that reaches one of your brand's inboxes. Inboxes are part of the Agent Inbox , in beta and enabled on request. A reply reaches Nitrosend when it is sent to an inbox address, so send with that address as reply_to (or from ) when you want replies reported. Copy { "type" : "email.received" , "timestamp" : "2026-09-30T10:04:12.000Z" , "data" : { "message_id" : 48213 , "idempotency_key" : "receipt-77" , "tags" : { "order" : "77" }, "from" : "ada@example.com" , "to" : "support@yourbrand.com" , "subject" : "Re: Your receipt" , "text" : "Can I change the delivery address?" , "html" : " Can I change the delivery address? " , "truncated" : false , "auto_submitted" : false , "attachments" : [ { "id" : 311 , "filename" : "photo.jpg" , "content_type" : "image/jpeg" , "size" : 48213 , "scanned" : true } ], "conversation_id" : 9021 , "conversation_message_id" : 55410 } } When the message answers one of your sends, message_id , idempotency_key and tags identify that send, as they do on every other event. Otherwise message_id is null and tags is empty. text and html are each capped at 64 KiB; truncated is true when either was cut. The full message stays in the inbox. attachments lists the files the inbox stored, without their contents. Download one with GET /v1/my/conversations/{conversation_id}/messages/{conversation_message_id}/attachments/{id} . A file with scanned: false was not virus-scanned and downloads only with ?acknowledge_unscanned=true . auto_submitted is true for automatic replies such as out-of-office notices. Mail the inbox quarantines (spam, viruses, blocked senders) is not sent. Each message is reported once. Verify the signature Requests are signed in the Standard Webhooks format, so any Standard Webhooks library can verify them. Verify every request before acting on it, using the raw body exactly as received. With the Node SDK: Copy import express from 'express' ; import { verifyWebhook , WebhookVerificationError } from '@nitrosend/sdk' ; const app = express (); // Register this route before any JSON body parser, so the raw body reaches it. app . post ( '/webhooks/nitrosend' , express . raw ({ type: 'application/json' }), async ( req , res ) => { try { const event = await verifyWebhook ( req . body . toString ( 'utf8' ), req . headers , process . env . NITROSEND_WEBHOOK_SECRET ! , ); // event.type, event.data.message_id, ... res . sendStatus ( 200 ); } catch ( error ) { if ( error instanceof WebhookVerificationError ) return res . sendStatus ( 400 ); throw error ; } }); Without a library: Base64-decode the part of the secret after whsec_ . That is the key. Build the signed content: {webhook-id}.{webhook-timestamp}.{raw body} . Compute an HMAC-SHA256 of it with the key and base64-encode the result. Compare it, in constant time, with each v1, value in webhook-signature (values are space-separated). Accept the request if one matches. Reject the request if webhook-timestamp is more than 5 minutes from now. Copy import base64, hashlib, hmac, time def verify ( body : bytes , headers , secret : str ) -> bool : try : msg_id = headers[ "webhook-id" ] timestamp = int (headers[ "webhook-timestamp" ]) signatures = headers[ "webhook-signature" ].split( " " ) except ( KeyError , ValueError ): return False if abs (time.time() - timestamp) > 300 : return False key = base64.b64decode(secret.removeprefix( "whsec_" )) signed = f " { msg_id } . { timestamp } ." .encode() + body expected = base64.b64encode(hmac.new(key, signed, hashlib.sha256).digest()).decode() return any ( hmac.compare_digest(sig.split( "," , 1 )[ 1 ], expected) for sig in signatures if sig.startswith( "v1," ) ) Respond, retries and duplicates Return any 2xx status within 10 seconds to acknowledge an event. Anything else is a failed attempt: another status, a timeout, or a redirect, which Nitrosend does not follow. A failed event is retried after 1 minute, 5 minutes, 30 minutes, 2 hours, 8 hours and 24 hours, then marked failed. An event can arrive more than once (for example, when your endpoint handled it but answered too late), and a retried event can arrive after later ones. webhook-id stays the same across retries of an event: record the ids you have handled and skip repeats, and use timestamp for ordering. When deliveries to an endpoint have failed continuously for 3 days, the next failed attempt turns the endpoint off and emails the account owner. Events that occur while an endpoint is off are not sent, including after you turn it back on. Test and inspect Choose Send test event from the webhook's menu, or call POST /v1/my/webhooks/{id}/test , to send a sample event through the same signed delivery path. A test event has "test": true in data and its ids are null . The API sends email.delivered by default; pass {"type": "email.received"} (or any other event) to test that event's shape. Each webhook keeps the last 7 days of deliveries, with their status, response code and attempt count. See them in the webhook's edit dialog, or with GET /v1/my/webhooks/{id}/deliveries . API Action Method Endpoint List webhooks GET /v1/my/webhooks Create a webhook POST /v1/my/webhooks Get a webhook ( ?reveal=true for the full secret) GET /v1/my/webhooks/{id} Change the URL, events, or turn it on or off PATCH /v1/my/webhooks/{id} Delete a webhook and its delivery history DELETE /v1/my/webhooks/{id} Send a test event POST /v1/my/webhooks/{id}/test List recent deliveries GET /v1/my/webhooks/{id}/deliveries See the API Reference for request and response schemas. ### API Reference Path: `/integrations/api-reference` The full, interactive Nitrosend REST API reference, generated from our OpenAPI 3.1 spec The complete, always-current Nitrosend REST API reference is generated from our OpenAPI 3.1 spec. Browse every endpoint, parameter, and response schema, with copy-paste examples in curl, JavaScript, and Python. Open the full API Reference Every endpoint, request and response schema, and ready-to-run code samples. Quick facts Base URL : https://api.nitrosend.com/v1/my Auth : Authorization: Bearer nskey_live_... — grab a key in Settings > API Keys at app.nitrosend.com/my/settings/api-keys OpenAPI spec : https://api.nitrosend.com/openapi.yaml — import it into Postman, an SDK generator, or your agent's tool schema Info Every plan, including free plans, has full API access. Plan limits apply to send volume, seats, and AI action allowances — not to the API itself. Prefer a guided walkthrough with language examples? See the REST API guide . Operating from a terminal or CI? See the CLI overview . ### Vercel AI SDK Path: `/integrations/vercel-ai-sdk` Use Nitrosend tools inside Vercel AI SDK agents and generateText/streamText calls @nitrosend/ai-sdk exposes Nitrosend's MCP tool surface — drafting campaigns, sending tests, managing contacts, building flows, and reviewing delivery — to applications built on the Vercel AI SDK . It is a typed wrapper over the Nitrosend remote MCP server and is not a model package. Install Copy npm install @nitrosend/ai-sdk ai @ai-sdk/mcp zod The examples below also use @ai-sdk/openai for the model. Substitute your preferred AI SDK model package ( @ai-sdk/anthropic , @ai-sdk/google , etc.): Copy npm install @ai-sdk/openai Authenticate Get a key from Settings → API Keys ( app.nitrosend.com/my/settings/api-keys ) and export it. The package reads NITROSEND_API_KEY automatically, or you can pass apiKey explicitly. Copy export NITROSEND_API_KEY = "nskey_live_ " Quickstart — generateText stopWhen: isStepCount(N) is required for tool-calling workflows: AI SDK defaults to a single step, so without it the call stops as soon as the model invokes a Nitrosend tool and you never see the final summary text. Copy import { generateText , isStepCount } from 'ai' ; import { openai } from '@ai-sdk/openai' ; import { withNitrosendTools } from '@nitrosend/ai-sdk' ; const result = await withNitrosendTools ({}, async ({ tools }) => { return generateText ({ model: openai ( 'gpt-4o' ), tools , stopWhen: isStepCount ( 5 ), prompt: 'Draft a welcome campaign for the Newsletter list.' , }); }); Streaming — streamText Copy import { streamText , isStepCount } from 'ai' ; import { openai } from '@ai-sdk/openai' ; import { withNitrosendTools } from '@nitrosend/ai-sdk' ; await withNitrosendTools ({}, async ({ tools }) => { const stream = streamText ({ model: openai ( 'gpt-4o' ), tools , stopWhen: isStepCount ( 5 ), prompt: 'Add founder@acme.com to the "Power users" list and confirm.' , }); for await ( const part of stream . textStream ) { process . stdout . write ( part ); } await stream . consumeStream (); }); withNitrosendTools opens the MCP transport, hands you the toolset, and closes the transport in a finally block when your callback resolves or throws — including when streamText is awaited inside. Lifecycle and subsets For longer-lived processes, hold the client yourself: Copy import { nitrosend } from '@nitrosend/ai-sdk' ; const { tools , client , close } = await nitrosend (); try { // …pass `tools` to generateText/streamText, or use `client` directly… } finally { await close (); } Filter the toolset to only what your agent should reach for — types narrow to match. Use withNitrosendTools so the transport closes after use: Copy import { withNitrosendTools } from '@nitrosend/ai-sdk' ; await withNitrosendTools ( { tools: [ 'nitro_get_status' , 'nitro_compose_campaign' ] }, async ({ tools }) => { // tools is typed to exactly { nitro_get_status, nitro_compose_campaign } }, ); Errors The wrapper does not retry. All failures throw NitrosendAISDKError with a stable .code and the original error preserved on .cause : AUTH_MISSING — no credential was found in options or env. AUTH_INVALID_PREFIX — apiKey did not start with nskey_live_ / nskey_test_ . CLIENT_INIT_FAILED — MCP transport could not connect (check URL, network, key). TOOLS_LIST_FAILED — server rejected the tools list call. Related REST API — Nitrosend HTTP API used by @nitrosend/sdk . MCP overview — connecting Claude Desktop / Cursor / IDEs via the @nitrosend/mcp stdio bridge. Tools Registry submission — public AI SDK tool directory; this package targets that listing. For one-click installation, account-scoped credentials, and marketplace billing, use the shipped Vercel Marketplace integration . ### Vercel Marketplace Path: `/integrations/vercel-marketplace` Install Nitrosend from Vercel Marketplace and connect project-ready AI SDK and MCP secrets Nitrosend's Vercel Marketplace integration is a thin native adapter over the existing Nitrosend dashboard. Vercel handles install, resource connection, secret delivery, plan changes, and billing. Nitrosend handles account, brand, campaign, list, template, and support workflows in the existing dashboard through SSO. The Marketplace integration is provider-isolated: an existing Nitrosend login may be reused for SSO, but a Vercel installation is not silently attached to a Stripe-owned account. Vercel's installation and resource identifiers remain the source of ownership for its local entitlement. Marketplace availability depends on Vercel provider approval. Until approval is complete, use the regular Nitrosend dashboard and API key flow. What Vercel Creates When you install Nitrosend from Vercel Marketplace, Vercel provisions a nitrosend-email resource. The resource maps to a Nitrosend account and brand, then creates project secrets for connected Vercel projects: NITROSEND_API_KEY NITROSEND_MCP_URL NITROSEND_API_URL Vercel may add a project-selected prefix when a resource is connected to a project. If you choose a prefix in Vercel, use the prefixed variable names in your app. Without a prefix, the examples below work as written. Use With AI SDK Copy npm install @nitrosend/ai-sdk ai @ai-sdk/mcp zod Copy import { generateText , isStepCount } from "ai" ; import { withNitrosendTools } from "@nitrosend/ai-sdk" ; const result = await withNitrosendTools ({}, async ({ tools }) => { return generateText ({ model , tools , stopWhen: isStepCount ( 5 ), prompt: "Draft a welcome campaign for the newest trial users." , }); }); @nitrosend/ai-sdk reads NITROSEND_API_KEY and NITROSEND_MCP_URL automatically from the Vercel project environment. Management Use Vercel for Marketplace resource operations: install and reinstall Nitrosend connect the resource to projects rotate secrets, with old keys kept through Vercel's requested grace window change the Marketplace billing plan uninstall the resource Use the existing Nitrosend dashboard for product work: manage brands and sending domains build campaigns and templates manage lists and contacts review usage and account settings contact support There is no separate Marketplace dashboard. Vercel SSO opens the existing Nitrosend dashboard with the right account and brand context. The SSO entry point follows Vercel's redirect-login flow: Vercel sends mode=sso , code , and state ; Nitrosend exchanges them with Vercel for a short-lived OIDC token, verifies it, then opens the existing dashboard callback. If the email on your Vercel team already has a Nitrosend account, the install is not attached to it automatically. Vercel shows a notice, and the first SSO takes you to the Nitrosend sign-in; once you are signed in as an admin of the account you want to use, the installation is linked to it. Installs with a new email get a Nitrosend account created for them. Billing Marketplace-billed resources are billed through Vercel. Nitrosend keeps a local entitlement subscription so account limits work, but it does not create a separate direct subscription for the same Marketplace resource. Paid Marketplace plans use resource-level billing, usage reporting, invoice submission, and invoice lifecycle webhooks. Free resources do not submit chargeable invoices. Nitrosend sends Vercel's current Marketplace billing payloads: usage reports go to /billing with timestamp , eod , period , billing.items , and usage ; invoices go to /billing/invoices with invoiceDate , period , items , and a stable externalId . Invoice webhooks are verified with Vercel's X-Vercel-Signature header before entitlement state is updated. Failed-payment notpaid events are recorded without immediate suspension; overdue or refunded events suspend the Marketplace resource, and paid events reactivate it. Paid resource and installation deletions submit a final resource invoice before deactivating local entitlements, and deletion responses remain unfinalized while Marketplace invoices are open. ### Stripe Projects Path: `/integrations/stripe-projects` Provision a Nitrosend account, API key and hosted sender from the Stripe CLI, pay for a tier with the card on your Stripe account, and manage it all with stripe projects Nitrosend is a provider on Stripe Projects . One command creates or links a Nitrosend account, provisions a sending brand with an API key and a hosted sender, writes the credentials to your .env , and lets you pick, upgrade and downgrade tiers from the terminal. Everything the CLI creates is an ordinary Nitrosend account you can open in the dashboard. Availability depends on Stripe listing Nitrosend in the Projects catalog. Until then, use the regular dashboard and API key flow. Quickstart Copy stripe plugin install projects stripe projects init stripe projects add nitrosend/email --config '{"company_name":"Acme Coffee","website":"https://acme.example"}' stripe projects env --pull add returns NITROSEND_API_KEY , NITROSEND_API_URL , NITROSEND_MCP_URL , NITROSEND_BRAND_SID and NITROSEND_FROM_ADDRESS . The sender is hello@ .nitrosend.net , reserved for you and activated on your first send; no DNS is needed. If you omit company_name , the CLI asks for it before the sender can be reserved. stripe projects llm-context points your coding agent at the Nitrosend agent guide: the variables, the MCP connection and the first send. Accounts Linking needs no browser. Stripe verifies the email on your Stripe account, and the link is made in one step: New to Nitrosend. That email gets a Nitrosend account, owned by you, with the normal dashboard access. Already a Nitrosend user. The account you administer is linked. If you administer several, the CLI asks which one. Already paying Nitrosend directly. Your plan and card stay as they are; change the plan in the dashboard. A Stripe account links to one Nitrosend account, so teammates working on the same Stripe account work on the same Nitrosend account. stripe projects unlink nitrosend removes the link and keeps the account. Tiers and billing Tiers are the public Nitrosend plans: Free, Pro and Ultra. Every tier has unlimited contacts; agent and API calls are not metered. Enrichment and email validation are metered per use on every tier. Copy stripe projects upgrade nitrosend # pick pro or ultra stripe projects downgrade nitrosend # back to free A paid tier is charged to the card on your Stripe Projects billing profile through a Shared Payment Token. Nitrosend runs the subscription in its own Stripe account with that token as the payment method, so the first charge and every renewal stay inside your Stripe Projects spending limit and show in stripe projects spend . An upgrade is charged the difference at once. Ultra costs more than the default Stripe Projects spending limit, so raise your limit in your Stripe account settings before choosing it. The dashboard shows the plan and routes plan changes back to the CLI. Credentials stripe projects rotate nitrosend/email issues a new API key; the previous key keeps working for 24 hours. stripe projects remove nitrosend/email deprovisions the resource: the key is revoked after a grace period, the brand and its content stay, and the account stays. Sending from your own domain The hosted sender needs no DNS. To send from your own domain, open Settings > Domains in the dashboard, add the domain, publish the records it prints, and select it. Both senders can coexist. Stripe customer data Nitrosend can also import the customers, subscriptions and invoices from the Stripe account you build on and turn them into segments, merge fields and flow triggers. See Stripe . Dashboard stripe projects open nitrosend prints a one-time sign-in link to the dashboard for this account. The link expires after five minutes and works once. ### AWS Marketplace Path: `/integrations/aws-marketplace` Link an AWS Marketplace license to Nitrosend and manage plan billing through AWS Nitrosend's AWS Marketplace integration uses the existing Nitrosend dashboard. AWS owns the paid plan and eligible usage charges; Nitrosend owns your brands, campaigns, contacts, templates, sending setup and usage controls. Marketplace availability depends on AWS seller registration and product approval. Until the listing is approved, use the regular Nitrosend signup and billing flow. Link a subscription After you subscribe in AWS Marketplace, AWS opens Nitrosend with a one-time registration token. Nitrosend verifies that token directly with AWS and then asks you to sign in or create an account. Choose the Nitrosend account that should use the AWS license and approve the link. An AWS request never silently attaches itself to an existing Nitrosend account. After approval, Nitrosend reads the license entitlement from AWS and shows the plan on the existing Billing page. Each AWS license stays separate. If one AWS account buys more than one agreement, ending one license does not end another license. Plans and usage The first offer contains monthly Pro and Ultra plans. Each plan includes its normal monthly email, SMS and AI allowances. Eligible use above those allowances is reported to the same AWS license each hour and appears through AWS Marketplace billing. The initial AWS offer supports: email overage US and Canada SMS overage Australia SMS overage AI action overage Nitrosend applies the normal allowance, spend cap, trust and delivery checks before work runs. A resource that is not priced in the AWS offer is unavailable through that license; it is not moved to a saved card. Manage billing Use the Nitrosend Billing page to review: the linked plan and current allowance period email, SMS and AI use included and excess usage spend-cap or entitlement holds the AWS ownership state Use AWS Marketplace to change or end the subscription. Nitrosend does not ask for a card for an AWS-owned plan. If AWS changes or ends the entitlement, Nitrosend verifies the current license state before changing access. Support Use Nitrosend support for account, sending and usage questions. Use AWS Marketplace for agreement, invoice and payment administration. ### Email providers (BYO) Path: `/integrations/email-providers` Bring your own email provider — send Nitrosend campaigns, flows, and transactional email through SendGrid, Postmark, Resend, Mailgun, or Amazon SES By default, Nitrosend sends your email through its managed infrastructure — no setup required. On paid plans you can instead bring your own provider (BYO): Nitrosend keeps handling composition, audiences, automation, tracking, and suppression, while your own provider account transports the mail. Supported providers Provider Credentials needed SendGrid API key Postmark Server token + account token Resend API key Mailgun API key + sending domain Amazon SES Access key ID + secret access key + region Info BYO sending keys are available on paid plans. On the Free plan you can review the provider screens, but credentials cannot be saved. Two ways to connect Dashboard: go to Integrations , choose your provider, and enter the credentials. Nitrosend runs a connection test when you save a new integration, and you can re-run Test connection from the integration's page at any time. MCP (agents): call nitro_configure_providers with operation: "configure" . This stores the credentials — it does not test them. To test credentials against the provider, run Test connection in the dashboard or call POST /v1/my/integrations/{id}/test via the REST API. Copy { "operation" : "status" } operation: "status" reports the stored configuration of the brand's active (primary) provider — plan gating, domain verification, and sending mode. It does not contact the provider, verify connection health, and it won't show a newly configured provider until that provider is set as primary. Credentials are stored encrypted and are never returned in tool responses or API payloads. Configured, connected, usable Configured — credentials are saved on the brand's active (primary) integration. Reported by operation: "status" . Connected — the most recent connection test passed. Shown on the integration's page in the dashboard (and returned by the REST test endpoint). Usable — your plan permits BYO sending and nothing blocks it. Reported by operation: "status" . It does not check domain verification or connection health. Making it the active provider Your first BYO integration automatically becomes the brand's active email provider. If you connect a second provider (or reconnect after switching), it does not take over automatically — open the integration's page and choose Set as primary to switch sending to it. Sending domains under BYO Each BYO provider issues its own DNS records for domain authentication. Add and verify your sending domain from Domains in the dashboard or with the nitro_manage_domains MCP tool — the records returned come from your provider. Warning Changing or rotating a provider's credentials resets that integration's verified sending domains to pending. You'll need to re-verify them before sending resumes on those domains. Which provider? If you don't have an existing provider relationship, you don't need one. Each brand can explicitly prepare a reserved Nitrosend sender when it is ready to send. Capacity for that identity grows from its own matured clean delivered volume; it is not an age-based schedule. BYO is for teams that already have reputation, volume pricing, or compliance requirements tied to an existing provider account. ### SendGrid Path: `/integrations/sendgrid` Send Nitrosend email through your own SendGrid account — BYO SendGrid API key setup Use your SendGrid account as the sending provider for Nitrosend campaigns, flows, and transactional email. See Email providers for how BYO sending works, plan requirements, and status semantics. What you need A SendGrid API key. Use a Full Access key, or a restricted key with Mail Send , Sender Authentication , and Inbound Parse permissions — Nitrosend uses SendGrid's domain authentication and Inbound Parse APIs when you add a sending domain. Info A Mail Send-only key passes the connection test (Nitrosend validates keys against SendGrid's scopes endpoint), but it cannot complete sending-domain setup. Connect from the dashboard Go to Integrations in Nitrosend. Choose SendGrid . Paste your API key and save. Nitrosend tests the connection as part of saving a new integration. Connect via MCP Copy { "operation" : "configure" , "provider" : "sendgrid" , "api_key" : " " } Warning The MCP call stores the key without testing it. {"operation": "status"} reports stored configuration only — to actually test the credentials, run Test connection from the SendGrid integration page in the dashboard, or call POST /v1/my/integrations/{id}/test via the REST API. Making it the active provider If this is your first BYO provider, it becomes the brand's active email provider automatically. If you already had another provider connected, open the integration's page and choose Set as primary to switch sending to it. Sending domains After connecting, add your sending domain from Domains (or nitro_manage_domains ). The DNS records returned come from SendGrid's domain authentication. Troubleshooting "SendGrid API key is invalid" — the key was rejected by SendGrid. Generate a new key from Settings → API Keys in SendGrid. Domain setup fails after connecting — your key is missing the Sender Authentication or Inbound Parse permission. Use a Full Access key or add those scopes. Rotated your key? Re-entering credentials resets this integration's verified domains to pending — re-verify them from Domains . ### Postmark Path: `/integrations/postmark` Send Nitrosend email through your own Postmark account — BYO server token and account token setup Use your Postmark account as the sending provider for Nitrosend campaigns, flows, and transactional email. See Email providers for how BYO sending works, plan requirements, and status semantics. What you need Your Postmark server token (from the server's API Tokens tab). Your Postmark account token (from Account → API Tokens). Both are required: Nitrosend validates the account token against Postmark's domains API and the server token against the server API when it tests the connection. Connect from the dashboard Go to Integrations in Nitrosend. Choose Postmark . Enter the server token and account token, then save. Nitrosend tests the connection as part of saving a new integration. Connect via MCP The tool's generic credential arguments map to Postmark's two tokens: Copy { "operation" : "configure" , "provider" : "postmark" , "api_key" : " " , "api_secret" : " " } Warning The MCP call stores the tokens without testing them. {"operation": "status"} reports stored configuration only — to actually test the credentials, run Test connection from the Postmark integration page in the dashboard, or call POST /v1/my/integrations/{id}/test via the REST API. Making it the active provider If this is your first BYO provider, it becomes the brand's active email provider automatically. If you already had another provider connected, open the integration's page and choose Set as primary to switch sending to it. Sending domains After connecting, add your sending domain from Domains (or nitro_manage_domains ). The DNS records returned come from Postmark's domain verification. Troubleshooting "Postmark account token is invalid" — the account-level token was rejected; copy it from Account → API Tokens (not a server token). "Postmark server token is invalid" — the server token was rejected; copy it from the specific server's API Tokens tab. Rotated a token? Re-entering credentials resets this integration's verified domains to pending — re-verify them from Domains . ### Resend Path: `/integrations/resend` Send Nitrosend email through your own Resend account — BYO Resend API key setup Use your Resend account as the sending provider for Nitrosend campaigns, flows, and transactional email. See Email providers for how BYO sending works, plan requirements, and status semantics. What you need A Resend API key (from the Resend dashboard → API Keys). The key needs permission to read domains and send email — Nitrosend validates it against Resend's domains endpoint when testing the connection. Connect from the dashboard Go to Integrations in Nitrosend. Choose Resend . Paste your API key and save. Nitrosend tests the connection as part of saving a new integration. Connect via MCP Copy { "operation" : "configure" , "provider" : "resend" , "api_key" : " " } Warning The MCP call stores the key without testing it. {"operation": "status"} reports stored configuration only — to actually test the credentials, run Test connection from the Resend integration page in the dashboard, or call POST /v1/my/integrations/{id}/test via the REST API. Making it the active provider If this is your first BYO provider, it becomes the brand's active email provider automatically. If you already had another provider connected, open the integration's page and choose Set as primary to switch sending to it. Sending domains After connecting, add your sending domain from Domains (or nitro_manage_domains ). The DNS records returned come from Resend's domain verification. Troubleshooting "Resend API key is invalid" — the key was rejected by Resend. Generate a fresh key with full access (a send-only key can fail the domains check used by the connection test). Rotated your key? Re-entering credentials resets this integration's verified domains to pending — re-verify them from Domains . ### Mailgun Path: `/integrations/mailgun` Send Nitrosend email through your own Mailgun account — BYO Mailgun API key and sending domain setup Use your Mailgun account as the sending provider for Nitrosend campaigns, flows, and transactional email. See Email providers for how BYO sending works, plan requirements, and status semantics. What you need Your Mailgun API key (private API key from the Mailgun dashboard). Your Mailgun sending domain (the domain you send from in Mailgun, e.g. mail.yourdomain.com ). Nitrosend connects to Mailgun's US API endpoint and validates the key by listing your Mailgun domains when it tests the connection. Warning EU-hosted Mailgun accounts are not currently supported — connections use Mailgun's US endpoint. Connect from the dashboard Go to Integrations in Nitrosend. Choose Mailgun . Enter the API key and sending domain, then save. Nitrosend tests the connection as part of saving a new integration. Connect via MCP Info In the MCP tool, the region argument carries the Mailgun sending domain — not a geographic region. Copy { "operation" : "configure" , "provider" : "mailgun" , "api_key" : " " , "region" : "mail.yourdomain.com" } Warning The MCP call stores the credentials without testing them. {"operation": "status"} reports stored configuration only — to actually test the credentials, run Test connection from the Mailgun integration page in the dashboard, or call POST /v1/my/integrations/{id}/test via the REST API. Making it the active provider If this is your first BYO provider, it becomes the brand's active email provider automatically. If you already had another provider connected, open the integration's page and choose Set as primary to switch sending to it. Sending domains After connecting, add your sending domain from Domains (or nitro_manage_domains ). The DNS records returned come from Mailgun's domain verification. Troubleshooting Connection test fails — confirm you're using the private API key, that the key's account can list domains, and that your Mailgun account is US-hosted. Rotated your key? Re-entering credentials resets this integration's verified domains to pending — re-verify them from Domains . ### Amazon SES Path: `/integrations/ses` Send Nitrosend email through your own Amazon SES account — BYO AWS access keys and region setup Use your own Amazon SES account as the sending provider for Nitrosend campaigns, flows, and transactional email. See Email providers for how BYO sending works, plan requirements, and status semantics. Nitrosend sender vs BYO SES The default Nitrosend sender already runs on SES — you don't need this integration to use Nitrosend. BYO SES is for teams that want mail to flow through their own AWS account : your quotas, your reputation, your region. What you need An AWS IAM access key ID and secret access key . The AWS region your SES account sends from (e.g. us-east-1 ). Attach the AmazonSESFullAccess and AmazonSNSFullAccess managed policies to the IAM user — this is the documented, working setup. The connection test does more than read your send quota: it also provisions the integration's bounce and complaint feedback pipeline (an SNS topic and an SES configuration set, including reading, updating, and subscribing to the topic), so the IAM user needs SES and SNS management permissions from the very first Test connection . Adding a sending domain additionally creates and verifies SES identities. Warning Nitrosend does not publish a least-privilege IAM policy for BYO SES — the integration provisions and manages infrastructure across both SES and SNS, and a hand-trimmed policy will fail the connection test or domain setup in non-obvious ways. Use the managed policies above. Connect from the dashboard Go to Integrations in Nitrosend. Choose Amazon SES . Enter the access key ID, secret access key, and region, then save. Nitrosend tests the connection as part of saving a new integration. Connect via MCP The tool's generic credential arguments map to the AWS keypair: Copy { "operation" : "configure" , "provider" : "ses" , "api_key" : " " , "api_secret" : " " , "region" : "us-east-1" } Warning The MCP call stores the credentials without testing them. {"operation": "status"} reports stored configuration only — to actually test the credentials, run Test connection from the SES integration page in the dashboard, or call POST /v1/my/integrations/{id}/test via the REST API. Making it the active provider If this is your first BYO provider, it becomes the brand's active email provider automatically. If you already had another provider connected, open the integration's page and choose Set as primary to switch sending to it. Sending domains After connecting, add your sending domain from Domains (or nitro_manage_domains ). The DNS records returned come from SES identity verification in your AWS account. Troubleshooting Connection test fails — the test reads your send quota and provisions feedback infrastructure, so confirm the IAM user carries the policies above and the region matches where your SES identities live. New AWS accounts start in the SES sandbox with restricted sending; request production access in the SES console. Domain setup fails after connecting — the IAM policy is missing SES identity permissions (see above). Rotated your keys? Re-entering credentials resets this integration's verified domains to pending — re-verify them from Domains . ### Mailchimp Path: `/integrations/mailchimp` Import Mailchimp audiences into Nitrosend Connect Mailchimp to migrate your existing audiences and members into Nitrosend. What syncs The Mailchimp connector imports: Audiences as Nitrosend contact lists Audience members as contacts Email address, first name, last name, and common merge fields Audience membership Mailchimp source metadata on the contact Mailchimp campaign history, templates, and reports are not migrated. Consent handling Nitrosend preserves Mailchimp email consent conservatively: Mailchimp members with subscribed status are imported as subscribed to email. Members with unsubscribed , cleaned , pending , archived, or unknown status are imported without email opt-in. This lets you keep the contact record and source data without accidentally widening your sendable audience. Connect Mailchimp Go to Integrations in Nitrosend. Choose Mailchimp . Click Connect and complete the Mailchimp OAuth flow. Return to Nitrosend and watch the sync progress. The sync runs in the background. You can leave the page while it imports contacts and return to the integration detail page to check progress. After import After the sync completes: Review imported lists in Contacts > Lists ( app.nitrosend.com/my/contact-lists ). Use segments to build dynamic audiences from Mailchimp lists, tags, merge fields, and engagement data. Send only to contacts who are subscribed in Nitrosend. Warning Mailchimp sync is a contact and audience migration path. It does not automatically send campaigns or activate automations. ### Attio Path: `/integrations/attio` Sync Attio people and list membership into Nitrosend Connect Attio to keep people and list membership from your CRM synchronized with Nitrosend. What syncs The Attio connector imports: Attio people as Nitrosend contacts (email and phone where present) Attio list membership as Nitrosend list membership Attio source metadata on the contact Attio notes, tasks, deals, and custom objects are not imported. Consent handling Attio is a CRM, not a marketing-consent system, so Nitrosend imports Attio people conservatively: email and phone channels are unsubscribed by default . Keep the contact data without widening your sendable audience — opt contacts in through your own consent flow before sending marketing email. Connect Attio Go to Integrations in Nitrosend. Choose Attio . Click Connect and complete the Attio OAuth flow. Return to Nitrosend and run Sync contacts . The sync runs in the background — the sync progress tray keeps you updated anywhere in the dashboard, or return to the integration detail page for details. Run Sync contacts again any time to refresh. After import Review imported lists in Contacts > Lists ( app.nitrosend.com/my/contact-lists ). Use segments to build audiences from Attio lists and contact fields. Send only to contacts who are subscribed in Nitrosend. Warning Attio sync imports people and list membership. It does not send campaigns, activate automations, or write data back to Attio. ### HubSpot Path: `/integrations/hubspot` Sync HubSpot CRM contacts and lists into Nitrosend Connect HubSpot to keep CRM contacts and lists synchronized with Nitrosend. What syncs The HubSpot connector imports: HubSpot contacts as Nitrosend contacts HubSpot lists as Nitrosend list membership HubSpot source metadata on the contact HubSpot deals, companies, tickets, and marketing assets are not imported. Consent handling Nitrosend imports HubSpot contacts conservatively: the email channel is unsubscribed by default . Keep the CRM record without widening your sendable audience — opt contacts in through your own consent flow before sending marketing email. Connect HubSpot Go to Integrations in Nitrosend. Choose HubSpot . Click Connect and complete the HubSpot OAuth flow. Return to Nitrosend and run Sync contacts . The sync runs in the background — the sync progress tray keeps you updated anywhere in the dashboard, or return to the integration detail page for details. Run Sync contacts again any time to refresh. After import Review imported lists in Contacts > Lists ( app.nitrosend.com/my/contact-lists ). Use segments to build audiences from HubSpot lists and contact fields. Send only to contacts who are subscribed in Nitrosend. Warning HubSpot sync imports contacts and lists. It does not send campaigns, activate automations, or write data back to HubSpot. ### Shopify Path: `/integrations/shopify` Sync Shopify customers into Nitrosend and receive store commerce events for segments and flows Connect your Shopify store to import customers into Nitrosend and receive commerce activity you can use in segments and automation flows. Customer import The Shopify connector imports: Shopify customers as Nitrosend contacts (email and phone where present) Shopify source metadata on the contact Customers are imported as contacts only — Shopify sync does not create contact lists. Use segments to build audiences from imported customers and their activity. Consent handling Nitrosend honours the marketing consent recorded in Shopify: customers who opted in to email or SMS marketing in your store are imported as subscribed on that channel; everyone else is imported unsubscribed by default . Store event webhooks When you connect, Nitrosend automatically registers webhooks on your store for order creation and payment, refunds, product creation/updates/deletion, and customer creation/updates. Every webhook is verified against Shopify's HMAC signature before it is processed. That activity flows into your contacts' event history, where segments and flow triggers can use it. Nitrosend also handles Shopify's mandatory privacy webhooks (customer data requests and redaction). Storefront Web Pixel (optional) For storefront browsing activity, the integration page shows a read-only Web Pixel collector key . Use this public key as the collectorKey setting in the Shopify Web Pixel extension to send storefront events to Nitrosend. Connect Shopify Go to Integrations in Nitrosend. Choose Shopify and enter your store domain. Click Connect and complete the Shopify OAuth flow. Return to Nitrosend and run Sync customers . The sync runs in the background — the sync progress tray keeps you updated anywhere in the dashboard, or return to the integration detail page for details. Store webhooks keep commerce activity flowing after the initial sync. After import Review imported customers in Contacts . Use segments to build audiences from customer fields and commerce events. Build flows triggered by store activity. Send only to contacts who are subscribed in Nitrosend. Warning Shopify sync imports customers and receives store events. It does not migrate your Shopify email templates or discount codes, and it does not write data back to Shopify. ### Stripe Path: `/integrations/stripe` Bring Stripe customers, subscriptions and payments into Nitrosend for segments, personalisation and flows Install the Nitrosend app on your Stripe account to import customers with their subscription and payment history, and to trigger flows from billing events as they happen. This is your own Stripe account's data. It is separate from how Nitrosend bills you; nothing here touches your Nitrosend plan. What syncs The Stripe connector imports: Stripe customers as Nitrosend contacts (email and phone where present) Subscription facts on the contact: plan, status, MRR, renewal date, trial end, cancellation flag Paid invoices and refunds as orders, so revenue and lifetime value are counted once Stripe source metadata on the contact Customers are imported as contacts only. Use segments to build audiences from subscription status, plan, MRR and payment activity. Consent handling Stripe customers have not opted in to marketing email by installing an app, so they are imported unsubscribed by default . Opt contacts in through your own consent flow before sending marketing email; transactional and account email can use the facts straight away. Events and flow triggers Once connected, Stripe sends account events to Nitrosend. Each event is verified, matched to your connection and applied to the contact, and the following flow triggers fire: Trigger Stripe event Purchase invoice.paid Checkout checkout.session.completed Subscription started customer.subscription.created with status active or trialing Subscription canceled customer.subscription.deleted , or an update that turns on cancel at period end Trial ending customer.subscription.trial_will_end Payment failed invoice.payment_failed Every trigger carries event.source , event.stripe_event_id , event.stripe_event_type and event.customer_id ; subscription triggers add event.plan and event.currency , and a failed payment adds event.amount . A redelivered Stripe event never enrols a contact twice. Connect Stripe From Nitrosend: Go to Integrations in Nitrosend. Choose Stripe . Click Connect and approve the Nitrosend app on the Stripe account you want to import. Return to Nitrosend and run Sync customers . From the Stripe App Marketplace: Install Nitrosend from the listing. If the account email is new to Nitrosend, an account is created for you and you land in the dashboard signed in. If this Stripe account is already connected to a Nitrosend account, you land in that account. If your email already has a Nitrosend account that is not yet connected to this Stripe account, sign in and connect Stripe from Integrations . The app asks for read access to customers, subscriptions, invoices, charges, refunds, checkout sessions and events. Nitrosend never stores an API key you paste; the app token is refreshed on your behalf and only ever reads. The sync runs in the background. The sync progress tray keeps you updated anywhere in the dashboard, or return to the integration detail page for details. Run Sync customers again any time to refresh history; live events keep contacts current between syncs. Test mode Choose the test-mode install link to connect a Stripe test environment. A test connection is kept apart from a live one, so both can exist for the same Stripe account without mixing customers or revenue. Disconnect and reconnect Uninstalling the app from Stripe stops all reads immediately; Nitrosend keeps the imported contacts and history. Reconnecting from Integrations restores the same connection and its baseline, so nothing is counted twice. After import Review imported customers in Contacts . Use segments to build audiences from plan, status, MRR and payment activity. Build flows from the triggers above. Send only to contacts who are subscribed in Nitrosend. Warning Stripe sync imports customers and receives billing events. It does not charge customers, change subscriptions, or write data back to Stripe. ### Payments for agents Path: `/guides/agent-payments` Upgrade a Nitrosend plan or add prepaid balance through MCP, CLI, or REST with owner approval and verified completion An agent prepares the purchase. The account owner approves Stripe Checkout. Nitrosend activates the plan or credits the balance only after provider verification. Info The billing tool never pays. It returns an approval URL when a provider needs the account owner, then exposes a status operation that proves the outcome. Choose what you are buying Plans and prepaid balance solve different problems. Check this distinction before writing any payment code. Purchase What it changes Start with Complete when Plan The recurring Nitrosend subscription, allowances, and entitlements plans , then checkout Checkout status reports active Prepaid balance Funds explicitly metered services such as email validation Funding status, then add_funds Funding purchase status reports credited A paid plan does not create prepaid balance. Adding funds does not upgrade the plan. The lifecycle in one minute Copy Agent or app Nitrosend Stripe or provider Account owner | prepare intent | | | |-------------------->| create/reuse purchase | | | | | | | | | poll with ID | | | |-------------------->| read reconciled state | | | Keep the returned purchase ID. It is the stable handle for polling and recovery. The approval URL is temporary and sensitive. Before you start Call status first. It identifies the current plan, provider route, prepaid balance, purchase availability, and provider-safe next action. Use a billing-authorized account context. Account-scoped API keys can create purchases over MCP, CLI, and REST. External OAuth MCP sessions can inspect billing, but they do not carry the account principal required for a mutation. If OAuth returns ask_account_admin , an owner or admin must complete the action in Nitrosend or provide a scoped API-key workflow. Generate one stable idempotency key per purchase intent and persist it before the first mutation. Treat provider-managed routes as instructions, not failures. Shopify plan purchases can return a Shopify-hosted approval URL; Vercel accounts may return a marketplace management action. Upgrade a plan List the available plans, create or reuse one purchase intent, open the returned approval URL when present, then poll the returned purchase_id . MCP CLI REST Use an account-scoped API key for checkout . OAuth MCP sessions can run status and plans , then hand the mutation to an account owner or admin. Start with account and provider state: Copy { "operation" : "status" } List plans and select the returned plan_id : Copy { "operation" : "plans" } Start checkout with a key that survives retries: Copy { "operation" : "checkout" , "params" : { "plan_id" : 2 }, "idempotency_key" : "plan-pro-acme-2026-08-31" } A change to an active provider subscription can return confirmation_required . Ask the operator, then retry the unchanged intent with the same key: Copy { "operation" : "checkout" , "params" : { "plan_id" : 2 , "confirm" : true }, "idempotency_key" : "plan-pro-acme-2026-08-31" } Poll using the purchase ID from the checkout response: Copy { "operation" : "checkout_status" , "params" : { "purchase_id" : 481 } } The Nitrosend CLI calls the same MCP tool directly: Copy nitrosend mcp tools call nitro_manage_billing --machine \ --args '{"operation":"status"}' nitrosend mcp tools call nitro_manage_billing --machine \ --args '{"operation":"plans"}' nitrosend mcp tools call nitro_manage_billing --machine \ --args '{"operation":"checkout","params":{"plan_id":2},"idempotency_key":"plan-pro-acme-2026-08-31"}' nitrosend mcp tools call nitro_manage_billing --machine \ --args '{"operation":"checkout_status","params":{"purchase_id":481}}' The CLI does not create another payment layer. It prints the Nitrosend envelope, including the approval and next action, so the operator can open the hosted checkout and the runner can poll the same purchase. Start checkout with the idempotency key in the header: Copy curl -X POST https://api.nitrosend.com/v1/my/subscription/checkout \ -H "Authorization: Bearer $NITROSEND_API_KEY " \ -H "Content-Type: application/json" \ -H "Idempotency-Key: plan-pro-acme-2026-08-31" \ -d '{"plan_id":2}' Poll the returned purchase ID: Copy curl "https://api.nitrosend.com/v1/my/subscription/checkout_status?purchase_id=481" \ -H "Authorization: Bearer $NITROSEND_API_KEY " See the generated reference for plan checkout and checkout status . A response may have no checkout URL when the requested plan is already current, the provider applies the change directly, or next_action points to marketplace management. Shopify plan purchases can return a Shopify-hosted approval URL. Follow approval , billing_route , manage_url , and next_action rather than assuming every plan change opens Stripe. Prove the plan change The terminal proof is Nitrosend reporting the subscription as active on the requested plan. The browser success page is useful feedback, but it is not the authority. Continue polling checkout_status through delayed webhooks or provider readback. Add prepaid funds Read funding status before presenting an amount. It returns the currency, available and reserved balance, allowed amount range, provider route, and whether purchase is currently available. MCP CLI REST Use an account-scoped API key for add_funds . OAuth MCP sessions can read funding status, then hand the mutation to an account owner or admin. Read the current balance and purchase capability: Copy { "operation" : "status" } Create or reuse the add-funds intent: Copy { "operation" : "add_funds" , "params" : { "amount_cents" : 5000 , "currency" : "USD" }, "idempotency_key" : "fund-validation-acme-2026-08-31" } Poll the returned funding purchase ID: Copy { "operation" : "funding_purchase_status" , "params" : { "purchase_id" : 812 } } These are direct Nitrosend MCP calls: Copy nitrosend mcp tools call nitro_manage_billing --machine \ --args '{"operation":"status"}' nitrosend mcp tools call nitro_manage_billing --machine \ --args '{"operation":"add_funds","params":{"amount_cents":5000,"currency":"USD"},"idempotency_key":"fund-validation-acme-2026-08-31"}' nitrosend mcp tools call nitro_manage_billing --machine \ --args '{"operation":"funding_purchase_status","params":{"purchase_id":812}}' Inspect the balance and policy: Copy curl https://api.nitrosend.com/v1/my/billing/funding \ -H "Authorization: Bearer $NITROSEND_API_KEY " Create the purchase: Copy curl -X POST https://api.nitrosend.com/v1/my/billing/funding/purchases \ -H "Authorization: Bearer $NITROSEND_API_KEY " \ -H "Content-Type: application/json" \ -H "Idempotency-Key: fund-validation-acme-2026-08-31" \ -d '{"amount_cents":5000,"currency":"USD"}' Poll the returned purchase: Copy curl https://api.nitrosend.com/v1/my/billing/funding/purchases/812 \ -H "Authorization: Bearer $NITROSEND_API_KEY " See the generated reference for funding status , creating a funding purchase , and funding purchase status . Prove the credit The purchase is complete when its customer-visible status is credited and the funding projection includes the credited amount. Funding status reads the reconciled Nitrosend ledger; provider webhooks and the funding reconciliation worker move a verified purchase into that ledger. A pending or otherwise non-terminal purchase still needs reconciliation, so do not start another purchase with a new key while the first outcome is unknown. Checkout, Link, Payment Links, and Stripe CLI These Stripe names refer to different surfaces: Term Role in this flow Stripe Checkout Session The hosted, single purchase approval session created by Nitrosend Link A Stripe wallet that may appear as a payment method inside Checkout when the account and customer are eligible Stripe Payment Links Stripe's reusable link product; Nitrosend plan and funding purchases do not use it as their lifecycle owner Stripe CLI A development tool for forwarding and inspecting test webhooks; it cannot approve a Checkout Session or replace the browser Link flow Link requires no separate Nitrosend operation. Open the checkout_url , then choose Link if Stripe offers it. The Nitrosend API, MCP tool, and CLI all create the same provider checkout. Retry and recovery State or error Safe response confirmation_required Ask the operator, then retry the same plan, key, and request with confirm: true ask_account_admin Stop the OAuth mutation; an owner/admin must act in Nitrosend or provide an account-scoped API-key workflow Open plan purchase Poll the existing purchase; do not choose another plan yet Funding purchase capability is unavailable Follow the provider-safe next_action returned by funding status pending Poll with bounded backoff; keep the original purchase ID and idempotency key Expired or terminal failed checkout Start a new intent with a new idempotency key partially_reversed or reversed Do not replay the purchase; trust the current funding balance and returned recovery action idempotency_conflict Restore the original request or create a new key for the changed intent Provider-managed route Open the returned marketplace management surface A stable key identifies intent, not an attempt. Network timeouts and process restarts do not justify a new key. Production checklist Persist the intent, idempotency key, purchase ID, requested plan or amount, and terminal status. Keep approval URLs out of routine logs and analytics. Allow only trusted https approval URLs returned by Nitrosend. Poll with bounded backoff and resume after process restarts. Branch on provider route and next action instead of assuming Stripe. Mark internal work complete only after Nitrosend reports active or credited . For the state and authority model, read Purchase lifecycle . For exact fields, use the MCP tools reference and generated API reference . ### Example Workflows Path: `/guides/mcp-examples` Complete prompt-to-result examples showing how AI tools use Nitrosend via MCP These examples show the full loop: what you say, what your AI calls, and what comes back. Every tool call uses real parameter names and realistic response data. Tip These examples work with any MCP-connected AI tool — Claude, ChatGPT, Codex, Gemini, Cursor, and more. The tool calls are identical across all clients. Example 1: Check account health You say: What's my Nitrosend account status? Is anything blocking me from sending? AI calls nitro_get_status (no parameters): Copy {} Response: Copy { "result" : { "status" : "needs_attention" , "current_brand" : { "sid" : "8f1e2d3c4b5a69788796a5b4" , "name" : "Acme Co" , "company_name" : "Acme Co" , "context_source" : "oauth_selection" }, "available_brands" : { "items" : [ { "sid" : "8f1e2d3c4b5a69788796a5b4" , "name" : "Acme Co" , "is_current" : true }, { "sid" : "1a2b3c4d5e6f708192837465" , "name" : "Acme Outlet" } ], "returned" : 2 , "limit" : 25 }, "available_brands_truncated" : false , "account" : { "tier" : "free" }, "brand" : { "sid" : "8f1e2d3c4b5a69788796a5b4" , "name" : "Acme Co" , "setup_complete" : false , "company_name" : "Acme Co" , "can_send" : false , "domain_verified" : false , "contact_count" : 45 , "flow_count" : 1 , "campaign_count" : 2 }, "brand_subdomain" : { "namespace_status" : "active" , "status" : "not_materialized" , "ready" : false , "preparation_required" : true , "fqdn" : "acme-co.nitrosend.net" , "apex" : "nitrosend.net" , "local_part" : "hello" , "local_part_editable" : false , "fqdn_changeable" : false }, "billing" : { "tier" : "free" , "resources" : { "email" : { "used" : 12 , "allowance" : 500 , "mode" : "monthly" }, "sms" : { "used" : 0 , "allowance" : 50 , "mode" : "lifetime" }, "ai" : { "used" : 3 , "allowance" : 20 , "mode" : "monthly" } } }, "delivery" : { "assessment_scope" : "account_capacity" , "admission_status" : "denied" , "commercial_capacity" : { "status" : "known" , "limit" : 50 , "reserved" : 0 , "accepted" : 12 , "provider_unknown" : 0 , "remaining" : 38 }, "pacing_state" : { "status" : "ready" , "policy_version" : "delivery-pacing-v3" , "scopes" : [] }, "blocking_control" : "sender_identity" , "reason_code" : "sender_not_ready" }, "issues" : [ { "area" : "domain" , "severity" : "high" , "message" : "No ready sending identity. Activate your reserved Nitrosend brand subdomain, or verify a custom domain." , "brand_subdomain_status" : "not_materialized" , "brand_subdomain_preparation_required" : true , "blocks" : "campaign approval and live sends" } ], "recommendations" : [ { "action" : "Set physical address" , "tool" : "nitro_set_brand_kit" }, { "action" : "Activate or verify a sending identity" , "tool" : "nitro_manage_domains" } ] }, "meta" : { "tool" : "nitro_get_status" , "current_brand" : { "sid" : "8f1e2d3c4b5a69788796a5b4" , "name" : "Acme Co" } } } What happens next: The AI reads the structured identity state and asks to activate the permanently reserved sender or connect a customer-owned domain. For the Nitrosend path it calls nitro_manage_domains with operation: "prepare_brand_subdomain" . That operation materializes the local identity synchronously and selects it only when no sender is already selected. If another sender remains selected, the agent then calls operation: "select_brand_subdomain" explicitly. Activation retains no email and performs no provider work. For accounts with multiple brands, switch context before making changes: Copy { "brand_sid" : "1a2b3c4d5e6f708192837465" } The AI calls nitro_select_brand with that SID. Future OAuth MCP tool calls use that brand. API key connections are pinned to the API key's brand and cannot switch. Example 2: Create and send an email campaign You say: Create a spring sale campaign for my Newsletter list with a hero banner, sale announcement, and a Shop Now button. Step 1 — AI calls nitro_compose_campaign : Copy { "name" : "Spring Sale 2026" , "subject" : "Spring Sale — 30% off everything" , "sections" : [ { "type" : "header" , "props" : {} }, { "type" : "hero" , "props" : { "image_url" : "https://images.unsplash.com/photo-spring-flowers" , "title" : "Spring Sale" , "subtitle" : "30% off everything this week" , "cta_text" : "Shop Now" , "cta_url" : "https://store.example.com/sale" } }, { "type" : "text" , "props" : { "content" : " Hi {{ contact.first_name }}, Our biggest sale of the season is here. Everything in the store is 30% off through Sunday. " } }, { "type" : "button" , "props" : { "text" : "Shop the Sale" , "href" : "https://store.example.com/sale" } }, { "type" : "footer" , "props" : {} } ], "audience" : { "audience_type" : "lists" , "contact_list_id" : 42 } } Response: Copy { "result" : { "campaign_id" : 7 , "template_id" : 15 , "flow_id" : 22 , "status" : "draft" , "audience_count" : 1250 } } 1 Preview AI calls nitro_review_delivery with {"target_type": "campaign", "target_id": 7} and shares the preview URL so you can review the email visually. 2 Test If you want an inbox check first, AI calls nitro_send_test_message with {"target_type": "campaign", "target_id": 7, "to": ["you@example.com"]} . This sends only to the test recipient, not the campaign audience. 3 You confirm You say "Looks good, send it." 4 Approve and send AI calls nitro_control_delivery twice: Copy { "target_type" : "campaign" , "target_id" : 7 , "operation" : "approve" } Then: Copy { "target_type" : "campaign" , "target_id" : 7 , "operation" : "live" } Campaign goes live and starts sending to 1,250 contacts. If the campaign audience is all_contacts , the final call must include confirm_send_to_all : Copy { "target_type" : "campaign" , "target_id" : 7 , "operation" : "live" , "confirm_send_to_all" : true } Approval, Safe Mode, and confirm_send_to_all are separate checks. Approval verifies content and preflight readiness. Under Safe Mode an API-key email campaign send waits for the account owner to approve its exact content and audience, and API-key flow and SMS live/schedule calls are blocked. confirm_send_to_all only confirms that the broad all-subscribed-contacts audience is intentional. Example 3: Build a welcome automation flow You say: Set up a welcome series: when someone joins my Subscribers list, send a welcome email immediately, wait 3 days, then send a follow-up with tips. AI calls nitro_compose_flow : Copy { "name" : "Welcome Series" , "trigger" : { "event" : "list_add" , "contact_list_id" : 5 }, "steps" : [ { "type" : "email" , "subject" : "Welcome aboard!" , "design" : { "sections" : [ { "type" : "header" , "props" : {} }, { "type" : "text" , "props" : { "content" : " Welcome, {{ contact.first_name }}! We're excited to have you. Here's what to expect from us. " } }, { "type" : "button" , "props" : { "text" : "Get Started" , "href" : "https://example.com/start" } }, { "type" : "footer" , "props" : {} } ] } }, { "type" : "wait" , "duration" : 259200 }, { "type" : "email" , "subject" : "3 tips to get the most out of Example" , "design" : { "sections" : [ { "type" : "header" , "props" : {} }, { "type" : "text" , "props" : { "content" : " Quick tips Here are 3 things our most successful users do in their first week: Complete your profile Invite a teammate Set up your first project " } }, { "type" : "footer" , "props" : {} } ] } } ] } Response: Copy { "result" : { "id" : 12 , "draft_revision_id" : 42 , "step_count" : 3 , "status" : "draft" , "trigger" : { "event" : "list_add" , "contact_list_id" : 5 } } } What happens next: The flow is in draft. AI copies the flow ID from result.id and the exact current revision from result.draft_revision_id in the persisted nitro_compose_flow response. It calls nitro_review_delivery with {"target_type": "flow", "target_id": 12, "revision_id": 42} . When review returns result.revision_id as 42 , AI copies that review-returned value into both control calls: {"target_type": "flow", "target_id": 12, "operation": "approve", "revision_id": 42} and then {"target_type": "flow", "target_id": 12, "operation": "live", "revision_id": 42} . From now on, every contact added to the Subscribers list automatically receives the welcome series. Example 4: Bulk-tag contacts You say: Tag contacts 101, 102, and 103 as vip and newsletter . AI calls nitro_manage_audience : Copy { "operation" : "bulk_tag" , "params" : { "contact_ids" : [ 101 , 102 , 103 ], "tags" : [ "vip" , "newsletter" ], "tag_action" : "add" } } Response: Copy { "result" : { "dry_run" : false , "updated" : 3 , "tags" : [ "vip" , "newsletter" ], "action" : "add" } } What happens next: Each contact's existing tags are merged with vip and newsletter (no duplicates). Use tag_action: "remove" to strip tags, or tag_action: "set" to replace each contact's tag list entirely. Pass dry_run: true to preview — the response then returns contact_count (how many would be affected) instead of updated , and persists nothing. Tip To tag by email instead of ID, first call nitro_search_contacts to resolve emails → contact IDs, then pass the IDs into bulk_tag . The tool intentionally requires contact_ids so an empty filter can't accidentally tag every contact in the brand. Buy a plan or add prepaid funds Plan purchase and prepaid funding share nitro_manage_billing , but they remain separate lifecycles. Start with status , ask the account owner to approve the exact plan or amount, and pass a stable idempotency_key when you create the purchase. If Nitrosend returns an approval URL, hand it to the owner and poll Nitrosend afterward. A plan is complete at active ; prepaid funding is complete at credited . Payments for agents has complete MCP, CLI, and REST flows. Purchase lifecycle explains approval, idempotency, provider routing, and completion proof. Use the MCP tools reference for the current request and response fields. What's next Connect your AI tool — setup guides for Claude, ChatGPT, Gemini, Cursor, and more Brand Kit & AI Memory — teach your AI your brand voice and strategy API Reference — full REST API documentation ### Sending Emails Path: `/guides/sending-emails` Design and send emails with Nitrosend Nitrosend supports two ways to send emails: campaigns for bulk sends to a list, and transactional messages for one-off sends triggered by your app. Campaigns Campaigns let you send an email to a contact list or segment. Create one from the dashboard: Go to Campaigns > New Campaign ( app.nitrosend.com/my/campaigns ) Choose a template or start from scratch Select your audience (a contact list or segment) Preview, test, and send Transactional messages For receipts, password resets, and other triggered emails, use the API: Copy curl -X POST https://api.nitrosend.com/v1/my/messages \ -H "Authorization: Bearer nskey_live_..." \ -H "Content-Type: application/json" \ -d '{ "channel": "email", "to": "user@example.com", "subject": "Your receipt", "html": " Thanks for your purchase ", "body": "Thanks for your purchase" }' Links in transactional messages go straight to their destination. Nitrosend never rewrites them for click tracking, because they often carry sign-in, reset, or confirmation tokens. Open tracking still follows the brand setting in Open and click tracking . Email editor The drag-and-drop editor lets you build responsive emails visually. Your brand theme (colors, fonts, logo) is applied automatically. All emails are rendered as MJML under the hood for maximum inbox compatibility. Sending activity The sending activity log shows individual sends across campaigns, flows, transactional messages, and tests. Use it to inspect delivery status, failures, and message details after a send. Use the suppressions API when you need the account-level block list and bounce diagnostic text behind failed recipients. GET /v1/my/suppressions returns the suppressed address, reason, source event reference, and bounded provider diagnostic when available. Open and click tracking Nitrosend measures opens with a small image in the HTML part of each email, and clicks by sending links through a tracking redirect. Both are on for every brand by default and feed the Opened and Clicked numbers in Activity and the engagement filters in segments. Turn either off for a brand under Brand > Sender > Engagement tracking ( app.nitrosend.com/my/brand/sender ), or set email_track_opens or email_track_clicks to false on PATCH /v1/my/brands/{sid} (see the API Reference ). The card shows the current setting, and GET /v1/my/brands/{sid} returns both fields. Open tracking off : emails carry no tracking image. Click tracking off : links point straight to their destination. Links in transactional messages never go through the redirect, whatever the setting. The unsubscribe link is never rewritten. The change applies to emails rendered after you save it. A campaign already sending switches over from its next batch. Emails already delivered keep their image and links, and their links redirect as before, but opens and clicks from them are no longer recorded. Opens and clicks recorded before you turned tracking off stay in Activity and segments. To delete them, email contact@nitrosend.com : support deletes the recorded events for the tracking you turned off, segment engagement is rebuilt without them, and aggregate counts stay. View in browser Emails sent from Nitrosend can include a view-in-browser snapshot. This gives recipients a stable web version of the email and gives your team a reliable way to inspect what was sent after delivery. ### Sending Domains Path: `/guides/sending-domains` Prepare your reserved Nitrosend sender or connect a customer-owned sending subdomain After Brand Kit setup, every brand permanently reserves one protected address: hello@ .nitrosend.net . The slug comes from the confirmed company name, never the account owner's name or a placeholder. Reservation creates no Domain or sending-identity row for a dormant account. Activation—or the first eligible API/MCP send—materializes those local rows synchronously under one already-verified shared SES root. There is no per-brand DNS, DKIM, provider identity, tenant resource, background job, polling state, or .com fallback. You can instead add a sending domain you own. Your email then goes out from your address on Nitrosend's infrastructure, and the reputation you build is portable with your domain. There's no separate email provider to wire up. Activate the Nitrosend sender Go to Settings → Domains ( app.nitrosend.com/my/settings/domains ). Review the reserved .nitrosend.net address and choose Activate sender . Nitrosend creates the local logical identity immediately. If the brand has no selected sender, that same transaction selects it; an existing custom sender remains selected until you explicitly replace it. Choose the visible local part, such as hello , whenever you select this hosted identity. Activation never queues or sends email and never calls a DNS or email provider. The reserved FQDN cannot be renamed in this release. A Nitrosend brand-subdomain sender also does not consume your customer-domain allowance. Add a sending subdomain Go to Settings → Domains ( app.nitrosend.com/my/settings/domains ). Under Add a sending domain , enter a subdomain you own — for example send.yourdomain.com . Nitrosend generates the DNS records for that subdomain and shows them on screen. Add those records at your DNS provider (see below). Nitrosend verifies them automatically — once they resolve, the domain flips to Verified and becomes available as a send-from address. Use a subdomain, not your primary domain Warning Always send from a subdomain ( send.yourdomain.com ), never your bare apex / primary domain ( yourdomain.com ). A subdomain isolates your marketing and product sending — its DNS records and its sending reputation — from the domain that runs your everyday inbox (for example Google Workspace or Gmail on yourdomain.com ). That matters for two reasons: It keeps your corporate mail untouched. Your primary domain already has mail authentication set up for your team's inboxes. Sending bulk email from it can interfere with that setup — a subdomain leaves it alone. It protects your reputation. Sending volume builds a reputation with inbox providers. Keeping it on a dedicated subdomain means a bad send never drags down the domain your team emails from. This is the setup inbox providers like Gmail expect from senders. Add the DNS records The Domains screen lists the exact records for your subdomain: typically a verification record, SPF , DKIM , and a return-path / tracking record. Add each one at your DNS provider (Cloudflare, GoDaddy, Namecheap, Route 53, and so on). Enter each record's Host in your provider's Host or Name field, with the type and value as shown. Most providers add your domain to that field themselves, so send becomes send.yourdomain.com , and @ means the domain itself. Some providers, such as Cloudflare, accept the full name too. Info DNS changes can take anywhere from a few minutes to a couple of days to propagate. Nitrosend re-checks automatically — you don't need to trigger verification manually. Route different mail through different subdomains You can verify more than one subdomain and push different email through each by changing the send-from address: hello@news.yourdomain.com — marketing campaigns hello@txn.yourdomain.com — transactional / product email Splitting sending this way isolates reputation between mail types, so a large promotional send can't drag down your receipts and password resets. This is mostly useful for large-volume senders — most brands are perfectly fine with a single subdomain. Set a reply-to address Your send-from address doesn't have to be where replies land. Set a Reply-To under Brand → Sender ( app.nitrosend.com/my/brand/sender ) and replies go to whatever inbox you like — independent of the subdomain you send from. For example, send from news@send.yourdomain.com but have replies arrive at hello@yourdomain.com . The Reply-To can be any mailbox you actually monitor, on any domain. How sending capacity grows Sending allowance depends on your plan and sender standing: Probation, Standard or Trusted. Standing ranks attributable delivery history, positive current list verification, domain strength and account signals together. Strong evidence can earn a higher standing on day one; payment alone cannot. Trusted receives the full plan allowance immediately, without a second volume ramp. See Pricing for the plan-and-standing allowances. Each effective sending identity still owns its own delivery and reputation history. History is not copied between domains, and changing sender context does not copy its first-send content clearance. Probation continues to require review of each changed content identity; higher standing does not bypass sender authentication, suppression or safety checks. The account's fixed plan/standing allowance, provider limits, and delivery pacing are separate inputs to one admission decision. The API, MCP status, and review responses report the binding result; clients should not calculate a private limit from plan names. ### Managing Contacts Path: `/guides/managing-contacts` Import, organize, and segment your audience Contacts are the people you send messages to. Each contact can have an email address, phone number, or both. Importing contacts Go to Contacts > Import ( app.nitrosend.com/my/contacts/import ) to upload a CSV file. Map your columns to contact fields during import. Imports run in the background and show live progress while rows are processed. You can leave the page and return to the progress tray later. You can also connect Mailchimp from Integrations to migrate audiences and members directly into Nitrosend. Mailchimp contacts keep their source metadata, and only Mailchimp members with subscribed status are opted in for email in Nitrosend. You can also add contacts via the API: Copy curl -X POST https://api.nitrosend.com/v1/my/contacts \ -H "Authorization: Bearer nskey_live_..." \ -H "Content-Type: application/json" \ -d '{ "email": "jane@example.com", "first_name": "Jane", "last_name": "Doe" }' Contact lists Organize contacts into lists for targeted sending. A contact can belong to multiple lists. Saved views Use saved views to keep useful contact-table setups: search terms, filters, visible columns, and sort order. Saved views are helpful for day-to-day operations such as "recent imports", "VIPs with no recent clicks", or "contacts needing cleanup". If the same rules should become an audience for campaigns or flow conditions, create a segment instead. Tags Tags are free-form string labels (e.g. vip , newsletter , trial ) you attach to contacts for segmentation and targeting. A contact can have any number of tags. Tags are stored as an array of strings under the reserved tags key on each contact's data object. Any tag string you use with a contact — via the REST API, the MCP tool, or the dashboard — is created automatically on first use. You can manage tags programmatically in two ways: Via the REST API Set the full list of tags on a contact by writing to data.tags . You can do this at creation time in the POST /v1/my/contacts body, or on an existing contact with PATCH : Copy curl -X PATCH https://api.nitrosend.com/v1/my/contacts/42 \ -H "Authorization: Bearer nskey_live_..." \ -H "Content-Type: application/json" \ -d '{ "data": { "tags": ["vip", "newsletter"] } }' Filter contacts by a single tag with the tag query parameter: Copy curl -H "Authorization: Bearer nskey_live_..." \ "https://api.nitrosend.com/v1/my/contacts?tag=vip" Warning PATCHing the data field replaces the entire data object , not just the keys you send. If the contact has other custom fields (e.g. data.plan , data.apollo ), sending { "data": { "tags": [...] } } will wipe them. To add or remove tags safely via REST, GET /v1/my/contacts/{id} first, merge your changes into the existing data hash, then PATCH the full merged object back. For add/remove semantics across many contacts in one call — without the read-modify-write dance — use the MCP tool below. Via the MCP tool (agents) When an AI agent is managing contacts on a user's behalf, use the nitro_manage_audience tool with the bulk_tag operation. It supports add , remove , and set semantics across many contacts in one call — no read-modify-write dance. Copy { "operation" : "bulk_tag" , "params" : { "contact_ids" : [ 1 , 2 , 3 ], "tags" : [ "vip" , "newsletter" ], "tag_action" : "add" } } tag_action: "add" (default) — merges the given tags into each contact's existing tags, deduplicated. tag_action: "remove" — removes the given tags from each contact; leaves other tags untouched. tag_action: "set" — replaces the entire tag list on each contact with the given tags. contact_ids is required — omit it and the call fails rather than silently tagging every contact in the brand. Pass dry_run: true alongside to preview the affected contact_count without persisting. See Example Workflows for a full prompt-to-result walkthrough. Segments Segments are dynamic groups defined by conditions (e.g., "subscribed in the last 30 days" or "opened an email this week"). Unlike lists, segments update automatically as contacts match or stop matching the criteria. The segment builder supports profile, list membership, tag, custom-field, engagement, deliverability, and event-history filters. Tags are one of the available segment filters — use the contact_tag filter name with the cont (contains) predicate to build tag-based segments, e.g. { name: "contact_tag", predicate: "cont", value: "vip" } . Contact activity Open a contact to review their activity timeline. The timeline brings sends, opens, clicks, unsubscribes, bounces, complaints, list changes, and custom events together so you can understand recent behavior before sending again. ### Website Signup Forms Path: `/guides/website-signup-forms` Collect signups from your website straight into a list, with no server and no exposed secret key Nitrosend has a built-in website signup form. You copy a snippet, paste it on any website (including no-code site builders like Bandzoogle, Squarespace, or Webflow), and visitor signups land directly on a Nitrosend list. Because the list membership fires your list_add flows, a new signup can enter a welcome sequence automatically. There is no server to build and no secret key to expose. The form authenticates with a public key that is designed to live in your website source. Info This is the officially supported way to collect website signups. You do not need Zapier, a serverless proxy, or a third-party form tool. Where to find it Go to Contacts and open the list you want signups to land on (for example, Newsletter ). Edit the list. Below the contact toolbar there is a Website signup form panel. On first use the panel auto-creates a public key named Website Forms for the brand and reveals it, so the snippet works immediately. Public keys vs secret keys Nitrosend has two kinds of API key. Using the right one is the whole safety story for embedding a form. Key Prefix Where it belongs What it can do Public (website) key wpkey_live_… Safe to ship in website source Create contacts and add them to your lists, nothing else Secret key nskey_live_… Server-side only, never in a browser Full read/write access to your account Warning Never put a secret key ( nskey_live_… ) in website code. The signup endpoint rejects secret keys on purpose, and the drop-in script and browser SDK both refuse to send with one. Always use the public wpkey_live_… key from the Website signup form panel. Copy a snippet The panel offers three snippet styles. Pick whichever fits your site. HTML form + drop-in script A complete, ready-to-paste form. Good for a plain HTML block on a site builder. Copy form data-nitro-form data-list-id = "123" > label > Email input type = "email" name = "email" required > label > label > First name input type = "text" name = "first_name" > label > input type = "text" name = "company" tabindex = "-1" autocomplete = "off" style = "position:absolute;left:-9999px" aria-hidden = "true" > button type = "submit" > Subscribe button > p data-nitro-message hidden > p > form > script src = "https://api.nitrosend.com/nitrosend-forms.js" data-public-key = "wpkey_live_…" defer > script > Drop-in JS only Already have your own form markup? Add data-nitro-form and data-list-id="…" to it, then include just the script tag: Copy script src = "https://api.nitrosend.com/nitrosend-forms.js" data-public-key = "wpkey_live_…" defer > script > The script auto-binds any form on the page that carries data-nitro-form plus data-list-id . It collects the email , first_name , last_name , and phone inputs by name . Any input with data-nitro-field="key" is sent as custom data[key] . Browser SDK For React, Vue, or anywhere you bundle JavaScript, use @nitrosend/sdk/browser : Copy import { createNitrosendPublicClient } from "@nitrosend/sdk/browser" ; const nitro = createNitrosendPublicClient ({ publicKey: "wpkey_live_…" , }); await nitro . contacts . signup ({ listId: "123" , email , firstName , }); Restrict the form to your domain Use the Allowed origins field in the panel to lock a key to your own site. Enter a comma-separated list of origins, for example: Copy https://yourdomain.com, https://www.yourdomain.com Submissions from any other origin are rejected. Leave the field blank to accept signups from any site. You can change origins any time from the panel or the API key edit screen. Trigger a welcome sequence A signup adds the contact to the list you chose, which fires the same list_add flow trigger as any other membership change. To greet new subscribers automatically: Build a flow (see Building Flows ). Set its trigger to Add to list ( list_add ) filtered to your signup list. Set it live. Every website signup now enters that flow on its own. No extra wiring. Test it The panel has a Send a test submission button. It posts a real entry to the list exactly the way a visitor's browser would, so you can confirm the whole path end to end. Use a throwaway email to avoid polluting your list; the contact appears in the app within seconds. Spam protection Each generated form includes a hidden honeypot field. Bots that fill it are silently dropped, so no contact is created and no flow fires, while real submissions pass through untouched. The endpoint is also rate limited per IP and per key. Leave the hidden honeypot input in place; do not remove it. Reference Under the hood, forms post to POST /v1/public/contacts with the public key as a bearer token. email and list_id are required; first_name , last_name , phone , source , and data are optional. See the API Reference for the full schema and responses. ### Building Flows Path: `/guides/building-flows` Create automated workflows triggered by events Flows are automated sequences of actions triggered by events. Use them for welcome series, abandoned cart reminders, re-engagement campaigns, and more. How flows work A flow starts with a trigger — an event that kicks off the automation. Then it executes a series of steps : sending emails, waiting for a delay, splitting by condition, and more. Creating a flow Go to Flows > New Flow ( app.nitrosend.com/my/flows/new ) Choose a trigger (e.g., "Contact added to list", "Event received") Add steps using the visual flow editor Activate the flow Step types Send Email — send a template to the contact Send SMS — send a text message Wait — pause for a duration (e.g., 2 days) Condition — branch based on contact properties or behavior Update Contact — modify contact fields Testing Use the Test button to simulate a flow run with a test contact before activating. ### Creating Campaigns Path: `/guides/creating-campaigns` Send bulk email campaigns to your audience A campaign is a one-time email or SMS send to an explicit audience. Steps Choose a name — for internal tracking Select a template — pick an existing template or create one inline Pick your audience — choose contact lists, a segment, or All Contacts Preview and test — send a test email to yourself Send or schedule — send immediately or pick a date and time Audience options Audience Use when Contact lists You want to send to one or more curated lists. Lists can also be filtered by a segment. Segment You want to send to contacts matching saved rules, without choosing a list first. All Contacts You intentionally want to send to every subscribed contact in the brand. If no audience is selected, the campaign stays a draft and delivery is blocked. All Contacts requires a confirmation before sending or scheduling. Campaign states State Description Draft Being edited, not yet sent Scheduled Queued for a future send time Sending Currently being delivered Sent Delivery complete Tracking After sending, view open rates, click rates, and bounce/unsubscribe metrics on the campaign detail page. Open and click tracking can be turned off per brand; see Open and click tracking . Iterate on a sent campaign Sent campaigns are locked — their content, audience, schedule, and template can't be changed so the record stays faithful to what went out. To send a variant, click Edit as new campaign on the locked banner (or Duplicate from the overflow menu on any row). You get a fresh draft that inherits the audience and email but resets the schedule and approval, ready for you to tweak and re-send. Renaming a sent campaign still works on any status — the name is internal metadata. See Campaigns → Edit lock and lifecycle for the full matrix. ### Using the Agent Inbox Path: `/guides/agent-inbox` Set up an inbox on your domain, triage the queue, reply, and connect your agent The agent inbox is in beta; request access from the Inbox page in your dashboard. This guide takes you from a verified domain to a working inbox your agent and your team answer together. For what the inbox is and how the queue works, see Agent Inbox . Create an inbox Verify your domain under Settings > Domains ( app.nitrosend.com/my/settings/domains ) if you haven't already. Add the receiving DNS records shown for your domain. Your existing mail keeps flowing to your current provider; Nitrosend only takes the addresses you claim. Create the inbox: choose the address (for example support@ ) and a display name. Once DNS verifies, mail to that address lands in your queue. Add aliases Open Inbox , click Manage inboxes , and choose your domain. Under Addresses , add an alias such as ask@ . Mail to the alias lands in the same queue, and replies go out from whichever address each customer wrote to. Make main sets the address new messages start from; the previous main address stays as an alias. Triage the queue Open Inbox in the dashboard. Tabs split the queue by state: Needs you , Agent ready , Handled , Quarantine , and All threads . The whole queue works from the keyboard: Key Action j / k Move down / up the list Enter Open the thread e Mark handled r Reply to the open thread c Compose a new message Esc Close the thread Each thread shows the conversation timeline, the item's state, and the available actions: Mark handled , Needs human , Release to agent , and Quarantine . Virus-flagged mail never offers release to agent. Compose a new message Click New message (or press c ). Choose the address to send from, add recipients (To, Cc, and Bcc all accept multiple addresses; paste a comma-separated list and it splits into recipients), write a subject and a plain-text body, and send. Cmd+Enter sends from the keyboard. Drafts survive closing the composer. If the recipient already has an open conversation with that inbox, your message continues the existing thread. View a message's source Hover any message in a thread and click the source icon to see the full stored headers, authentication results, and raw text and HTML content. Connect your agent Agents work the queue over MCP with two tools: Copy nitro_inbox list_queue | get_item | list_mailbox | get_thread | get_thread_page | get_message_body | validate_reply nitro_inbox_action send_reply | send_reply_test | mark_handled | request_human | release_to_agent | mark_quarantine | classify_spam A typical loop: list_queue with state: "agent_ready" , get_item for the thread, follow get_thread_page or get_message_body when the response provides those continuation calls, validate_reply to check the draft, send_reply_test to verify an explicit test recipient when needed, send_reply to answer, and request_human for anything the agent should not decide alone. An accepted send_reply marks its item handled on its own; use mark_handled for items you close without replying. list_queue and list_mailbox return compact rows by default (id, state, reason codes, sender, subject, timing) and take view: "full" for previews and contact context. The queue filters by state , inbox_id , reason_code , sender , query , since , and sort ; page 1 carries the state counts. Reads that will not reply can pass purpose: "read" to get_thread and skip the reply context. When a read returns a brand_context_ref , pass it on the next read so the brand packet is reused rather than re-sent. Sender rules ( set_sender_policy , or POST /v1/my/inboxes/:id/sender_policy ) take three decisions: allow skips the provider spam gate for that sender or domain, block quarantines it, and handle projects its mail straight to handled with the reason sender_auto_handled , which is how notification senders (CI, OTP mailers, marketplace bots) stay out of the queue for good. Like allow , handle overrides the provider spam verdict for that sender; a virus verdict or an inactive inbox still quarantines regardless of the rule. Separately, a Reply-To that differs from From but shares its registrable domain on an authenticated message is recorded as reply_to_mismatch_aligned and no longer sends ordinary list mail to a human; a cross-domain Reply-To still does. Triage verbs ( mark_handled , request_human , release_to_agent , mark_quarantine , classify_spam ) act on one item with action_item_id , on up to 100 with action_item_ids , or on a filter (the same keys as list_queue ) with expected_count set to the total the listing reported; a filter that selects more than 100 items is refused, so narrow it or work through it in pages. A batch returns updated ids with their new state and skipped ids with a reason, never item bodies; if the filtered selection changed since it was listed, the call is refused with the live count so nothing acts on mail you have not seen. REST API Everything the dashboard does is available over REST: Endpoint Purpose GET /v1/my/inbox_action_items List the queue ( state , inbox_id , reason_code , sender , query , since , sort filters) POST /v1/my/inbox_action_items/:id/act Apply a verb ( mark_handled , request_human , release_to_agent , mark_quarantine ) POST /v1/my/inbox_action_items/act Apply one verb to many items ( action_item_ids , or filter + expected_count ; max 100) GET /v1/my/conversations List threads ( mailbox=true , inbox_id , query ) GET /v1/my/conversations/:id Thread with messages POST /v1/my/conversations Compose a new message ( inbox_id , to[] , cc[] , bcc[] , subject , body ) POST /v1/my/conversations/:id/reply Reply to a thread GET /v1/my/conversations/:id/messages/:message_id/source Full headers and source for one message To have new mail pushed to your own system as it arrives, subscribe a webhook to email.received . Mutating requests take an Idempotency-Key header, so a retried request never sends twice: Copy curl -X POST https://api.nitrosend.com/v1/my/conversations \ -H "Authorization: Bearer nskey_live_..." \ -H "Idempotency-Key: 4f7c1e2a-..." \ -H "Content-Type: application/json" \ -d '{ "conversation": { "inbox_id": 12, "to": ["dana@example.com"], "subject": "Kicking off your onboarding", "body": "Hi Dana, welcome aboard. Want me to walk you through your first campaign this week?" } }' ### Running Outreach Path: `/guides/outreach` Search for leads, review and reveal them, activate a sequence, and handle replies Outreach is coming soon; register your interest from the Outreach page in your dashboard. This guide walks one outreach cycle end to end. For the model behind it, see Outreach . Run a search Open Outreach and start a search. Describe the people you want to reach: role, industry, company size, location. The search returns leads with a fit rating and company context. Searches are saved, so you can refine and re-run them as your targeting sharpens. Review and reveal leads The search detail view lists every lead with fit (high, medium, low), verification status, and sequence state. Filter by any of these to work the list down. Reveal a lead to get their verified email address. Revealing is deliberate rather than automatic: you spend reveals on the leads worth contacting, and every revealed address is verified before it can enter a sequence. Activate a sequence Select the leads you want to contact and activate them into a sequence. Sequences run on flows, so the emails, timing, and approval gates are the same ones you already use: design the touches once, and every activated lead moves through them on schedule. Leads that fail verification are held rather than sent, and the hold reason is shown on the lead. Handle replies When a lead replies, two things happen immediately: Their sequence stops. No further touches are sent. The reply lands in your agent inbox queue, where your agent or your team continues the exchange as a normal conversation. Watch the sequence states on the search detail view to see where every lead stands: ready, held, in sequence, completed, stopped, or converted. Keep it deliverable Outreach volume is paced deliberately. Verified addresses only, measured sending, and an immediate stop on reply keep your sending reputation intact while you scale the program. ### Glossary Path: `/concepts/glossary` What every word in the Nitrosend dashboard means, grouped by where you find it Nitrosend has its own vocabulary, and you meet most of it in the left sidebar. This page defines those words in the order you run into them, so you can look up a screen you are staring at rather than guess a search term. Each entry says what the thing is . Where there is more to know, it links to the page that covers the behaviour in depth. The direct link for every screen is listed in Dashboard URLs . If you drive Nitrosend through an AI agent, this page is also a translation table: the agent talks about campaigns, flows, lists and segments because those are the same objects you see on screen. The sidebar These are the top-level areas, in the order they appear. Term What it is Home The dashboard landing screen: recent sending, quick stats, and what needs attention. Setup A checklist that walks you through getting an account ready to send. It shows a percentage, moves down the sidebar as you progress, and disappears once complete. See Setup Center . Campaigns One-time sends to a chosen audience — a newsletter, an announcement, a reminder. More in Campaigns . Flows Automated sequences that run on a trigger rather than on a date, like a welcome series. More in Flows . Templates Reusable email designs you can start a campaign or flow step from. Carries a New chip. More in Templates . Inbox A shared mailbox where replies to your sending land and your AI agent can draft answers. Currently in beta, so the tab may show a Beta chip until it is switched on for your account. More in Agent Inbox . Outreach Finding new leads and sending them a sequence, as opposed to emailing people you already have. Marked Soon while it rolls out. More in Outreach . Activity The record of what actually went out and what happened to it. See Activity and the Send Log . Contacts Everyone you can email, plus the ways of grouping them. See Audiences . Clients For agencies managing separate accounts on behalf of their own customers. Only appears if your account is set up for managed clients. Brand Everything that defines who an email comes from and what it looks like. See Brand . Integrations Connections to other tools — your email provider, CRM, or store. See Integrations . Settings How the current brand is operated: Domains, API Keys, AI Memory and Channels. See Settings . Learning Center Guides and walkthroughs inside the app. Affiliates Your referral dashboard. Only appears if you are on the affiliate program. Brand switcher The brand selector in the sidebar footer changes which brand you are working in. Brand-scoped areas — including campaigns, contacts, templates and Settings — follow the brand you have selected. Setup Center The Setup entry, expanded. Five steps, tracked as a percentage: Set up your Brand Kit Verify your sending domain Choose your plan Connect your AI agent Import your subscribers Setup moves around on purpose: it sits above Home while you are getting started, drops to the bottom of the sidebar once the essentials are done, and disappears once everything is complete. Account, brand, plan Account is the thing that gets billed and that people log into. Brand sits inside an account: a separate sending identity with its own logo, colors, sending domain, contacts and campaigns. One account can hold several brands, which is how agencies keep clients apart. Your plan is shown as a label next to your name in the sidebar — Free , Pro , Ultra , or Enterprise . It governs your monthly email allowance and how many brands you can create. Audiences: contacts, lists, segments and saved views This is the vocabulary people mix up most, because there are three different ways to group the same contacts and only two of them can be sent to. Contact — one person you can email. Contacts carry an email address, optional phone, and any extra data you have imported. Contact list — a group you put people into explicitly. Membership is a decision: someone is on the list because they were imported into it, signed up to it, or was added by hand. The Contacts sub-navigation labels this tab Lists ; the page itself is titled Contact Lists . Same thing. Segment — a group defined by a rule rather than by membership, so it re-evaluates itself. "Everyone who opened something in the last 30 days" is a segment. Add a matching contact tomorrow and they are in it without you doing anything. More in Segments . Saved view — a filter on the contacts table , saved so you can come back to it. This is the one that catches people out: a saved view is a way of looking at your contacts, not an audience. You cannot send to a saved view. If you want the same rules to become something you can send to, make it a segment. More in Contacts . Tag — a free-form label you stick on contacts, like vip or july-workshop . Tags are a common ingredient in segment rules. Custom field — any extra column you brought in with your contacts, usable in segment rules and in personalization. Subscribed / unsubscribed — whether a contact has opted in to hear from you on a channel. Unsubscribed contacts stay in your account but drop out of sending. Suppressed — an address Nitrosend will not send to, regardless of lists or segments, because sending to it would damage your deliverability. See Deliverability words . Campaigns and flows A campaign goes out once, to an audience you choose. A flow runs continuously, starting for each contact when something triggers it. Both share the same status words: draft — still being written; nothing has gone out. scheduled — queued for a future date and time. Campaigns only. live — sending now, or in a flow's case, actively running for whoever triggers it. paused — stopped part-way; can be resumed. completed — finished sending. Campaigns only. cancelled — stopped and not resumable. archived — filed away, out of your normal lists. A flow you have edited but not yet published shows unpublished changes next to its status, meaning what is running is not what you are looking at. Go Live is the button that publishes and starts it. Campaign content locks once a campaign has sent, though you can always rename it. Campaigns covers exactly what stays editable at each status. Flow steps Inside a flow, each box on the canvas is a step , added from Add step . Steps are grouped under two headings, Actions (things that happen) and Logic (things that control what happens next). Actions: Step What it does Email Sends an email. SMS Sends a text message. Greyed out until the brand has a sending number. Subscribe Opts the contact in. Unsubscribe Opts the contact out. Webhook Calls an external URL from the flow. Not the same as event webhooks , which Settings manages. Find Email and Verify Email also appear in the palette, but they are signposts rather than steps you can add here: finding and checking addresses is lead-sourcing work, so clicking either card takes you to Outreach instead of dropping a step into the flow. Logic: Step What it does Trigger What starts the flow for a contact. Every flow begins with one. Wait Pauses before the next step. Split Branches down different paths depending on a condition. Emit Event Records an event you can trigger other things from. The email builder Emails are built from stacked blocks . The type of block decides what you can put in it, which matters because putting the wrong content in a block is a common way to get a design that will not save: The insert panel splits them into Message blocks, which carry your content, and Structure blocks, which shape the layout. Message: Block Holds Hero An opening block with a title, supporting copy and a main call to action. Text Words. Formatted body copy, not images. Image A picture. Images go here, not inside a text block. Button A standalone call to action. Product One item with its price, copy and link. Product Grid Several products together, with names and prices. Gallery A grid of images, for lookbooks or photo stories. Structure: Block Holds Header The masthead at the top, typically logo-first. 2 Columns Splits a row into two, the most reliable multi-column layout in email. 3 Columns Splits a row into three, for compact features or comparisons. Divider A visual break between sections. Spacer Blank vertical space. Social Icons linking to your social profiles. Footer The closing block with company details and the unsubscribe link. The builder header shows Saved or Unsaved so you can tell whether your latest edit has been stored. Merge field — a placeholder like a first name that fills in per recipient when the email is sent. Brand, Settings and Account These three areas separate what recipients see, how the current brand is operated, and how the account is administered. Brand: what recipients see The Brand area holds the identity and sender details recipients see. Its tabs: Brand Kit and Sender are the canonical Brand pages. You may also see Domains, API Keys and AI Memory in the Brand tab row as migration notices; those entries explain that the operational pages have moved to Settings and link to their new locations. Brand Kit — your logo, colors and voice. Contains Company (company name, description, and physical address , which is required in the footer of marketing email by law), Brand Identity (spacing, corner radius and type sizes that give your emails a consistent look), Brand Voice (how your AI agent should write for you), and Social Links . Sender — the Sender & Testing card, where three fields decide what recipients see: From name — the name in the inbox, e.g. your business name. From email — the address it comes from. Reply-to email — where replies go, if that differs from the From address. Settings: how the current brand is operated The Settings area is scoped to the current brand. Its tabs: Domains — the domains the current brand is allowed to send from, and their DNS setup. See below. ( app.nitrosend.com/my/settings/domains ) API Keys — credentials for connecting Nitrosend to your own code. Only shown if your role can manage them. ( app.nitrosend.com/my/settings/api-keys ) Webhooks : HTTPS endpoints of yours that receive signed events as the brand's transactional email is sent, delivered, bounced, opened and replied to, and as the account changes. Only shown if your role can manage API keys. See Webhooks . ( app.nitrosend.com/my/settings/webhooks ) AI Memory — a free-text note your AI agent reads every time it works on your account: preferences, house style, things to always or never do. ( app.nitrosend.com/my/settings/memory ) Channels — the current brand's SMS numbers. ( app.nitrosend.com/my/settings/channels ) The final Account entry links out of the brand-scoped Settings section. Account: workspace administration The Account area holds account profile details, Team and Billing. These settings apply to the workspace rather than only the current brand. ( app.nitrosend.com/my/account , /my/account/team , /my/account/billing ) Domain verification Before you can send from a domain you have to prove you own it, by adding DNS records at whoever hosts your domain. Nitrosend shows each required record and whether it is in place. A domain reads Verified once everything checks out, or Needs attention if something is missing or has changed. The records are grouped into families, named on screen as: Record Why it exists Verification Proves the domain is yours. SPF Lists who is allowed to send for your domain. DKIM Signs your mail so receivers can tell it was not tampered with. DMARC Tells receivers what to do with mail that fails the checks above. MX Routes mail addressed to the domain. Return-path Where bounce notifications go. Tracking Makes opens and clicks measurable on your own domain. Records prefixed From-domain (From-domain DKIM, From-domain DMARC, and so on) apply to the domain in your From address when that differs from the subdomain you send through. Activity and the Send Log Activity is the overview: how much went out and how it performed ( app.nitrosend.com/my/activity ). The Send Log is the itemised list, one row per message ( app.nitrosend.com/my/sends/log ). Sends are labelled by where they came from — Campaign , Flow , Test , or Transactional (triggered one-to-one mail like a receipt, rather than marketing). The headline numbers: Recipients — how many people a send went to. Delivered — accepted by the receiving mail server. Opened — the recipient opened it. Approximate by nature, since some mail apps block the measurement. Clicked — the recipient clicked a link. Deliverability words Bounce — the message could not be delivered. A permanent bounce means the address is bad; a temporary one may succeed later. Complaint — the recipient marked your email as spam. The most damaging outcome, and the reason lists should only contain people who asked to hear from you. Unsubscribe — the recipient opted out. Suppression — an address Nitrosend refuses to send to, usually after a hard bounce or a complaint. Suppression protects your sender reputation, so an address staying suppressed is the system working. Inbox The Agent Inbox sorts incoming replies into five tabs: Needs you — a human has to answer this. Agent ready — your AI agent has drafted a reply and is waiting for you to approve it. Handled — dealt with, by you or by the agent. Quarantine — held back as suspicious. All threads — everything, unfiltered. An individual item can also carry a Needs a human tag, marking that one message as something the agent should not answer on its own. Integrations An integration is a connection to another tool. They come in a few kinds: Email delivery — send through your own provider: Amazon SES, Mailgun, Postmark, Resend, SendGrid. Contact and customer sync — keep people in step with a CRM such as Attio or HubSpot. Audience migration — bring an existing audience across, for example from Mailchimp. Commerce events — pull orders and products from a store like Shopify. Working through an AI agent Nitrosend is built to be driven by an AI assistant as well as by hand, and both act on the same objects. When your agent says it is creating a campaign, adding contacts to a list, or defining a segment, it means exactly what those words mean on this page. Two terms belong to that surface specifically: MCP — the protocol your assistant uses to talk to Nitrosend. Setting it up is covered in Integrations . Brand Kit and AI Memory — what the agent reads to stay on-voice. See Brand Kit and memory . ### Dashboard URLs Path: `/concepts/dashboard-urls` Direct links for the pages of the Nitrosend dashboard, so you or your AI agent can jump straight to Settings, API Keys, Domains, Billing, Contacts and Campaigns The Nitrosend dashboard lives at https://app.nitrosend.com and every signed-in page sits under the /my/ prefix, for example https://app.nitrosend.com/my/settings/api-keys . A path without /my/ (such as /settings/api-keys ) does not exist and shows a "Page not found" screen. URLs are not account- or brand-scoped: the brand switcher in the sidebar footer decides which brand a page shows. Info For AI agents: cite dashboard links only from this page. If a page is not listed here, say the link is not documented rather than guessing a path. Pages marked gated only appear for accounts with that feature enabled. Settings Settings is scoped to the current brand. These are the pages behind the Settings > ... click-paths used throughout the docs. Click-path Page URL Settings > API Keys Create and manage nskey_live_... keys (shown when your role can manage keys) https://app.nitrosend.com/my/settings/api-keys Settings > API Keys > New Create a new API key https://app.nitrosend.com/my/settings/api-keys/new Settings > Webhooks Endpoints that receive signed transactional email events (shown when your role can manage API keys) https://app.nitrosend.com/my/settings/webhooks Settings > Webhooks > New Add a webhook endpoint https://app.nitrosend.com/my/settings/webhooks/new Settings > Domains Sending domains, the reserved Nitrosend sender, DNS records https://app.nitrosend.com/my/settings/domains Settings > AI Memory The memory document your AI agent reads https://app.nitrosend.com/my/settings/memory Settings > Channels SMS numbers for the current brand https://app.nitrosend.com/my/settings/channels Account Account settings apply to the whole workspace, not just the current brand. Click-path Page URL Account Profile and account details https://app.nitrosend.com/my/account Account > Team Invite and manage team members https://app.nitrosend.com/my/account/team Account > Billing Plan, invoices, prepaid balance https://app.nitrosend.com/my/account/billing Account > Billing > Plan Change or upgrade your plan https://app.nitrosend.com/my/account/billing/plan Account > Billing > Payment methods Cards on file https://app.nitrosend.com/my/account/billing/payment-methods Account > Billing > Add funds Top up prepaid balance https://app.nitrosend.com/my/account/billing/add-funds Brand Click-path Page URL Brand > Brand Kit Logo, colors, company details, brand voice https://app.nitrosend.com/my/brand Brand > Sender From name, from email, reply-to, test recipients https://app.nitrosend.com/my/brand/sender Sign in and sign up Page URL Create an account https://app.nitrosend.com/join Log in https://app.nitrosend.com/login Forgot password https://app.nitrosend.com/forgot-password Sidebar The top-level areas, in sidebar order. https://app.nitrosend.com/my on its own opens Home. Sidebar entry Page URL Home Dashboard landing screen https://app.nitrosend.com/my/dashboard Setup Setup Center checklist https://app.nitrosend.com/my/setup Campaigns One-time sends https://app.nitrosend.com/my/campaigns Flows Automated sequences https://app.nitrosend.com/my/flows Templates Reusable email designs https://app.nitrosend.com/my/templates Inbox Agent Inbox (beta) https://app.nitrosend.com/my/inbox Outreach Lead discovery and sequences ( gated ) https://app.nitrosend.com/my/outreach Activity Sending overview and performance https://app.nitrosend.com/my/activity Contacts Everyone you can email https://app.nitrosend.com/my/contacts Clients Managed client accounts for agencies ( gated ) https://app.nitrosend.com/my/clients Brand Brand Kit and sender identity https://app.nitrosend.com/my/brand Integrations Email providers, CRMs, stores https://app.nitrosend.com/my/integrations Settings Domains, API Keys, Webhooks, AI Memory, Channels (opens on Domains) https://app.nitrosend.com/my/settings Learning Center Guides inside the app https://app.nitrosend.com/my/learn Affiliates Referral dashboard ( gated ) https://app.nitrosend.com/my/affiliates Contacts Click-path Page URL Contacts Contacts table and saved views https://app.nitrosend.com/my/contacts Contacts > Add contact Create one contact by hand https://app.nitrosend.com/my/contacts/new Contacts > Import Upload a CSV https://app.nitrosend.com/my/contacts/import Contacts > Lists Contact lists https://app.nitrosend.com/my/contact-lists Contacts > Lists > New Create a contact list https://app.nitrosend.com/my/contact-lists/new Contacts > Segments Rule-based audiences https://app.nitrosend.com/my/segments Sending, activity and outreach Click-path Page URL Campaigns All campaigns; the New Campaign button lives here https://app.nitrosend.com/my/campaigns Flows > New Flow Start a new flow https://app.nitrosend.com/my/flows/new Activity Sending overview https://app.nitrosend.com/my/activity Activity > Send Log One row per message https://app.nitrosend.com/my/sends/log Activity > Email overview Email performance summary https://app.nitrosend.com/my/sends/overview Outreach > New Start a lead search ( gated ) https://app.nitrosend.com/my/outreach/new Outreach > Sequences Outreach sequences ( gated ) https://app.nitrosend.com/my/outreach/sequences Outreach > Replies Replies to outreach ( gated ) https://app.nitrosend.com/my/outreach/replies Integrations > Add Connect a provider, CRM or store https://app.nitrosend.com/my/integrations/new Clients > API Agency API access for managed clients ( gated ) https://app.nitrosend.com/my/clients/api Pages for a single record Replace {id} with the record's ID. Page URL pattern Campaign https://app.nitrosend.com/my/campaigns/{id} Flow https://app.nitrosend.com/my/flows/{id} Template editor https://app.nitrosend.com/my/templates/{id} Template preview https://app.nitrosend.com/my/templates/{id}/preview Contact https://app.nitrosend.com/my/contacts/{id} Edit contact https://app.nitrosend.com/my/contacts/{id}/edit Contact list https://app.nitrosend.com/my/contact-lists/{id} Edit contact list https://app.nitrosend.com/my/contact-lists/{id}/edit Integration https://app.nitrosend.com/my/integrations/{id} Inbox thread https://app.nitrosend.com/my/inbox/{id} Outreach campaign https://app.nitrosend.com/my/outreach/{id} API key https://app.nitrosend.com/my/settings/api-keys/{id} Webhook https://app.nitrosend.com/my/settings/webhooks/{id} Older links that still work Settings used to live under Brand. These paths redirect to the pages above, so a link saved before the move is not broken: /my/brand/api-keys , /my/brand/domains , /my/brand/memory , /my/brand/channels , /my/settings/account , /my/settings/team , /my/settings/billing , /billing and /campaigns/{id} . See also the Glossary : what every word in the dashboard means, grouped by where you find it. ### Purchase lifecycle Path: `/concepts/payment-lifecycle` The state, authority, idempotency, and provider invariants behind Nitrosend plan checkout and prepaid funding Payment is a state transition, not a URL. Nitrosend records purchase intent before provider work, exposes human approval when required, and reports success only after the provider outcome is reconciled into account state. Two purchase domains Domain Durable intent Account state changed Terminal success Plan purchase Requested plan plus idempotency claim Subscription, commercial tier, entitlements Subscription is active on the requested plan Prepaid funding Requested amount, currency, instrument, and idempotency claim Funding ledger and available balance Funding purchase is credited The domains share orchestration principles, not storage or balances. A plan purchase cannot satisfy a prepaid deficit, and a funding credit cannot activate subscription entitlements. State transitions Plan purchase Copy request | +--> confirmation_required -- operator confirms with same key --> request | +--> provider applies, schedules, or declines the change directly | +--> pending + approval URL | +--> active +--> expired +--> failed +--> provider outcome still pending A purchase can complete without hosted checkout when the existing billing provider applies or schedules the change directly. When checkout is hosted, the same pending subscription and purchase claim carry the provider session, expiration, and requested plan. Prepaid funding Copy requested -> checkout_created -> pending -> credited | | | | +--------------+---------------+--> failed +--> expired +--> partially_reversed --> reversed +-------------------------> reversed Customer-facing status collapses internal dispatch and uncertain provider work into a recoverable pending state. The service retains more precise evidence for reconciliation and audit. Authority boundaries Three authorities participate: The caller owns intent. It chooses a plan or amount and supplies a stable idempotency key. The account owner owns approval. Nitrosend returns a provider approval URL when money or an external subscription needs confirmation. The agent does not open, approve, or complete that step on the owner's behalf. Nitrosend owns product finality. Webhooks and provider readback are reconciled into the subscription or funding ledger. Consumers trust the resulting active or credited state. A browser redirect is not evidence of settlement. Webhook receipt alone is also not the consumer contract; the reconciled Nitrosend read model is. Idempotency boundary The idempotency key is bound to an account, operation, and request fingerprint. An exact retry returns or resumes the original purchase. Reusing the key with a changed plan, amount, currency, or instrument conflicts. A timeout does not justify a new key. A new key is appropriate only for a new intent or after an expired or terminal failed attempt that the next action explicitly permits replacing. The provider receives a derived idempotency identity from the same durable claim. This prevents a retry between local persistence and the provider response from creating parallel checkout sessions. Provider routing Billing route is account state, not a client preference. Route Plan behavior Prepaid behavior Stripe Hosted Checkout or an in-place subscription change Hosted funding checkout when purchase policy is available Shopify Nitrosend returns a Shopify-hosted approval URL A provider-hosted approval URL is returned when a funding instrument is available Vercel Nitrosend returns the Vercel resource management action Funding status explains the marketplace-managed or unavailable route Transition or unavailable Mutation stops with a provider-safe recovery action Purchase capability is unavailable until the route is resolved Clients must not construct Stripe sessions, Shopify confirmations, or Vercel management links themselves. They follow billing_route , manage_url , approval , and next_action . Webhook and readback authority Webhooks make completion prompt; readback makes it recoverable. Both converge on the same local state transition. A valid webhook can reconcile the provider object into Nitrosend state. Plan checkout status may perform provider readback when a webhook is delayed. Funding purchase status reads local reconciled state; provider webhooks and background reconciliation own provider readback and apply. Reconciliation is idempotent, so webhook delivery and polling can race safely. Unknown or incomplete provider evidence remains pending or fails closed. Funding is credited through the ledger transition, not by trusting a success redirect or client-supplied amount. Plan checkout status is an active recovery tool. Funding purchase status is the stable observation surface for the ledger transition performed by the webhook or reconciliation worker. Authorization and URL visibility Billing is account-scoped. Read operations can expose status without granting mutation authority. Checkout creation and approval URL visibility require a billing-authorized context. Headless clients should use an account-scoped API key stored as a secret. External OAuth MCP sessions can inspect billing but do not carry a per-request account principal for mutations. The correct result is an admin handoff to Nitrosend or a scoped API-key workflow. A client must never work around that result by calling Stripe directly. Approval URLs are capabilities: Return them only to an authorized caller. Keep them out of routine logs, traces, and analytics. Discard them after expiration or terminal completion. Use only the exact provider URL returned by Nitrosend. Error taxonomy Class Examples Meaning Caller correction Missing plan, invalid amount, invalid key Fix the request before retrying Intent conflict idempotency_conflict The key is already bound to another request Authority ask_account_admin The caller may read but cannot create or view the approval Provider route billing_provider_unavailable , marketplace management Follow the returned account route Open purchase Pending plan or funding purchase Resume and poll the existing purchase Evidence unavailable Pending purchase, unverified checkout Retry status with bounded backoff Terminal or reversal expired , failed , partially_reversed , reversed Do not replay the purchase; trust the returned balance and recovery action Testing invariants A payment integration is ready when tests prove all of these: Exact retries return one local purchase and one provider checkout. Changed requests under the same key conflict. The caller cannot observe an approval URL without billing authority. Checkout creation does not activate a plan or credit a balance. Redirect arrival does not activate a plan or credit a balance. Valid webhook evidence and status readback converge on the same final state. Invalid or incomplete provider evidence cannot produce active or credited . Plan purchase does not change prepaid balance. Funding purchase does not change subscription entitlements. Shopify plan purchases receive Shopify-hosted approval, while marketplace-managed accounts receive their provider management action. Neither route receives an incorrect Stripe checkout. Use Payments for agents for the operational flow. The MCP tools reference and generated API reference remain authoritative for exact fields and response schemas. ### Contacts Path: `/concepts/contacts` Understanding contacts, channels, activity, and saved views in Nitrosend A contact represents a person in your audience. Contacts have channels (email, phone), custom fields, tags, list memberships, subscription state, and an activity history. Channels Each contact can have multiple communication channels: Email — for email campaigns and transactional messages Phone — for SMS messages Custom fields Custom fields hold anything else you know about a contact, such as a plan or a signup source. Set them inside the contact's data object when you create or update the contact, through the REST API or attributes.data in the nitro_manage_audience MCP tool: Copy { "email" : "alice@example.com" , "data" : { "plan" : "pro" , "signup_source" : "webinar" } } A key sent at the top level instead of inside data is ignored. New keys are registered automatically, so you can use them in segment conditions and in templates, where you reference one as {{ data.plan }} (see personalization ). Integration fields are written by their sync under the integration's name, such as data.shopify , and can't be set through the API. Tags Tags are free-form labels you can attach to contacts (for example vip , newsletter , free-trial ). Tags are a special reserved key on each contact's data object — stored as an array of strings — and are used for segmentation and targeting alongside lists and custom fields. Unlike lists, tags are lightweight: a contact can have any number of tags. You can set the tags on an individual contact via the REST API , or add, remove, and replace tags across many contacts at once via the nitro_manage_audience MCP tool. Subscription status Contacts can be subscribed, unsubscribed, or bounced per channel. Unsubscribed contacts are automatically excluded from campaigns. Activity timeline Each contact has an activity timeline showing sends, opens, clicks, unsubscribes, bounces, complaints, custom events, and list changes in one place. Use it to understand how a contact reached their current state before you target them again. Saved views The contacts table supports saved views. A saved view keeps the current search, filters, visible columns, and sort order so you can return to an operational slice of your audience quickly. Saved views are for working in the contacts table. Use segments when the same rules should become a reusable audience for campaigns, flows, or agents. ### Templates Path: `/concepts/templates` Reusable email designs Templates are reusable email designs. Build them once with the drag-and-drop editor, then use them across campaigns and flows. Editor The visual editor uses a section-based layout. Drag components (text, images, buttons, dividers) into sections. Your brand theme is applied automatically. Personalization Merge tags fill in each recipient's details when the email is sent. Insert them from the editor's merge tag menu, or type them: Copy Hi {{ contact.first_name }}, You're on the {{ data.plan | default: "free" }} plan. Lifetime spend: {{ data.shopify.customers.amount_spent }} Contact fields : contact.first_name , contact.last_name and contact.email , plus profile fields such as contact.company , contact.title and contact.total_spent . Custom fields : data. reads a field from the contact's data object (see custom fields ). Use dots for nested fields, for example data.preferences.locale . Integration fields : synced fields live under the integration's name, for example data.shopify.customers.amount_spent . They render in campaigns and flows; transactional messages render contact and custom fields only. Fallbacks : in campaigns and flows, a field the contact doesn't have renders empty. {{ data.plan | default: "free" }} renders free instead. Other filters include upcase , capitalize , number , currency and date . Values are HTML-escaped in the HTML version of the email, so a field can't add markup. MJML rendering Templates are stored as structured JSON and rendered to MJML for maximum email client compatibility. You don't need to write any HTML. ### Flows Path: `/concepts/flows` Automated multi-step workflows Flows are event-driven automation sequences. When a trigger fires, the flow executes a series of steps for each qualifying contact. Triggers Contact added to list — fires when a contact joins a specific list Event received — fires when a custom event is tracked for a contact Tag added — fires when a tag is applied to a contact Steps Flows support multiple step types that can be chained together: Send Email / SMS — deliver a message Wait — pause for a specified duration Condition — branch the flow based on contact data or behavior Update Contact — set fields or add tags Flow states State Description Draft Being designed Active Running and processing contacts Paused Temporarily stopped Archived No longer in use ### Campaigns Path: `/concepts/campaigns` One-time email sends to your audience A campaign is a one-time email or SMS send to an explicit audience. Each campaign uses a template and tracks delivery metrics (opens, clicks, bounces, unsubscribes). Campaign vs Flow Campaign — manual, one-time send to selected lists, a segment, or All Contacts. Good for newsletters, announcements, promotions. Flow — automated, event-triggered sequence. Good for welcome series, onboarding, re-engagement. Audiences Campaigns do not fall back to everyone when no audience is selected. Choose one audience type: Contact lists — send to one or more lists, optionally filtered by a segment. Segment — send to contacts matching a saved segment. All Contacts — send to every subscribed contact in the brand. All Contacts is a deliberate target. The dashboard shows it as a pinned audience option and asks for confirmation before delivery. MCP/API users must also confirm an all-contacts launch with confirm_send_to_all . Scheduling Campaigns can be sent immediately or scheduled for a future date and time. Edit lock and lifecycle Once a campaign is sent, its content is locked. The API response includes an editable: boolean field and the dashboard renders a banner reflecting the current lifecycle: Status Editable What you can do draft yes Full edit access. scheduled yes, until 5 minutes before send A countdown banner appears 15 minutes before the lock time. scheduled (final 5 min) no Cancel or duplicate. live no Wait for completion. paused no Resume sends the original to remaining contacts. Cancel + Duplicate if you want to change direction. completed / cancelled / archived no Duplicate to iterate. Name is always editable. The name is internal metadata (list/search reference), never delivered content. Renaming a sent campaign succeeds on every status. Status transitions are always allowed. Resume, Cancel, and other lifecycle operations are not content edits and are not blocked by the lock. When a locked campaign receives content edits (trigger, template, channel, scheduled_at), the API returns: Copy { "code" : 422 , "error" : true , "error_code" : "campaign_locked" , "message" : "This campaign has been sent and can't be changed." , "status" : "completed" , "hint" : "Duplicate it to create a new draft." } Duplicating a sent campaign POST /v1/my/campaigns/{id}/duplicate returns a fresh draft that copies the source's audience (trigger.audience_type, contact_list_ids, segment_id) and template content (design, subject, preheader, body, from_name, from_email, reply_to). It does not copy schedule, approval, send history, or metrics — the new campaign starts clean with status: draft , approval_state: pending_review , and name "Copy of {original}" . Duplicate works on any status, locked or not. In the dashboard, the banner on locked campaigns shows Edit as new campaign as the primary CTA, and the overflow menu on every campaign row exposes the same action. ### Segments Path: `/concepts/segments` Dynamic audience groups based on profile, engagement, membership, and deliverability filters Segments are dynamic contact groups defined by filter conditions. Unlike static lists, segments automatically update as contacts match or stop matching the criteria. Filter builder Build segments with the same filter builder used across contacts, segments, sending activity, and flow split conditions. Filters are grouped so you can quickly find the right rule: Profile — name, email, phone, country, source, tags, custom fields, and enrichment fields Membership — contact list membership Engagement — engagement rating, sends, opens, clicks, open rate, click rate, last opened, and last clicked Deliverability — suppressed, bounced, complained, soft-bounce count, suppression reason, and unsubscribe date Behavior — event history rules such as performed, not performed, count at least, and count at most Date filters support relative windows such as "within the last 30 days" and negative windows such as "has not opened in the last 30 days." Segment counts Segment counts are refreshed in the background and shown in the segment list. Counts are intended as an operational guide before sending; final campaign audiences are still evaluated at send time so the current subscription, suppression, and unsubscribe state is respected. Usage Use segments as the audience for campaigns, or as conditions in flow steps to branch logic based on segment membership. Saved views vs segments Saved views remember how you like to inspect the contacts table. Segments define an audience that can be reused for sending, automation, and agent workflows. If a filter set should affect delivery, save it as a segment. ### Agent Inbox Path: `/concepts/agent-inbox` Two-way email on your own domain, answered by your AI agent and your team together Info The agent inbox is in beta. Request access from the Inbox page in your dashboard and we'll enable it for your workspace. An agent inbox is a real email address on your own domain, like support@yourcompany.com , where Nitrosend receives mail, triages it, and lets your AI agent and your team answer it together. Campaigns and flows send email; the inbox is where email comes back. Your domain, your existing mail untouched Inboxes run on your top-level domain, not a subdomain. You point your domain's MX records at Nitrosend, and Nitrosend receives mail for the addresses you claim. Everything else forwards to your existing mail provider untouched: Gmail, Google Workspace, Outlook, Zoho, whatever you use today keeps working exactly as before. The exact DNS records for your domain are shown in Settings > Domains ( app.nitrosend.com/my/settings/domains ). Once the records verify, your claimed addresses start receiving. One inbox, several addresses An inbox on your own domain can answer to more than one address on that domain. Add aliases such as ask@ or billing@ to an inbox and mail to any of them lands in the same queue. Each thread shows the address the customer wrote to, and replies go out from that address. When you compose a new message, you can send from any of the inbox's addresses. Aliases need Nitrosend to receive the domain's mail through MX. A domain that forwards mail from another provider receives at the inbox's main address only. The action queue Every inbound message lands in the action queue in one of four states: Needs you — a human should look at this. Mail lands here when it needs judgment: a mismatched reply-to address, a provider spam flag, or suspicious content in the body. Agent ready — clean mail your agent can answer. Passed spam, virus, and authentication checks. Handled — resolved items, kept as an audit trail. Quarantine — mail that failed security checks or arrived on a disabled inbox. Quarantined mail that failed a virus check can never be released to the agent. Nitrosend checks every inbound message against provider spam, virus, SPF, DKIM, and DMARC verdicts, and scans the body for prompt-injection attempts aimed at your agent. Flagged content is marked in the queue so a human sees exactly why an item needs attention. Inbox vs Campaigns Campaigns and flows — designed, on-brand email to many people. Templates, sections, pixel-perfect layout. Inbox — plain 1:1 human email. No templates, no designer. A reply from your inbox reads like a person wrote it, because the recipient should never feel like a campaign target mid-conversation. Replies and new messages Reply to any thread, or compose a new message from an inbox address. Compose supports multiple To recipients plus Cc and Bcc. Replies thread correctly with the original message, and every outbound message goes through the same sending checks as the rest of your account. Sending to an address that already has an open conversation continues that thread instead of starting a new one. Agent and human, together Your agent works the queue over MCP: nitro_inbox — read the queue and mailbox: list_queue , get_item , list_mailbox , get_thread , get_thread_page , get_message_body , validate_reply nitro_inbox_action — act on items: send_reply , send_reply_test , mark_handled , request_human , release_to_agent , mark_quarantine The agent answers what it can and escalates with request_human when it should not decide alone. Your team works the same queue in the dashboard under Inbox , with the same actions. Every disposition is recorded, whoever made it. Message source Every message has a source view: the full stored header map, authentication results (SPF, DKIM, DMARC, spam, virus), and the raw text and HTML content. Open it from any message in a thread when you need to debug delivery or verify where a message really came from. ### Outreach Path: `/concepts/outreach` Find leads, verify their email addresses, and activate them into sequenced sending Info Outreach is coming soon. Register your interest from the Outreach page in your dashboard and we'll let you know the moment it opens. Outreach finds new leads, verifies that their email addresses are real, and activates them into automated sequences. It is the outbound counterpart to your contact base: contacts are people who know you; leads are people you want to reach. How it works Search — describe who you want to reach (role, industry, company size, location). Nitrosend runs the search and returns matching leads with a fit rating. Review — each lead carries a fit level (high, medium, low) and a deliverability verdict. Reveal a lead to get their verified email address. Activate — put revealed leads into a sequence. Activation triggers a flow, so sequences use the same flow engine, templates, and approval gates as everything else you send. Monitor — replies land in your agent inbox, and sequence states update live. Verification before sending A lead is only worth contacting if the address is real. Every revealed address is verified before it can enter a sequence, and the deliverability verdict is shown on the lead. Unverified addresses are held back rather than burned against your sending reputation. Replies always stop a sequence The moment a lead replies, their sequence stops. No exceptions, no "one more touch." The reply lands in your inbox queue where your agent or your team picks up the conversation as a human exchange. Sequence states Each lead in a sequence carries one state: State Meaning Ready Revealed and eligible for activation Held Waiting on verification or a safety check In sequence Actively receiving the sequence Completed Finished the sequence without a reply Stopped Sequence ended early, usually by a reply Converted Replied or took the goal action Failed / Skipped Could not be contacted, with the reason recorded Outreach vs Campaigns Campaigns send to your existing audience: contacts who opted in. Outreach contacts people who have never heard from you. Volume is deliberately paced, addresses are verified first, and replies immediately convert the exchange into a conversation. ## MCP Tools ### nitro Path: `/mcp` Version: 1.7.0 AI-native email marketing platform for campaigns, lifecycle flows, and transactional email #### Operations ##### TOOL nitro_get_status Summary: nitro_get_status Get current account context, sender identity, dashboard links, readiness, onboarding status, and recommendations. ##### TOOL nitro_select_account Summary: nitro_select_account Switch this OAuth MCP connection to an account listed by nitro_get_status; the next call uses it. Credential-authenticated connections are pinned and cannot switch. Parameters: - `account_id` (argument, integer, required) - ID of the account to switch to. Get IDs from nitro_get_status.available_accounts.items[*].id. ##### TOOL nitro_select_brand Summary: nitro_select_brand Select the current brand for this OAuth MCP connection. Use this instead of passing brand_sid to other tools. Credential-authenticated connections are pinned to one brand and cannot switch. Parameters: - `brand_sid` (argument, string, optional) - Exact brand SID to select. Provide either brand_sid or name. - `name` (argument, string, optional) - Brand name to select when the SID is unknown. Provide either name or brand_sid. Ambiguous names return candidates without changing context. ##### TOOL nitro_query Summary: nitro_query Query any Nitrosend entity. Returns paginated results. Parameters: - `entity` (argument, string, required) - Which entity type to query. Use nitro_search_contacts for full-text contact search. - `filters` (argument, object, optional) - Entity filters: flows status/campaign_id/trigger_event/search; campaigns status/search; templates subject; segments/lists name; contacts query/subscribed_email/subscribed_phone/list_id; events name/from/to; imports status; messages channel/status/to; suppressions email/reason/source_provider/active; history source/event_type/tool/actor/correlation_id/resource_uri/from/to; products status/query. All support integer id. - `page` (argument, integer, optional) - Page number (default 1) - `per` (argument, integer, optional) - Results per page (max 50, default 25) ##### TOOL nitro_search_contacts Summary: nitro_search_contacts Search contacts by email, name, or phone. Returns summary list or full profile. Parameters: - `query` (argument, string, required) - Email address, name, or phone number - `mode` (argument, string, optional) - summary = list, profile = single contact detail (default: summary) - `page` (argument, integer, optional) - Page number (default 1) - `per` (argument, integer, optional) - Results per page (max 50, default 25) ##### TOOL nitro_set_brand_kit Summary: nitro_set_brand_kit Set Brand Kit from a website, typed fields, document, or logo URL. Fields override scraping; async is URL-only. Request body: application/json ##### TOOL nitro_ingest Summary: nitro_ingest Store a brand image (PNG, JPEG, GIF, WebP; max 10 MB) from image_file (ChatGPT attachment), image_data, image_url or upload then signed_id. Returns a durable email URL, up to 1200px wide. Request body: application/json ##### TOOL nitro_manage_audience Summary: nitro_manage_audience Create or update contacts, manage subscriptions, lists, events, segments, tags, and explicit prepaid email validation. Custom contact fields belong in attributes.data. For more than 100 contact writes, use nitro_import_contacts with CSV. Parameters: - `operation` (argument, string, required) - Choose one audience operation. validate accepts exactly one of contact_channel_ids, contact_ids, list_id, segment_id, or all_contacts: true. Dry-run quotes without mutation; pass its quote_digest on execution to refuse audience or price drift. Execution needs idempotency_key and prepaid funds. Deletion needs confirm. - `params` (argument, object, required) - Parameters named by operation. Contact custom fields belong in attributes.data. bulk_tag tag_action: add|remove|set (default add). - `dry_run` (argument, boolean, optional) - Preview changes without persisting (default: false) - `confirm` (argument, boolean, optional) - Required for destructive operations: delete_segment, manage_list with action='delete' - `idempotency_key` (argument, string, optional) - Optional deduplication key. Same key returns cached result. ##### TOOL nitro_manage_outreach Summary: nitro_manage_outreach Discover named people for outreach: intent, estimate, then start. Companies are evidence, never recipients. start requires the quoted USD-cent spend cap and idempotency key. Request body: application/json ##### TOOL nitro_define_segment Summary: nitro_define_segment Preview or persist a filtered contact segment. preview_only defaults true; set false and provide name to save. Request body: application/json ##### TOOL nitro_import_contacts Summary: nitro_import_contacts Inline: max 100 contacts; large lists: CSV upload/finalize. Account-standing row/file limits: status guardrail (standing, max_rows) or GET /v1/my/imports/spec. Inline, opt_in omitted: new email contacts subscribe both channels; phone-only stay unsubscribed. Default CSV: new contacts subscribe both channels. In both cases, existing contacts keep subscriptions; added email subscribes, added phone does not. Request body: application/json ##### TOOL nitro_manage_template Summary: nitro_manage_template Create, edit, or clone a reusable email template. For a creative design, start with composition_mode=intent and follow its exact next_call. Exact edits use template_id, section_updates, and if_version; based_on clones. Use nitro_compose_campaign for broadcasts. Request body: application/json ##### TOOL nitro_compose_flow Summary: nitro_compose_flow Create or edit an inactive automation draft. For creative email, call composition_mode=intent and follow its exact next_call. replace needs flow_id, confirm, and the draft revision; patch preserves other nodes. Activate with nitro_control_delivery. Request body: application/json ##### TOOL nitro_compose_campaign Summary: nitro_compose_campaign Create or edit an email/SMS broadcast draft; never delivers it. For creative email, start with composition_mode=intent and follow its exact next_call. patch preserves omitted fields; replace requires campaign_id and confirm, and clears omitted audience or schedule. Use nitro_compose_flow for automation and nitro_send_message for transactional mail. Request body: application/json ##### TOOL nitro_manage_domains Summary: nitro_manage_domains Activate or select the Nitrosend brand-subdomain sender, or add, verify, diagnose, list, and remove customer sending domains. Receiving MX appears only after forward-all exists. Request body: application/json ##### TOOL nitro_configure_account Summary: nitro_configure_account Read or configure sender defaults and test recipients. Use nitro_get_status for readiness; from_email explicitly selects an authorized, ready, brand-owned sending identity. Parameters: - `from_name` (argument, string, optional) - Sender display name (e.g. 'Acme Marketing') - `from_email` (argument, string, optional) - Exact visible From address to select. May use the apex when a ready aligned sending subdomain authorizes it. - `reply_to` (argument, string, optional) - Reply-to email address - `test_email_recipients` (argument, array, optional) - Saved email addresses for test sends (max 5). Pass empty array to clear. ##### TOOL nitro_review_delivery Summary: nitro_review_delivery Read-only content and readiness review with validation, spam/SMS analysis, preflight, and previews. Review either one account entity or bounded inline email content; this never approves or sends. For flows, an omitted revision_id uses the current draft and a supplied revision_id asserts that it is still current. Parameters: - `target_type` (argument, string, optional) - Entity type to review - `target_id` (argument, integer, optional) - Entity ID to review - `revision_id` (argument, integer, optional) - Optional for flows. Omit to review the current draft, or supply its ID as a current-draft assertion. - `contact_id` (argument, integer, optional) - Optional contact ID for merge-tag personalization during review - `subject` (argument, string, optional) - Subject for a self-contained inline email review - `html` (argument, string, optional) - Rendered HTML for a self-contained inline email review ##### TOOL nitro_send_test_message Summary: nitro_send_test_message Send one real email/SMS test for a template, campaign, or exact flow revision. Never approves or sends to an audience. Parameters: - `target_type` (argument, string, optional) - Target entity type. Use with target_id unless latest_campaign or template_id is used. - `target_id` (argument, integer, optional) - Target entity ID. Use with target_type. - `latest_campaign` (argument, boolean, optional) - Use the most recently created campaign in this brand. - `template_id` (argument, integer, optional) - Template to test directly, or the specific flow/campaign template to choose. - `action_id` (argument, integer, optional) - Flow action ID to test when a flow has multiple message steps. - `revision_id` (argument, integer, optional) - Required for flow targets. Exact immutable flow revision to test. - `channel` (argument, string, optional) - Channel to test. Use auto unless a standalone template is ambiguous. - `contact_id` (argument, integer, optional) - Contact ID for recipient and merge-tag personalization. If present, this contact supplies the recipient address/phone. - `to` (argument, array, optional) - Explicit test recipients. Use email addresses for email targets and E.164 phone numbers for SMS targets. - `data` (argument, object, optional) - Sample values for required data.* merge fields in an email test. - `dry_run` (argument, boolean, optional) - Validate target and recipients without sending. - `idempotency_key` (argument, string, optional) - Required for live test sends. Reuse the same stable key on retry to prevent duplicate delivery. ##### TOOL nitro_control_delivery Summary: nitro_control_delivery Approve or control delivery after review. Restarting a paused flow may require a choice for contacts already in progress. revision_id can assert the current draft. Parameters: - `target_type` (argument, string, required) - `target_id` (argument, integer, required) - `expected_brand_sid` (argument, string, optional) - Optional reviewed-brand assertion. When available, copy meta.current_brand.sid from the reviewed result. - `operation` (argument, string, required) - approve preflights; schedule is campaign-only; delete requires confirm and a never-sent draft. - `scheduled_at` (argument, string, optional) - Required for schedule. - `revision_id` (argument, integer, optional) - Optional current-draft assertion for flow approve, reject, and live. - `resume_mode` (argument, string, optional) - For paused flows: stop current journeys, or restart waits and continue gradually. - `confirm_send_to_all` (argument, boolean, optional) - Explicit all_contacts confirmation for live/schedule. - `confirm` (argument, boolean, optional) - Required for delete. - `idempotency_key` (argument, string, optional) - Campaign-live retry key; reuse only for the same send. ##### TOOL nitro_get_insights Summary: nitro_get_insights Get email analytics with trends, benchmarks, and recommendations. Parameters: - `scope` (argument, string, required) - Scope of insights: account-wide, per flow, per campaign, or per message - `entity_id` (argument, integer, optional) - Required for flow/campaign/message scope - `period` (argument, string, optional) - Time period for metrics (default 30d) ##### TOOL nitro_configure_providers Summary: nitro_configure_providers Configure BYO email provider credentials or check provider status. Parameters: - `operation` (argument, string, required) - configure sets BYO provider credentials; status checks current provider health - `provider` (argument, string, optional) - Email provider (required for configure) - `api_key` (argument, string, optional) - Provider API key (required for configure, never returned in responses) - `api_secret` (argument, string, optional) - Optional provider secret (never returned in responses) - `region` (argument, string, optional) - Provider region where required, or the Mailgun sending domain ##### TOOL nitro_set_memory Summary: nitro_set_memory Read or update the AI memory document. Operations: read (get current), update (replace entirely), patch (replace a ## section by heading), append (add text to end). Parameters: - `operation` (argument, string, required) - read: get current document. update: replace entirely. patch: replace a ## section by heading. append: add text to end. - `document` (argument, string, optional) - Full markdown document (required for update). - `heading` (argument, string, optional) - Section heading to patch (e.g. 'Brand Goals'). Required for patch operation. Matches ## headings. - `content` (argument, string, optional) - New content for the section (patch) or text to append (append). - `dry_run` (argument, boolean, optional) - `idempotency_key` (argument, string, optional) - Required for non-dry-run append. Reuse the same key only for an exact retry. Update and patch are set operations and may omit it. ##### TOOL nitro_manage_billing Summary: nitro_manage_billing Read billing/plans, create operator-approved subscription or add-funds checkout URLs, and poll status. Add-funds behavior depends on the selected instrument: current live instruments return operator approval URLs, and no live presented-credential rail is enabled. Prepaid balance is separate from plans. Request body: application/json ##### TOOL nitro_send_message Summary: nitro_send_message Send one transactional email/SMS now. Use nitro_compose_campaign for broadcasts and nitro_compose_flow for sequences. Reuse idempotency_key only for an exact retry. Parameters: - `channel` (argument, string, required) - Delivery channel - `to` (argument, string, required) - Recipient email address or E.164 phone number - `subject` (argument, string, optional) - Email subject line (required for email) - `body` (argument, string, optional) - Message body. Required for SMS. Optional plain text for email. - `template_id` (argument, integer, optional) - Load email design from an existing template (email only) - `data` (argument, object, optional) - Transactional merge variables. Use in email templates as {{ data.order_id }} or nested paths like {{ data.customer.name }}. - `idempotency_key` (argument, string, required) - Required for live sends. Reuse the same stable key on retry to prevent duplicate delivery. - `dry_run` (argument, boolean, optional) - Validate and preview without sending ##### TOOL nitro_inbox Summary: nitro_inbox Read the agent queue or mailbox. Follow next_calls for older or truncated thread content. Parameters: - `command` (argument, string, required) - `action_item_id` (argument, integer, optional) - `conversation_id` (argument, integer, optional) - `before_occurred_at` (argument, string, optional) - Thread cursor from get_thread/get_thread_page - `before_message_id` (argument, integer, optional) - Cursor message id paired with before_occurred_at - `message_id` (argument, integer, optional) - `attachment_id` (argument, integer, optional) - `acknowledge_unscanned` (argument, boolean, optional) - Operator accepted the unscanned-download risk - `offset` (argument, integer, optional) - Body offset from get_thread/get_message_body - `state` (argument, string, optional) - Default needs_attention - `status` (argument, string, optional) - `query` (argument, string, optional) - Subject, preview, or sender - `inbox_id` (argument, integer, optional) - `sender` (argument, string, optional) - Sender fragment - `reason_code` (argument, string, optional) - `since` (argument, string, optional) - ISO 8601 - `sort` (argument, string, optional) - `view` (argument, string, optional) - Default compact - `include_counts` (argument, boolean, optional) - `purpose` (argument, string, optional) - get_thread only - `page` (argument, integer, optional) - `per` (argument, integer, optional) - Max 100 compact - `brand_context_ref` (argument, string, optional) - Ref from a prior read - `reply_context_digest` (argument, string, optional) - From get_item/get_thread; required by validate_reply - `subject` (argument, string, optional) - `body` (argument, string, optional) - `html` (argument, string, optional) - `attachment_refs` (argument, array, optional) - Signed upload references ##### TOOL nitro_inbox_action Summary: nitro_inbox_action Reply, reserve attachments, triage one item or many, and set sender policy, read state, or contact linkage. Request body: application/json ##### TOOL nitro_request_support Summary: nitro_request_support Submit support only when asked or tools cannot resolve the issue; never promote it. Send a complete message within 1500 characters; there is no truncation. Parameters: - `subject` (argument, string, required) - Brief summary of the issue - `message` (argument, string, required) - Complete, self-contained summary of the issue. Must fit within 1500 characters; do not rely on truncation. ##### TOOL nitro_search_docs Summary: nitro_search_docs Search Nitrosend guides and API/MCP/CLI/integration references. Ground unestablished product claims in returned excerpts; search again or state what remains unverified. Parameters: - `query` (argument, string, required) - What to look up, e.g. 'verify sending domain', 'rest api authentication', 'cli install', 'connect cursor' - `limit` (argument, integer, optional) - Maximum results to return (default 6, max 10) ##### RESOURCE nitro://guide Summary: guide Reference guide for unfamiliar Nitrosend vocabulary and workflows. Covers Nitrosend entities (contacts, channels, flows, campaigns, templates, segments), the two operating modes (tool-driven and goal-driven), the recommended workflow sequence, volume and tier constraints, and common pitfalls. ##### RESOURCE nitro://account Summary: account Live snapshot of the current account context: account, brand, sender identity, dashboard links, tier (free / paid / trusted), email and SMS volume used vs caps, onboarding checklist progress, contact and list counts, and active flow/campaign summary. ##### RESOURCE nitro://schema Summary: schema Machine-readable schema for composing emails and flows. Includes every email section type (header, text, image, button, columns, product, social, divider, spacer, footer) with required and optional props, all flow step types with their parameters, trigger event names, and the full list of segment filter names and predicates. ##### RESOURCE nitro://brand-kit Summary: brand-kit The selected brand's Brand Kit identity. Returns brand_color, text_color, bg_color, font_heading, font_body, logo_url, company_name, company_description, physical_address, screenshot_url when a website screenshot exists, and the brand voice document. For local logo files, use nitro_ingest with image_data or upload reservation before setting logo_url. Creative intent contracts already include the canonical authoring context; read this resource for manual edits or deeper inspection. ##### RESOURCE nitro://providers Summary: providers Current email transport configuration and health. Shows whether delivery uses Nitrosend SES or a connected provider (Mailgun, SES, Postmark, Resend, SendGrid), domain verification status for each sending domain, and the explicitly selected sending identity. ##### RESOURCE nitro://config Summary: config Account-level sender defaults: from_name, from_email, reply_to, and saved test email recipients. These are applied automatically to campaigns and flows when per-message overrides are not specified. ##### RESOURCE nitro://email-design Summary: email-design Deep email design reference for campaign, template, or flow email sections. Composition contracts already include selected design capsules; read this only when deeper archetype, color, imagery, whitespace, footer, or section-translation context is needed. ##### RESOURCE nitro://email-marketing Summary: email-marketing Deep email marketing reference for campaign, template, or flow email content. Composition contracts already include selected marketing capsules; read this only when deeper strategy, audience, lifecycle, copy, deliverability, compliance, or intent context is needed. ##### RESOURCE nitro://lifecycle Summary: lifecycle Machine-readable lifecycle flows to draft from, such as welcome, cart and browse abandonment, post-purchase, win-back and sunset. Includes each flow's key, priority, trigger and list need, revenue rationale, and each email's objective, timing and checks. Pass a key as nitro_compose_flow lifecycle_key. ##### RESOURCE nitro://docs Summary: docs The Nitrosend product documentation map: every guide, integration, CLI, concept, authentication, and REST API / MCP reference page with its path and summary. Use the nitro_search_docs tool to search the full documentation content for how-to and reference answers. ##### RESOURCE nitro://examples/email Summary: example-email A complete working email design showing every section type in context — header with logo, hero image, text blocks, buttons, two-column product grid, social links, divider, spacer, and footer with unsubscribe. Use as a copy-paste starting point or reference for prop names, style overrides, and column nesting. ##### RESOURCE nitro://examples/flow Summary: example-flow A complete automation flow example demonstrating all step types: email with inline design, SMS, wait (duration in seconds), split with filter conditions and yes/no branches, emit_event, webhook, subscribe, and unsubscribe. Shows trigger configuration, branching structure, and merge-tag usage. ##### RESOURCE nitro://examples/campaign Summary: example-campaign Complete campaign examples for both email and SMS channels. Shows audience targeting with contact_list_ids and segment_id, scheduled delivery with scheduled_at, inline email design with sections, and SMS body composition. ##### RESOURCE nitro://knowledge/index Summary: knowledge-index Index of built-in email marketing knowledge topics. Returns a list of available slugs with titles and short descriptions. Use a slug with nitro://knowledge/{slug} to read the full topic content. Topics cover deliverability, subject lines, segmentation, automation best practices, and compliance. ##### RESOURCE nitro://memory Summary: memory The operator's persistent AI memory document. Contains business goals, target audience, preferred email strategy, tone of voice notes, and any other context the operator has saved. Creative intent contracts already include the authoritative memory projection; read this resource for explicit inspection or memory management. Managed via the nitro_set_memory tool. ##### RESOURCE nitro://history Summary: history Unified account history combining notification events and MCP tool audit entries. Includes deliverability warnings, auto-pauses, suppressions, and tool-call audit records with timestamps, correlation IDs, payload summaries, and rollback eligibility. ##### RESOURCE nitro://fields Summary: fields Per-account contact field catalog grouped by category (contact, custom, enrichment, engagement, tag). Each entry includes key, category, field_type, label, promoted flag, and fill_rate (percentage of contacts with a value, null when not yet computed). Read this for field and segment-filter inspection. The authoring intent's merge grammar is authoritative for recipient-copy bindings; field-catalog membership alone does not make an entry safe or portable email content. ##### RESOURCE nitro://authoring-context/{ref}{?cursor} Summary: authoring-context Lossless paged read of the exact canonical brand authoring packet named by a composition contract. Start at its supplied ref URI, follow each exact next_uri, concatenate chunk.text in offset order, and verify packet_digest before filling that contract's draft call. A stale ref never returns current content under the old address. Parameters: - `ref` (path, string, required) - `?cursor` (path, string, required) ##### RESOURCE nitro://outreach/campaigns/{campaign_id} Summary: outreach-campaign Bounded discovery-campaign detail: target profile, criterion coverage, spend budgets, funnel counters, source progress, degradation, and activation-ready inventory. Use nitro_manage_outreach for intent, estimate, start, pause, resume, cancel, and summary status. Parameters: - `campaign_id` (path, string, required) ##### RESOURCE nitro://knowledge/{slug} Summary: knowledge-topic Full content for a single knowledge topic. Pass a slug from the knowledge index (e.g. nitro://knowledge/deliverability). Returns a markdown document with actionable guidance your AI can apply when composing emails, building flows, or advising on strategy. Parameters: - `slug` (path, string, required) ##### RESOURCE nitro://prompts/{name} Summary: prompt Rendered prompt template by name. Returns structured workflow instructions that guide the AI through multi-step tasks like onboarding a new account, running a campaign review, or setting up a welcome series. Each prompt includes context requirements, step sequence, and expected tool calls. Parameters: - `name` (path, string, required) ##### PROMPT audience-analysis Summary: audience-analysis Analyse audience segments and engagement Parameters: - `goal` (argument, string, required) - What you want to learn or achieve, e.g. 'find disengaged subscribers', 'segment by purchase history', 'identify VIP customers' ##### PROMPT build-email Summary: build-email Compose a marketing email Parameters: - `goal` (argument, string, required) - What the email is for, e.g. 'announce our spring sale', 'weekly newsletter', 'event invitation', 'product launch' ##### PROMPT build-flow Summary: build-flow Create an automated email flow from scratch Parameters: - `goal` (argument, string, required) - What the flow should achieve, e.g. 'welcome series for new signups', 'abandoned cart recovery', 're-engage inactive subscribers' ##### PROMPT diagnose-deliverability Summary: diagnose-deliverability Diagnose and fix email deliverability issues ##### PROMPT flow-review Summary: flow-review Review and optimise an existing flow Parameters: - `flow_id` (argument, string, optional) - ID of the flow to review. If omitted, lists active flows for the user to choose. ##### PROMPT lifecycle-setup Summary: lifecycle-setup Review a brand site and draft the key lifecycle automation flows Parameters: - `website_url` (argument, string, optional) - Brand website to review and scrape, e.g. https://example.com - `test_recipient` (argument, string, optional) - Email address that should receive flow test messages ##### PROMPT onboard-brand-kit Summary: onboard-brand-kit Set up Brand Kit identity for a new brand ##### PROMPT weekly-report Summary: weekly-report Generate a weekly performance report ## CLI ### CLI overview Path: `/cli/overview` When to reach for the Nitrosend CLI, and what it gives you that chat MCP clients and the REST API can't Use the Nitrosend CLI when you're scripting CI, building an agent runner, or operating Nitrosend from a terminal. It is the Nitrosend MCP callable from a shell: same tools, same envelope, plus exit codes, dry-run, project context, and typed confirmation. Info All plans, including free plans, have full access to Nitrosend MCP/API/CLI. The CLI is not a paid-plan feature; plan limits apply to usage volume and paid add-ons. Info If you're using Claude Desktop, Claude.ai, or Claude Cowork, do not install the CLI to connect Nitrosend. Enable the official Nitrosend connector under Settings → Connectors instead (see Claude Desktop ). The CLI is for shell workflows that run outside the Claude connector. When to use which surface Nitrosend exposes three peer surfaces over the same platform. Pick the one that matches where your work lives. Surface Reach for it when Skip it when MCP via chat client (Claude Code, ChatGPT, Cursor, …) You want a conversation with the platform — drafting copy, exploring data, planning sends. You need deterministic, scriptable behaviour. CLI ( nitrosend ) You're scripting CI, building an agent runner, or running Nitrosend from a terminal. You need exit codes, JSON envelopes, dry-run, typed prod confirmation. You're connecting Claude Desktop, Claude.ai, or Cowork; enable the Nitrosend connector instead. You're integrating Nitrosend into application code. REST API You're integrating Nitrosend into application code, sending transactional messages from a backend, or wiring up a partner integration. You need the MCP envelope's next_action / blockers guidance fields. The CLI calls the same MCP tools your chat client calls and returns the same envelope shape — the difference is contract: it has stable exit codes, machine-readable output modes, and refuses unsafe ops in non-interactive mode. First workflow Three commands to confirm the CLI is wired up to your account. Copy nitrosend login nitrosend whoami nitrosend nitrosend login runs OAuth in your browser. whoami prints the active profile and API endpoint. The bare nitrosend invocation runs the dashboard — account state, blockers, and suggested next actions. For CI or headless workflows, swap OAuth for an API key: Copy nitrosend login --api-key nskey_live_... # or export NITROSEND_API_KEY = nskey_live_ ... nitrosend mcp tools list See Install & sign in for the full setup, Agent mode for the machine-readable contract, and Recipes for end-to-end examples. What the CLI doesn't try to be The CLI is for deterministic shell workflows. It is not a replacement for the chat experience or the dashboard. It does not own features the MCP doesn't expose — every command is a sensible thing to call from a shell and has a clean MCP analogue. If you need a richer composer, use a chat MCP client. If you need to ship Nitrosend inside an application, use the REST API . For image media files in templates and campaigns, v1 does not add a separate nitrosend media upload command. Use the REST direct-upload flow instead: reserve /v1/direct_uploads with purpose=image or purpose=media_asset , PUT the bytes to the returned upload URL, then submit the returned signed_id to /v1/my/images . ### Install & sign in Path: `/cli/install` Install the Nitrosend CLI, sign in with OAuth or an API key, and confirm your connection Install & sign in Info This page is for terminal, CI, and headless agent-runner workflows. Claude Desktop, Claude.ai, and Claude Cowork users should not install the CLI to connect Nitrosend; enable the official Nitrosend connector under Settings → Connectors instead (see Claude Desktop ). Info All plans, including free plans, have full access to Nitrosend MCP/API/CLI. Install and sign-in work the same way on Free; plan limits apply to usage volume and paid add-ons. Install The CLI ships as @nitrosend/cli on npm. Install globally with your package manager of choice: npm pnpm yarn Copy npm install -g @nitrosend/cli Copy pnpm add -g @nitrosend/cli Copy yarn global add @nitrosend/cli Requires Node.js 18 or newer. Confirm the install: Copy nitrosend --version Sign in The CLI supports two auth modes. OAuth for humans on a workstation, API keys for CI and headless agents. OAuth (recommended for humans) Copy nitrosend login Opens a browser window for OAuth PKCE. The resulting profile is stored under your platform config directory (or NITROSEND_CONFIG_DIR if set) — never inside your repo. Pass --profile to keep multiple accounts side by side: Copy nitrosend login --profile work nitrosend login --profile personal nitrosend whoami --profile work API key (recommended for CI and headless) Get an API key from Settings > API Keys in the Nitrosend dashboard ( app.nitrosend.com/my/settings/api-keys ), then either store it as a profile: Copy nitrosend login --api-key nskey_live_... …or pass it through the environment without writing to disk: Copy export NITROSEND_API_KEY = nskey_live_ ... nitrosend mcp tools list Environment variables take precedence over stored profiles, which makes the CLI safe to drop into a CI runner that injects secrets at job time. Tip In --non-interactive or --machine mode, OAuth login is rejected — interactive auth would block the run. Use an API key or a pre-existing profile for those workflows. Confirm the connection Copy nitrosend whoami Prints the active auth source, profile name, and API endpoint. If whoami succeeds, the next step is the dashboard — a single read that summarises account state, blockers, and what to do next: Copy nitrosend The bare nitrosend invocation is the dashboard alias. It calls nitro_get_status and renders the response envelope's blockers and next_action fields. From here, list MCP tools to confirm the platform surface is reachable: Copy nitrosend mcp tools list Sign out Copy nitrosend logout Removes the active profile. Use --profile to remove a specific one. logout does not revoke the underlying token — to revoke, do that from the dashboard. Custom API endpoints Point the CLI at a non-production endpoint with --api-url or NITROSEND_API_URL : Copy nitrosend login --api-url https://api.staging.nitrosend.com/mcp # or export NITROSEND_API_URL = https :// api . staging . nitrosend . com / mcp Project-level defaults live in .nitrosend.yml — see Project config . ### Agent mode Path: `/cli/agent-mode` Machine-readable output, stable exit codes, dry-run, and idempotency for CI and agent runners The CLI is contract-first. Every command has a CommandDescriptor with input and output schemas, a safety class, and explicit dry-run and idempotency policies. Agent mode is what you turn on to make those contracts load-bearing in CI and agent runners. Turning it on --machine is the master switch: Copy nitrosend mcp tools call nitro_get_status --machine --machine implies --json --non-interactive --no-color --no-pager and assigns an automatic idempotency key for commands that take side effects. It is the single flag CI and agent runners should use; the rest are escape hatches when you want partial behaviour. Flag What it does --machine Enables agent mode. Implies --json --non-interactive --no-color --no-pager . Auto-issues idempotency keys. --json Stable JSON envelope on stdout. --ndjson One stable stream event per line. --csv Tabular data only, for piping into spreadsheets. --non-interactive Refuse to prompt. Fail closed when input would be required. --no-color / --no-pager Strip ANSI and pagers — useful when piping. --dry-run Resolve and validate without performing side effects. Honoured by commands that declare supports_dry_run: true . --explain Return the resolved plan as data. No side effects. --yes Skip non-typed confirmations. Never bypasses typed confirmation. --trace Print timing diagnostics on stderr. Stdout is sacred In --json , --ndjson , or --machine mode, stdout carries only the result envelope. No spinners, warnings, prompts, traces, upgrade notices, or color codes. Diagnostics go to stderr. Pipe nitrosend ... --machine into jq and trust it. The result envelope Every successful command returns an object with this shape: Copy { "schema_version" : 1 , "command" : "mcp tools call" , "data" : { "..." : "tool-specific payload" }, "meta" : { "environment" : "development" , "profile" : "default" , "duration_ms" : 142 , "idempotency_key" : "cli-lt8h2c" }, "sidecars" : { "blockers" : [], "next_action" : "Run `nitrosend mcp tools list --json`." , "suggested_tool_calls" : [{ "name" : "nitro_get_status" , "arguments" : {} }] } } The sidecars block is the same envelope chat MCP clients read — next_action , blockers , and suggested_tool_calls propagate straight from the platform. Agent runners should consume sidecars to decide the next step rather than parse human-facing fields. Errors Errors share the envelope. They never appear on stdout in human-facing modes, but in --json they do — agents need a single stream to parse. Copy { "schema_version" : 1 , "command" : "mcp tools call" , "error" : { "code" : "validation_error" , "message" : "Email is required" , "how_to_fix" : "Pass `to: \" \" ` in --args." , "suggested_tool_call" : { "name" : "nitro_get_status" , "arguments" : {} } }, "meta" : { "environment" : "development" , "duration_ms" : 87 } } Do not retry the same call on error. Read how_to_fix and follow it. If suggested_tool_call is present, call that instead. Exit codes Stable across versions: Code Meaning 0 Success 64 Usage error (unknown command, bad flags) 65 Data or validation error 69 Service unavailable 70 Internal error 75 Temporary or retriable failure 77 Permission or auth failure 78 Unsupported or outdated CLI Agent runners should branch on exit codes for retry strategy: 75 is retriable with backoff, 77 means re-auth, 78 means upgrade the CLI before continuing, everything else is terminal for the run. Dry run and explain Two flavours of "don't actually do it": --dry-run — runs the command up to the side-effect boundary and reports what would happen. Only honoured by commands that declare supports_dry_run: true in their descriptor; others reject the flag with a usage error. --explain — returns the resolved plan as data. Available on every command. Use it from an agent harness to fetch a deterministic plan before deciding to execute. Copy nitrosend mcp tools call nitro_compose_campaign --args '{...}' --explain --json Idempotency Commands that take side effects declare idempotency: { mode: "auto" } in their descriptor. In --machine mode the CLI auto-issues a per-process idempotency key and surfaces it as meta.idempotency_key on the result, so a retry inside the same run dedupes against the original call. For per-call idempotency that survives runner restarts (the common CI case), pass an idempotency_key inside --args for the underlying MCP write tool, such as nitro_compose_campaign or nitro_compose_flow . The platform dedupes on that key, not on the CLI's per-process key. Copy nitrosend mcp tools call nitro_compose_campaign --machine --args '{ "name": "Release announcement", "channel": "email", "subject": "Release notes are live", "body": "The latest release is now available.", "idempotency_key": "release-announcement-42" }' Describe a command describe returns the descriptor for any command — schemas, examples, safety class, dry-run support, idempotency, and whether the command is suitable for agent use: Copy nitrosend describe mcp tools call --json Agent harnesses should describe a command once at startup and cache the schema instead of guessing flags. Confirmations --yes skips ordinary y/N prompts. It does not bypass typed confirmation on destructive commands — those require the user to type the literal target name and fail closed in --non-interactive and --machine modes. See Project config for production environment guards. ### Project config Path: `/cli/project-config` Per-project defaults via .nitrosend.yml — profiles, environments, and the production typed-confirm guard Project-level defaults live in .nitrosend.yml . The CLI walks up from your current directory until it finds one, then merges its values with environment variables and command flags. Flags win, then environment variables, then the file. File shape Copy profile : default environment : production output : json api_url : https://api.nitrosend.com/mcp Field Effect profile Default profile to use when --profile is not passed. environment Local label: development (the default), staging , sandbox , or production . Drives the production guard (see below). output Default output mode ( tty , json , ndjson , csv ). api_url Default MCP endpoint. Overridden by --api-url or NITROSEND_API_URL . .nitrosend.yml must never contain secrets. API keys belong in NITROSEND_API_KEY or in a profile stored under your platform config directory. Commit .nitrosend.yml to your repo so the whole team gets the same defaults; keep credentials outside it. Environments environment is a local label, one of production , staging , sandbox , or development . Any other value, or no .nitrosend.yml at all, means development . One value changes behavior: production : every destructive command requires typed confirmation of its target, even ones that would not otherwise ask. Designed to make it hard to run a destructive command in prod by muscle memory. The label is not a sandbox, and Nitrosend has no server-side sandbox. It does not change the endpoint, account, or brand: commands send real mail under every label, sandbox included. Labels other than development are rendered on command results. In agent mode, the label appears in meta.environment so harnesses can branch on it. Copy # .nitrosend.yml in your prod automation repo profile : prod environment : production output : json The production guard Destructive commands declare a confirmation_target in their descriptor — typically the resource name. Running them anywhere requires typing that target. In environment: production , the guard widens: every destructive command demands typed confirmation, even ones that wouldn't ordinarily require it. The promotion is per-environment, not per-command — set environment: production once in the relevant .nitrosend.yml and the rule applies to every destructive command run from that directory. Copy nitrosend fixture destroy demo --confirm demo In an interactive shell, the CLI also prompts: Type demo to continue: . The typed answer must match the target exactly. --yes does not bypass typed confirmation. --non-interactive and --machine fail closed when typed confirmation is required — they error with typed_confirmation_required (exit code 77 ) and tell you to rerun with --confirm . Agent runners that need to execute destructive prod commands must pass --confirm ahead of time, sourced from a human-approved input. Profiles Profiles let you keep multiple accounts or endpoints side by side. Switch with --profile or by setting profile in .nitrosend.yml . The active profile is reported by whoami : Copy nitrosend whoami Profile precedence, highest to lowest: --profile flag NITROSEND_PROFILE environment variable profile: in .nitrosend.yml The default profile Profiles store an OAuth refresh token or an API key plus the API URL. They're written to your platform config directory (or NITROSEND_CONFIG_DIR if set), not into the repo. Environment variables Variable Effect NITROSEND_API_KEY API key. Wins over any stored profile. NITROSEND_API_URL MCP endpoint URL. NITROSEND_PROFILE Profile name. NITROSEND_CONFIG_DIR Where profiles are stored. Default is the platform config dir. CI runners typically set only NITROSEND_API_KEY and rely on the rest from .nitrosend.yml . Example: monorepo with dev + prod scripts Copy my-app/ ├── .nitrosend.yml # profile: default, environment: development └── scripts/ └── deploy/ └── .nitrosend.yml # profile: prod, environment: production Running nitrosend ... from the repo root uses the default profile and skips prod confirmations. Running it from scripts/deploy/ uses the prod profile and demands typed confirmation for any destructive op. The CLI walks up from the cwd, so the nearest config wins. Both directories send real mail. ### Recipes Path: `/cli/recipes` End-to-end CLI examples for CI pipelines, local operating, and agent runners Concrete patterns for the three audiences the CLI is built for. CI pipelines GitHub Actions: draft a release campaign Use nitrosend mcp tools call to create a reviewed campaign draft after a deploy. --machine gives stable JSON, exit codes, and an auto-issued idempotency key. Copy name : Draft release campaign on : release : types : [ published ] jobs : notify : runs-on : ubuntu-latest steps : - uses : actions/setup-node@v4 with : node-version : '20' - run : npm install -g @nitrosend/cli - name : Draft release email env : NITROSEND_API_KEY : ${{ secrets.NITROSEND_API_KEY }} run : | nitrosend mcp tools call nitro_compose_campaign --machine --args "$(cat { "name": "Release ${{ github.event.release.tag_name }}", "channel": "email", "subject": "Release ${{ github.event.release.tag_name }}", "body": "Release notes are live: ${{ github.event.release.html_url }}", "idempotency_key": "release-${{ github.event.release.id }}" } EOF )" The idempotency_key inside --args is what the platform dedupes against — set it from a stable, retry-survivable value like the release ID so a re-run of the job won't create duplicate drafts. Branch on exit codes for retry strategy: 75 retriable, 77 means rotate the API key, 78 means upgrade the CLI step. Daily account health check Run the dashboard read, fail the job on blockers: Copy #!/usr/bin/env bash set -euo pipefail result = $( nitrosend --machine ) blockers = $( echo " $result " | jq -r '.sidecars.blockers // [] | length' ) if [ " $blockers " -gt 0 ]; then echo " $result " | jq '.sidecars.blockers' exit 1 fi Local operating Quick status snapshot Three commands cover most "what's the state of my account" questions: Copy nitrosend # account state, blockers, next_action nitrosend mcp resources read nitro://account nitrosend mcp tools call nitro_get_insights --args '{"scope":"account"}' Pipe campaign results into a spreadsheet Copy nitrosend mcp tools call nitro_query \ --args '{"resource":"campaigns","page":1,"per_page":100}' \ --csv > campaigns.csv CSV mode is table-only — no envelope, no sidecars. Use it when you want raw rows. Purchase a plan or add prepaid funds The Nitrosend CLI invokes nitro_manage_billing over MCP directly. No payment-specific CLI or external runtime sits between the command and Nitrosend. Copy # Inspect account route, plan, and prepaid balance nitrosend mcp tools call nitro_manage_billing --machine \ --args '{"operation":"status"}' # List plans, then prepare owner-approved checkout nitrosend mcp tools call nitro_manage_billing --machine \ --args '{"operation":"plans"}' nitrosend mcp tools call nitro_manage_billing --machine \ --args '{"operation":"checkout","params":{"plan_id":2},"idempotency_key":"plan-pro-acme-2026-08-31"}' # Or prepare a separate prepaid funding purchase nitrosend mcp tools call nitro_manage_billing --machine \ --args '{"operation":"add_funds","params":{"amount_cents":5000,"currency":"USD"},"idempotency_key":"fund-validation-acme-2026-08-31"}' The command returns an approval URL and purchase ID when provider approval is needed. The account owner completes Checkout in a browser. Poll checkout_status until the plan is active , or funding_purchase_status until funds are credited . See Payments for agents for complete commands, Link and Stripe CLI terminology, and recovery rules. Replay a recent command Copy nitrosend recent # show the last 10 commands nitrosend redo 1 --explain # show what `redo 1` would run, without running recent and redo are local-only — they never call the API and they redact obvious secrets in their stored history. Tab completion Copy # bash nitrosend completion bash >> ~/.bashrc # zsh nitrosend completion zsh > ~/.config/zsh/completions/_nitrosend # fish nitrosend completion fish > ~/.config/fish/completions/nitrosend.fish Agent runners Plan, then act Use --explain to get a deterministic plan before deciding to execute. The plan is the same envelope shape as a real run, with would_execute: false . Copy plan = $( nitrosend mcp tools call nitro_compose_campaign \ --args "$( cat brief.json)" --explain --machine ) # Inspect the plan, decide, then run for real echo " $plan " | jq '.data' if [ "$( echo " $plan " | jq -r '.data.would_execute')" = "false" ]; then nitrosend mcp tools call nitro_compose_campaign \ --args "$( cat brief.json)" --machine fi Discover the contract before calling Cache the descriptor at startup. Don't guess flags. Copy nitrosend describe mcp tools call --json | jq '.input_schema' The descriptor includes safety class, dry-run support, idempotency policy, and an agent.suitable flag. Skip any command marked suitable: false from an autonomous runner. Approval handoff When an agent needs human sign-off mid-run, hand off to approve / reject and pause. The CLI exposes these as stable stubs today and will gain an SSE waiter endpoint in a future release. Copy # Agent emits a token, then waits. nitrosend approve $TOKEN # human runs this nitrosend reject $TOKEN # or this Read sidecars for next-action loops Don't write your own "what should I do next" logic. The platform already returns one. Read sidecars.next_action and sidecars.suggested_tool_calls and let it drive the loop. Copy result = $( nitrosend mcp tools call nitro_get_status --machine ) next = $( echo " $result " | jq -r '.sidecars.next_action // empty' ) [ -n " $next " ] && echo "Next: $next " See also Agent mode — the full machine-readable contract Project config — environments and the production guard MCP integrations overview — when to use a chat MCP client instead ### Command reference Path: `/cli/reference` Full Nitrosend CLI command reference, generated from the CLI's command descriptors This page is generated from COMMAND_DESCRIPTORS in the CLI repo. Every command listed here has a typed input schema, output schema, safety class, and idempotency policy. Use nitrosend describe --json to read the live descriptor for any command. Tip In agent runners, prefer nitrosend describe over this page. The descriptor returned by the CLI is always in sync with the binary you're running. Core Command Summary nitrosend Show account state, blockers, and suggested next actions. nitrosend describe Return descriptor, schemas, examples, and safety metadata for a command. nitrosend completion Generate shell completion. nitrosend recent Show redacted recent commands. nitrosend redo [index] Show or re-run a recent command. Auth Command Summary nitrosend login Authenticate with OAuth in the browser. nitrosend login --api-key Store an API-key profile. nitrosend logout Remove the active local profile. nitrosend whoami Show active auth source, profile, and API endpoint. MCP The CLI is the Nitrosend MCP, callable from a shell. These commands dispatch JSON-RPC over HTTP to the same endpoint chat MCP clients use. Command Summary nitrosend mcp initialize Initialise MCP handshake. nitrosend mcp tools list List MCP tools. nitrosend mcp tools call --args '{...}' Call an MCP tool. nitrosend mcp resources list List MCP resources. nitrosend mcp resources read Read an MCP resource (e.g. nitro://account ). nitrosend mcp prompts list List MCP prompts. nitrosend mcp prompts get --args '{...}' Get an MCP prompt. The full set of available tools ( nitro_get_status , nitro_compose_campaign , …) is documented under the MCP Tools tab. Data Read-only list commands call nitro_query and return paginated rows. Command Summary nitrosend flows list List flows. nitrosend campaigns list List campaigns. nitrosend contacts list List contacts. nitrosend lists list List contact lists. nitrosend templates list List templates. nitrosend suppressions list List account suppressions with bounce diagnostics. Agent These commands are stable stubs for agent approval handoff. They will gain SSE waiter behaviour in a future release. Command Summary nitrosend approve Approve a pending agent operation token. nitrosend reject Reject a pending agent operation token. Fixtures Test commands that exercise destructive safety UX without touching live data. Command Summary nitrosend fixture destroy --confirm Exercise typed-confirmation flow against a fixture. Global flags Flag Effect --json / --ndjson / --csv Output mode. --machine Implies --json --non-interactive --no-color --no-pager ; auto-issues idempotency keys. --non-interactive Refuse to prompt; fail closed. --dry-run Resolve and validate without performing side effects. Honoured where supported. --explain Return the resolved plan as data. No side effects. --yes Skip ordinary confirmations. Never bypasses typed confirmation. --trace Print timing diagnostics on stderr. --profile Override active profile. --api-url Override the MCP endpoint. --api-key One-shot API key for this invocation. --args / --json-args JSON-encoded arguments for mcp tools call and mcp prompts get . --confirm Typed confirmation for destructive commands. --no-color / --no-pager Strip ANSI / pagers. --no-browser Skip the browser open during OAuth login. See Agent mode for the full envelope shape and exit code map. ## API Reference ### Nitrosend API Path: `/api` Version: 1.1.6 Multi-channel marketing automation API. Send email campaigns, build automation flows, manage contacts and segments, track events, and configure your Brand Kit — all via a single REST API. ## Authentication All `/v1/my/*` endpoints require authentication via **Bearer token**. Three token types are accepted: 1. **API Key** — `Authorization: Bearer nskey_live_...` 2. **JWT** — `Authorization: Bearer ` (obtained from `POST /v1/login`) 3. **Shopify ID token** — supplied by App Bridge for an installed embedded app ## Pagination Paginated endpoints return these headers: - `X-Total-Count` — total records - `X-Total-Pages` — total pages - `X-Page-Number` — current page - `X-Next-Page` — next page (omitted on last page) - `X-Prev-Page` — previous page (omitted on first page) Use `page` and `limit` (or `per`) query params to control pagination. Maximum `limit` is 100, default is 30. ## Error Responses All errors return a consistent JSON shape: ```json { "code": 422, "message": "Description of error", "error": true, "validation_errors": { "field": ["error message"] } } ``` The `validation_errors` key is only present on 422 responses. #### Operations ##### GET /mcp Summary: Probe the MCP OAuth endpoint Unauthenticated probes receive the RFC 9728 protected-resource metadata URL in `WWW-Authenticate` so OAuth clients can continue discovery. Authenticated requests receive `405 Method Not Allowed` because this server does not offer a server-initiated event stream over GET. Use `POST /mcp` for MCP Streamable HTTP requests. Responses: - 401: MCP authentication required - 405: Authenticated GET is not supported by the MCP transport ##### POST /mcp Summary: MCP JSON-RPC endpoint JSON-RPC endpoint for the Nitrosend MCP server. Read `nitro://account`, `nitro://config`, or call `nitro_get_status` for bound account identity; there is no per-tool account override argument. Request body: application/json Responses: - 200: JSON-RPC response ##### POST /v1/signup Summary: Create a new account Request body: application/json Responses: - 201: Account created - 422: Validation failed ##### POST /v1/login Summary: Sign in and receive a JWT Request body: application/json Responses: - 200: Signed in - 401: Not authenticated ##### DELETE /v1/logout Summary: Sign out and revoke JWT Responses: - 204: Signed out ##### GET /v1/oauth/me Summary: Get OAuth popup context Returns the account-selection and subscription context used by the `/oauth/connect` popup. This endpoint accepts the normal Bearer JWT and, for popup resume flows, the authenticated browser session cookie established by Devise/OmniAuth. Responses: - 200: Popup context - 401: Not authenticated ##### POST /v1/oauth/subscribe Summary: Subscribe the selected OAuth popup account Finalizes plan selection for the `/oauth/connect` popup before the browser returns to the Doorkeeper consent screen. This endpoint accepts the normal Bearer JWT and, for popup resume flows, the authenticated browser session cookie established by Devise/OmniAuth. Request body: application/json Responses: - 200: Popup can continue to consent - 401: Not authenticated - 422: Account or plan selection failed ##### POST /v1/oauth/launch Summary: Create a CSRF-safe OAuth provider launch URL Mints a short-lived launch URL for Google or GitHub sign-in. The frontend follows the returned API-origin launch page, which renders the real POST form to `/auth/:provider` with a valid Rails authenticity token. This preserves OmniAuth's POST-only request phase and CSRF protection for both the app popup flow and the `/oauth/connect` agent popup flow. Request body: application/json Responses: - 200: Launch URL created - 400: Invalid provider or resume URL ##### GET /v1/my/user Summary: Get current user profile Responses: - 200: User profile - 401: Not authenticated ##### PATCH /v1/my/user Summary: Update current user profile Request body: application/json Responses: - 200: Updated user - 422: Validation failed ##### GET /v1/my/impersonation Summary: Get current impersonation status Responses: - 200: Current impersonation state - 401: Not authenticated ##### DELETE /v1/my/impersonation Summary: Revoke the active impersonation session Responses: - 200: Impersonation session revoked - 401: Not authenticated ##### GET /v1/my/affiliate Summary: Get the current account's Affiliate Center Session/JWT only. API keys are rejected. For an entitled account that is not linked locally, the first read starts one background reconciliation. That reconciliation links an existing Rewardful affiliate by the account owner's email or creates one when absent. Responses: - 200: Affiliate Center payload or temporary setup state - 403: Not authorized - 404: Resource not found ##### POST /v1/my/affiliate Summary: Reconcile the current account's affiliate immediately Session/JWT-only repair path. This is idempotent and uses the same existing-or-create behavior as background reconciliation. Request body: application/json Responses: - 200: Existing or already-linked affiliate - 201: Affiliate identity linked to the account - 403: Not authorized - 404: Resource not found - 422: Validation failed - 503: Upstream service unavailable ##### GET /v1/my/account Summary: Get account details Responses: - 200: Account with billing. Brands are listed by GET /v1/my/brands. ##### PATCH /v1/my/account Summary: Update account settings Request body: application/json Responses: - 200: Updated account - 422: Validation failed ##### GET /v1/my/delivery/status Summary: Inspect the email sending allowance and pacing Returns a read-only projection of the persisted delivery controls for the selected Brand. This endpoint does not authorize or reserve a send; every send still passes through the canonical admission authority. Pacing describes when admitted work can dispatch and is not itself an admission decision. Responses: - 200: Current email sender-capacity and pacing projection - 401: Not authenticated - 404: Resource not found ##### POST /v1/my/validation_operations/quote Summary: Quote explicit prepaid email validation Classifies one exact current-Brand audience, applies current cached and eligibility evidence, and returns the maximum prepaid charge. It does not hold funds, call a provider, create an operation, or mutate Contacts. Validation has no plan-included allowance. Request body: application/json Responses: - 200: Non-mutating validation quote - 400: Bad request - 401: Not authenticated - 404: Resource not found ##### POST /v1/my/validation_operations Summary: Start or resume explicit prepaid email validation Creates one durable operation and holds at most the quoted amount from direct prepaid funds. Reusing the same Idempotency-Key with the same audience returns the same operation; after funding, the same request resumes a needs_funding operation. A changed audience conflicts. Parameters: - `Idempotency-Key` (header, string, required) - Exact-request idempotency key; changed normalized input conflicts. Request body: application/json Responses: - 202: Durable validation operation accepted or replayed - 400: Bad request - 401: Not authenticated - 404: Resource not found - 409: Request conflicts with durable lifecycle or idempotency state - 422: Validation failed ##### GET /v1/my/validation_operations/{id} Summary: Get an email validation operation Parameters: - `id` (path, string, required) - Opaque operation_id returned by the create endpoint. Responses: - 200: Current durable operation state - 401: Not authenticated - 404: Resource not found ##### GET /v1/my/validation_operations/{id}/items Summary: List per-candidate email validation results Parameters: - `id` (path, string, required) - Opaque operation_id returned by the create endpoint. - `page` (query, integer, optional) - `per` (query, integer, optional) Responses: - 200: Paginated operation items - 401: Not authenticated - 404: Resource not found ##### GET /v1/my/accounts Summary: List accessible accounts (paginated) Parameters: - `page` (query, integer, optional) - `per` (query, integer, optional) Responses: - 200: Accessible accounts ##### GET /v1/my/managed_accounts Summary: List the selected manager account's client portfolio Parameters: - `page` (query, integer, optional) - `per` (query, integer, optional) - `q` (query, string, optional) - `status` (query, object, optional) - Use needs_setup to group preparing, awaiting owner, payment required, and payment issue rows. - `sort` (query, string, optional) - `direction` (query, string, optional) Responses: - 200: Bounded managed-client portfolio - 403: Not authorized ##### POST /v1/my/managed_accounts Summary: Provision a paid-required, client-owned account Parameters: - `Idempotency-Key` (header, string, required) - Exact-request idempotency key; changed normalized input conflicts. Request body: application/json Responses: - 200: Exact idempotent replay - 201: Client provisioned in preparing state; no owner invitation has been sent - 403: Not authorized - 409: Request conflicts with durable lifecycle or idempotency state - 422: Validation failed ##### GET /v1/my/managed_accounts/{id} Summary: Get one managed-client portfolio row Parameters: - `id` (path, integer, required) Responses: - 200: Managed-client portfolio row - 403: Not authorized - 404: Resource not found ##### POST /v1/my/managed_accounts/{id}/resend_invitation Summary: Invalidate the prior invitation and queue a bounded replacement for owner-only delivery Parameters: - `id` (path, integer, required) Responses: - 200: Updated portfolio row; no invitation capability is returned - 403: Not authorized - 409: Request conflicts with durable lifecycle or idempotency state ##### POST /v1/my/managed_accounts/{id}/send_invitation Summary: Queue the first bounded owner invitation after client setup is ready Parameters: - `id` (path, integer, required) Responses: - 200: Updated portfolio row; no invitation capability is returned - 403: Not authorized - 409: Request conflicts with durable lifecycle or idempotency state ##### POST /v1/my/managed_accounts/{id}/payment_reminder Summary: Queue a cooldown-bound payment reminder to the owner Parameters: - `id` (path, integer, required) Responses: - 204: Reminder accepted for delivery - 403: Not authorized - 409: Request conflicts with durable lifecycle or idempotency state ##### POST /v1/my/managed_accounts/{id}/release Summary: End the selected manager's relationship with the client Parameters: - `id` (path, integer, required) Responses: - 200: Ended managed-client relationship - 403: Not authorized - 409: Request conflicts with durable lifecycle or idempotency state ##### GET /v1/my/account/provisioning_credentials Summary: List non-secret provisioning credential metadata Responses: - 200: Provisioning credential metadata - 403: Not authorized ##### POST /v1/my/account/provisioning_credentials Summary: Issue a provisioning credential and reveal its secret once Request body: application/json Responses: - 201: One-time credential result - 403: Not authorized - 422: Validation failed ##### DELETE /v1/my/account/provisioning_credentials/{id} Summary: Revoke one manager-bound provisioning credential Parameters: - `id` (path, integer, required) Responses: - 200: Revoked credential metadata - 403: Not authorized - 404: Resource not found ##### POST /v1/managed_account_claims/inspect Summary: Inspect a bounded owner consent capability Request body: application/json Responses: - 200: Bounded claim projection - 410: One-time capability is invalid, expired, rotated, or consumed ##### POST /v1/managed_account_claims/complete Summary: Record one-time consent for the exact authenticated owner Request body: application/json Responses: - 200: Consent recorded; status remains payment_required until paid activation - 403: Not authorized - 410: One-time capability is invalid, expired, rotated, or consumed - 422: Validation failed ##### GET /v1/partner/managed_accounts Summary: List managed clients using a manager-bound credential Responses: - 200: Managed-client portfolio - 401: Not authenticated - 403: Not authorized ##### POST /v1/partner/managed_accounts Summary: Provision a paid-required client-owned account Parameters: - `Idempotency-Key` (header, string, required) - Exact-request idempotency key; changed normalized input conflicts. Request body: application/json Responses: - 200: Exact idempotent replay - 201: Client provisioned in preparing state; no owner invitation has been sent - 401: Not authenticated - 403: Not authorized - 409: Request conflicts with durable lifecycle or idempotency state - 422: Validation failed ##### GET /v1/partner/managed_accounts/{id} Summary: Get one manager-scoped portfolio row Parameters: - `id` (path, integer, required) Responses: - 200: Managed-client portfolio row - 401: Not authenticated - 403: Not authorized - 404: Resource not found ##### POST /v1/partner/managed_accounts/{id}/resend_invitation Summary: Invalidate the prior invitation and queue a bounded replacement for owner-only delivery Parameters: - `id` (path, integer, required) Responses: - 200: Updated portfolio row; no invitation capability is returned - 401: Not authenticated - 403: Not authorized - 409: Request conflicts with durable lifecycle or idempotency state ##### POST /v1/partner/managed_accounts/{id}/send_invitation Summary: Queue the first bounded owner invitation after client setup is ready Parameters: - `id` (path, integer, required) Responses: - 200: Updated portfolio row; no invitation capability is returned - 401: Not authenticated - 403: Not authorized - 409: Request conflicts with durable lifecycle or idempotency state ##### GET /v1/partner/managed_accounts/{provisioning_id}/credentials Summary: List non-secret agent credential metadata for one managed client Parameters: - `provisioning_id` (path, integer, required) - Manager-scoped provisioning row id. Responses: - 200: Agent credential metadata; plaintext secrets are never listed - 401: Not authenticated - 403: Not authorized - 404: Resource not found ##### POST /v1/partner/managed_accounts/{provisioning_id}/credentials Summary: Issue one grant- and Brand-pinned agent credential Parameters: - `provisioning_id` (path, integer, required) - Manager-scoped provisioning row id. Request body: application/json Responses: - 201: One-time credential result; the secret cannot be recovered - 401: Not authenticated - 403: Not authorized - 404: Resource not found - 409: Request conflicts with durable lifecycle or idempotency state - 422: Validation failed ##### DELETE /v1/partner/managed_accounts/{provisioning_id}/credentials/{id} Summary: Revoke one agent credential immediately Parameters: - `provisioning_id` (path, integer, required) - Manager-scoped provisioning row id. - `id` (path, integer, required) Responses: - 200: Revoked credential metadata - 401: Not authenticated - 403: Not authorized - 404: Resource not found ##### GET /v1/my/account/management_grant Summary: Get the current account's pending or active management grant Only the canonical owner of the selected managed account may use this endpoint. Responses: - 200: Current management grant - 403: Not authorized - 404: Resource not found ##### POST /v1/my/account/management_grant/confirm Summary: Reconcile a consented, paid managed-account grant This endpoint cannot record owner consent or bypass paid activation. Request body: application/json Responses: - 200: Active management grant - 400: Bad request - 403: Not authorized - 404: Resource not found - 409: The grant cannot transition from its current state ##### POST /v1/my/account/management_grant/revoke Summary: Revoke an exact pending or active management grant Revocation takes effect on the next REST or MCP request and is idempotent for the exact grant. Request body: application/json Responses: - 200: Revoked management grant - 400: Bad request - 403: Not authorized - 404: Resource not found - 409: The grant cannot transition from its current state ##### GET /v1/my/account/team Summary: Get team metadata for the current account Responses: - 200: Team metadata ##### GET /v1/my/account/reach Summary: How far one send to an audience gets on the current plan Read-only. Reports the recipients per day at the account's sending standing and the emails left this month on the current plan, whether they cover a full send to the audience, and the cheapest listed plan whose first month would. The numbers come from the plan catalogue and this month's usage; the summary sentence is the one every surface shows. Silent (no summary, no recommendation) when the current plan covers the audience. Parameters: - `audience` (query, integer, required) - Number of recipients of one full send Responses: - 200: Reach on the current plan and the recommended plan - 422: audience is not a positive whole number (error_code audience_invalid) ##### GET /v1/my/account/memberships Summary: List account memberships (paginated) Parameters: - `page` (query, integer, optional) - `per` (query, integer, optional) Responses: - 200: Memberships ##### DELETE /v1/my/account/memberships/{id} Summary: Remove a membership Parameters: - `id` (path, integer, required) Responses: - 200: Removed membership ##### PATCH /v1/my/account/memberships/{id} Summary: Update a membership role Parameters: - `id` (path, integer, required) Request body: application/json Responses: - 200: Updated membership - 422: Validation failed ##### POST /v1/my/subscription Summary: Create a subscription Request body: application/json Responses: - 201: Subscription created - 422: Validation failed ##### PUT /v1/my/subscription/change Summary: Change the current subscription plan Request body: application/json Responses: - 200: Subscription updated - 422: Validation failed ##### POST /v1/my/subscription/checkout Summary: Start a subscription checkout Creates or replays one account-scoped plan purchase through the authoritative billing provider. A Stripe-backed active subscription is changed in place rather than creating a parallel subscription. Shopify-managed accounts receive a Shopify-hosted approval URL and never receive a Stripe checkout URL. Parameters: - `Idempotency-Key` (header, string, optional) - Optional stable retry key. The server also reuses an open same-plan checkout. Request body: application/json Responses: - 200: Subscription changed without external approval - 201: Provider-hosted checkout created - 403: Billing provider is not ready for checkout - 404: Requested plan is not available - 409: Another checkout is pending or the idempotency key conflicts - 422: Shopify rejected the requested billing terms - 503: The billing provider could not be reached ##### GET /v1/my/subscription/checkout_status Summary: Reconcile and read a plan purchase Reads the requested plan while approval is pending. Stripe and Shopify are read back before the local state is returned. Omit purchase_id to inspect the latest pending checkout or the current subscription. Parameters: - `purchase_id` (query, integer, optional) Responses: - 200: Reconciled plan purchase status - 404: Requested plan purchase was not found - 503: Provider state could not be verified ##### GET /v1/my/billing/funding Summary: Read the current prepaid funding projection and available instruments Responses: - 200: Current prepaid funding status ##### POST /v1/my/billing/funding/purchases Summary: Start an add-funds purchase with an optional hosted instrument Creates or replays one local funding purchase. Current public instruments return a provider-hosted approval URL. No request-side credential field is exposed by this endpoint. Parameters: - `Idempotency-Key` (header, string, required) Request body: application/json Responses: - 200: Existing idempotent funding purchase - 201: Funding purchase created - 409: Idempotency key was used for different funding input - 422: Amount or selected funding instrument is unavailable ##### GET /v1/my/billing/funding/purchases/{id} Summary: Read an account-scoped funding purchase Parameters: - `id` (path, string, required) - Local purchase ID or opaque Stripe Checkout Session ID Responses: - 200: Funding purchase status - 404: Funding purchase not found ##### POST /v1/my/subscription/coupon_preview Summary: Preview a promotion code or coupon for a subscription plan Validates a customer-entered Stripe promotion code or coupon ID against a paid plan and returns display totals for the checkout summary. The final subscription create/change request must still send `coupon_code`; preview data is not trusted as payment input. Request body: application/json Responses: - 200: Coupon preview - 422: Validation failed ##### GET /v1/my/account/invites Summary: List account invites (paginated) Parameters: - `page` (query, integer, optional) - `per` (query, integer, optional) Responses: - 200: Invites ##### POST /v1/my/account/invites Summary: Create an account invite Request body: application/json Responses: - 201: Created invite - 422: Validation failed ##### DELETE /v1/my/account/invites/{id} Summary: Revoke an invite Parameters: - `id` (path, integer, required) Responses: - 200: Revoked invite ##### POST /v1/my/account/invites/{id}/accept Summary: Accept an invite token Parameters: - `id` (path, string, required) Responses: - 200: Accepted invite - 422: Validation failed ##### GET /v1/my/contacts Summary: List contacts (paginated) Parameters: - `page` (query, integer, optional) - `limit` (query, integer, optional) - `search` (query, string, optional) - Full-text search across name, email, phone - `filters` (query, unknown, optional) - Canonical structured audience filters from the registry exposed by - `list_id` (query, integer, optional) - Legacy shortcut for a `contact_list in [id]` filter. - `tag` (query, string, optional) - Legacy shortcut for a `contact_tag eq tag` filter. Tags are stored - `sort` (query, string, optional) - Column to sort by. `created_at` orders by the contact's creation date; - `direction` (query, string, optional) - Sort direction. Only applies when `sort` is set. Responses: - 200: Paginated contacts - 503: A read did not finish within the request's database time limit (`error_code: query_timeout`). Nothing was changed; retry after the Retry-After interval. Any GET can return this. ##### POST /v1/my/contacts Summary: Create a contact Request body: application/json Responses: - 200: Existing contact updated in place. Returned when the submitted email - 201: Contact created - 422: Validation failed ##### GET /v1/my/contacts/{id} Summary: Get a contact Parameters: - `id` (path, integer, required) Responses: - 200: Contact with channels - 404: Resource not found ##### DELETE /v1/my/contacts/{id} Summary: Delete a contact Parameters: - `id` (path, integer, required) Responses: - 200: Deleted contact ##### PATCH /v1/my/contacts/{id} Summary: Update a contact Parameters: - `id` (path, integer, required) Request body: application/json Responses: - 200: Updated contact - 422: Validation failed ##### POST /v1/my/contacts/enrichment/quote Summary: Quote profile enrichment for one or more Contacts Resolves already-current and same-Brand reusable profile evidence before provider work. This endpoint never calls a provider or reserves money. Profile enrichment is priced per successful outcome and has no plan-included allowance. Request body: application/json Responses: - 200: Exact maximum-charge quote and work classification - 401: Not authenticated - 422: Validation failed ##### POST /v1/my/contacts/enrichment Summary: Queue profile enrichment for one or more Contacts Holds the quoted maximum, then queues bounded Contact work. Current evidence is skipped without charge. Fresh same-Brand evidence and usable provider results each commit one outcome; no-match and failures void the hold. This operation does not verify, subscribe, suppress, or qualify an email channel. Parameters: - `Idempotency-Key` (header, string, required) Request body: application/json Responses: - 200: Exact idempotent replay - 202: Enrichment queued - 400: Bad request - 401: Not authenticated - 402: Account funding changed before the enrichment hold was created - 409: Idempotency-Key reused with different Contact IDs - 422: Validation failed ##### GET /v1/my/contacts/{id}/timeline Summary: Unified activity timeline for a contact Read-only, keyset-paginated feed merging the contact's lifecycle `events` and email `activities` into one chronological stream (newest first). Entries are normalised and whitelisted — raw rows are never exposed. Page using the `cursor` returned as `next_cursor`. Parameters: - `id` (path, integer, required) - `cursor` (query, string, optional) - Opaque keyset cursor from a prior `next_cursor`. - `limit` (query, integer, optional) Responses: - 200: A page of timeline entries, newest first - 401: Not authenticated ##### GET /v1/my/contacts/{id}/enrichment Summary: Enrichment facts for a contact Read-only enrichment facts resolved into display rows. Facts sourced from an integration you have connected are attributed with that integration's name; facts from other sources are returned unattributed. Parameters: - `id` (path, integer, required) Responses: - 200: The contact's resolved enrichment rows - 401: Not authenticated ##### GET /v1/my/contacts/fields Summary: List the field catalog for this account Returns the structured field catalog: one row per known field for this account, with key, category, type, label, promotion state, and async fill-rate. Fields are lazily registered the first time a key is seen (import, API write, or enrichment). Use `PATCH /v1/my/contacts/fields/:id` to update `promoted` or `label`. Responses: - 200: Field catalog rows ordered by category and label - 401: Not authenticated ##### PATCH /v1/my/contacts/fields/{id} Summary: Update a field catalog row (owner/admin only) Updates the `promoted` flag and/or the human-readable `label` for a field catalog row. Restricted to account owners and admins. The `key`, `category`, and `field_type` of a row are immutable. Parameters: - `id` (path, integer, required) Request body: application/json Responses: - 200: Updated field catalog row - 401: Not authenticated - 403: Not authorized - 404: Resource not found - 422: Validation failed ##### POST /v1/my/images Summary: Ingest an image media asset REST adapter over the same image ingest capability used by MCP. Send exactly one source: `image_data`, `image_url`, multipart `file`, or a direct-upload `signed_id` created with purpose `image` or `media_asset`. The v1 media contract supports `media_kind: image` only. Request body: application/json, multipart/form-data Responses: - 201: Ingested image asset - 422: Validation failed ##### POST /v1/direct_uploads Summary: Reserve a direct-upload blob Creates an Active Storage direct-upload reservation and returns a `signed_id`, presigned upload URL, and required upload headers. For CSV imports, send `purpose: import`, PUT the file bytes to `direct_upload.url`, then submit the returned `signed_id` to `POST /v1/my/imports`. For image media assets, send `purpose: image` or `purpose: media_asset`, PUT the image bytes to `direct_upload.url`, then submit the returned `signed_id` to `POST /v1/my/images`. Request body: application/json Responses: - 200: Direct-upload reservation - 422: Validation failed ##### GET /v1/my/imports Summary: List import jobs Parameters: - `page` (query, integer, optional) - `limit` (query, integer, optional) Responses: - 200: Paginated import jobs ##### POST /v1/my/imports Summary: Start a CSV import Enqueues an import job from an Active Storage direct-upload `signed_id`. Create the blob through `POST /v1/direct_uploads` with `purpose=import`, PUT the file to the returned direct-upload URL, then submit the returned `signed_id` here. Poll `GET /v1/my/imports/{id}` for status, row counts, and row-level errors. For contact imports, pass `options` as a JSON object or JSON string with `list_ids` to assign imported contacts to one or more lists, e.g. `{"list_ids":[88]}`. Request body: application/json Responses: - 201: Import queued - 422: Validation failed ##### GET /v1/my/imports/spec Summary: Get import schema metadata Parameters: - `resource` (query, string, optional) - Optional resource name. Omit to list all schemas. Responses: - 200: Import schema metadata - 422: Validation failed ##### GET /v1/my/imports/{id} Summary: Get import job status Parameters: - `id` (path, integer, required) Responses: - 200: Import job with status, counts, and row errors - 404: Resource not found ##### DELETE /v1/my/imports/{id} Summary: Delete an import record Parameters: - `id` (path, integer, required) Responses: - 200: Deleted import - 404: Resource not found ##### POST /v1/my/imports/{id}/cancel Summary: Cancel a pending or processing import Parameters: - `id` (path, integer, required) Responses: - 200: Canceled import - 404: Resource not found - 422: Validation failed ##### GET /v1/my/exports Summary: List export jobs Parameters: - `page` (query, integer, optional) - `limit` (query, integer, optional) Responses: - 200: Paginated export jobs ##### POST /v1/my/exports Summary: Start a contact export Enqueues an export job that builds a CSV of the brand's contacts and attaches it to the export record. The same filters as `GET /v1/my/contacts` are supported (`search`, `list_id`, `tag`). Custom fields stored on contacts are emitted as additional CSV columns. Poll `GET /v1/my/exports/{id}` until `ready` is `true`, then download the file from the returned `download_path`. Request body: application/json Responses: - 201: Export queued - 422: Validation failed ##### GET /v1/my/exports/{id} Summary: Get an export job Parameters: - `id` (path, integer, required) Responses: - 200: Export job - 404: Resource not found ##### GET /v1/my/exports/{id}/download Summary: Download a completed export file Returns the CSV file once the export status is `complete`. Parameters: - `id` (path, integer, required) Responses: - 200: CSV file - 404: Resource not found - 422: Export is not ready ##### GET /v1/my/lists Summary: List contact lists (paginated) Parameters: - `name` (query, string, optional) - Exact case-insensitive list name filter - `page` (query, integer, optional) - `limit` (query, integer, optional) Responses: - 200: Paginated contact lists ##### POST /v1/my/lists Summary: Create a contact list Request body: application/json Responses: - 201: List created - 422: Validation failed ##### GET /v1/my/lists/{id} Summary: Get a contact list Parameters: - `id` (path, integer, required) Responses: - 200: Contact list - 404: Resource not found ##### DELETE /v1/my/lists/{id} Summary: Delete a contact list Parameters: - `id` (path, integer, required) Responses: - 200: Deleted list ##### PATCH /v1/my/lists/{id} Summary: Update a contact list Parameters: - `id` (path, integer, required) Request body: application/json Responses: - 200: Updated list - 422: Validation failed ##### GET /v1/my/lists/{id}/delete_warning Summary: Get delete warning metadata for a contact list Parameters: - `id` (path, integer, required) Responses: - 200: Flows connected to this list ##### POST /v1/my/lists/{id}/contacts/bulk Summary: Add or remove existing contacts from a list by email Public REST equivalent of the list membership batch primitive. The endpoint resolves existing contacts by email within the current brand and adds or removes memberships idempotently. It does not create contacts; emails with no current-brand contact are returned in `not_found`. Parameters: - `id` (path, integer, required) Request body: application/json Responses: - 200: Bulk list membership result - 404: Resource not found - 422: Validation failed ##### GET /v1/my/segments Summary: List all segments (paginated) Parameters: - `page` (query, integer, optional) - `per` (query, integer, optional) Responses: - 200: Segments ##### POST /v1/my/segments Summary: Create a segment `filters` must contain at least one condition. Empty filters (`[]`, `{}` or a group with no conditions) would match every contact, so they are rejected with a 422 and `validation_errors.filters`. To reach every contact, target the All contacts audience instead of a segment. Request body: application/json Responses: - 201: Segment created - 422: Validation failed ##### GET /v1/my/segments/{id} Summary: Get a segment Parameters: - `id` (path, integer, required) Responses: - 200: Segment - 404: Resource not found ##### DELETE /v1/my/segments/{id} Summary: Delete a segment Parameters: - `id` (path, integer, required) Responses: - 200: Deleted segment ##### PATCH /v1/my/segments/{id} Summary: Update a segment An update cannot remove every filter condition from a segment that has conditions; that returns a 422 with `validation_errors.filters`. Segments stored without conditions before this rule can still be renamed or re-saved with empty filters. Parameters: - `id` (path, integer, required) Request body: application/json Responses: - 200: Updated segment - 422: Validation failed ##### POST /v1/my/segments/count Summary: Count contacts matching filters Preview how many contacts match a set of segment filters without creating or saving a segment. Request body: application/json Responses: - 200: Contact count - 422: Validation failed ##### POST /v1/my/segments/preview Summary: Preview contacts matching filters Preview a prospective segment without saving it. Returns a live count, a bounded contact sample, and bounded overlap with existing segments. Invalid filters fail closed with `invalid_filter`. Request body: application/json Responses: - 200: Segment preview - 422: Validation failed ##### GET /v1/my/saved_views Summary: List saved views visible to the current user Returns all shared views in the current account plus the caller's own private views. Optionally filtered by surface. Parameters: - `surface` (query, string, optional) - Filter views by surface - `page` (query, integer, optional) - `limit` (query, integer, optional) Responses: - 200: Saved views ##### POST /v1/my/saved_views Summary: Create a saved view Creates a new saved view owned by the current user. Defaults to private visibility if not specified. Request body: application/json Responses: - 201: Saved view created - 422: Validation failed ##### DELETE /v1/my/saved_views/{id} Summary: Delete a saved view Deletes a saved view. Allowed only for the creator or the account owner. Other members receive 403. Parameters: - `id` (path, integer, required) Responses: - 200: Deleted saved view - 403: Not authorized - 404: Resource not found ##### PATCH /v1/my/saved_views/{id} Summary: Update a saved view Updates a saved view. Allowed only for the creator or the account owner. Other members receive 403. Parameters: - `id` (path, integer, required) Request body: application/json Responses: - 200: Updated saved view - 403: Not authorized - 404: Resource not found - 422: Validation failed ##### POST /v1/my/saved_views/{id}/fork Summary: Fork a saved view into a new private view Clones an existing view (typically a shared view) into a new private view owned by the caller. The original is not modified. Parameters: - `id` (path, integer, required) Responses: - 201: Forked saved view - 404: Resource not found - 422: Validation failed ##### GET /v1/my/campaigns Summary: List campaigns (paginated) Parameters: - `page` (query, integer, optional) - `limit` (query, integer, optional) Responses: - 200: Paginated campaigns ##### POST /v1/my/campaigns Summary: Create a campaign Request body: application/json Responses: - 201: Campaign created - 422: Validation failed ##### GET /v1/my/campaigns/{id} Summary: Get a campaign Parameters: - `id` (path, integer, required) Responses: - 200: Campaign with trigger, template, and templates - 404: Resource not found ##### DELETE /v1/my/campaigns/{id} Summary: Delete a campaign Parameters: - `id` (path, integer, required) Responses: - 200: Deleted campaign ##### PATCH /v1/my/campaigns/{id} Summary: Update a campaign Updates the campaign. When the campaign is no longer editable (see the `editable` field on the Campaign schema — false for live/paused/ completed/cancelled/archived, and for scheduled campaigns within 5 minutes of their send time), only name-only payloads and status transitions (Resume, Cancel) are accepted. Any other attribute returns 422 with `error_code: "campaign_locked"`. Use the `/duplicate` endpoint to fork a sent campaign into a new draft. Parameters: - `id` (path, integer, required) Request body: application/json Responses: - 200: Updated campaign - 409: Template version conflict - 422: Validation failed ##### POST /v1/my/campaigns/{id}/duplicate Summary: Duplicate a campaign Creates a new draft campaign that copies the source's audience (trigger.audience_type, contact_list_ids, segment_id, exclude_segment_ids, exclude_contact_list_ids) and template content (design, subject, preheader, body, from_name, from_email, reply_to). Resets status to draft, approval_state to pending_review, scheduled_at to null, and trigger.event to manual. Works on any status — this is how clients fork a sent campaign into a new draft. Sessions, activities, approvals, and metrics are not copied. Pass `cancel_source: true` to atomically cancel the source campaign in the same transaction as the duplicate — useful for 'cancel and duplicate' flows on paused campaigns. Parameters: - `id` (path, integer, required) Request body: application/json Responses: - 201: New draft campaign - 404: Resource not found ##### GET /v1/my/campaigns/{id}/render Summary: Render a campaign email to HTML Renders the campaign's current template through the shared preview renderer. Parameters: - `id` (path, integer, required) Responses: - 200: Rendered HTML - 404: Resource not found - 422: Validation failed ##### GET /v1/my/campaigns/{id}/readiness Summary: Read saved campaign readiness and capacity guidance Read-only structural checks and the deduplicated saved audience count. Capacity warnings are informational, not approval or admission gates. Save pending draft changes before requesting this projection. Parameters: - `id` (path, integer, required) Responses: - 200: Structural readiness with optional capacity guidance - 404: Resource not found - 503: A read did not finish within the request's database time limit (`error_code: query_timeout`). Nothing was changed; retry after the Retry-After interval. Any GET can return this. ##### GET /v1/my/campaigns/{id}/delivery Summary: Get current campaign delivery progress Polls the current campaign send using the same `Campaign::SendProgress` payload exposed on the campaign serializer. Active sends return `status: "sending"` with `poll_after_seconds`; terminal sends return `status: "completed"` or `status: "paused"` when a deliverability guard stopped the active send. Campaigns that have not started delivery return `status: "not_started"`. Polling also refreshes server-side progress: queued reservations older than 15 minutes are marked failed before the response is returned, so `failed`/`pending` can change on a poll even when no provider webhook has arrived. Parameters: - `id` (path, integer, required) Responses: - 200: Current campaign delivery progress - 404: Resource not found ##### POST /v1/my/campaigns/{id}/send_test Summary: Send a test email for a campaign Thin adapter over the same test-send service used by template tests. Use `contact_id` to send directly to a contact, or `sample_contact_id` to personalize explicit test recipients without sending to that contact. The two contact parameters are mutually exclusive. Test emails go to the account's own people (owner, members, an active managing account's people) and to addresses at the account's verified domains. Up to 10 other addresses per 30 days are allowed; past that, each refused recipient is reported in `results` with `status: failed` and the reason in `error`, and the other recipients still receive the test. Supply one fresh `Idempotency-Key` for each user-initiated send and reuse that exact key for transport retries. Parameters: - `id` (path, integer, required) - `Idempotency-Key` (header, string, required) Request body: application/json Responses: - 200: Test email sent - 404: Resource not found - 422: Validation failed ##### POST /v1/my/campaigns/{id}/send Summary: Send a campaign Delivers the exact persisted campaign snapshot. Save content, audience, and other authoring changes through the campaign update endpoint first. This endpoint rejects authoring fields rather than merging them during delivery. Pass `deliver_at` (RFC 3339) to schedule; omit it to send now. Both `expected_campaign_updated_at` and `template_if_version` are required so a stale caller cannot send a newer draft accidentally. Returns 422 `campaign_locked` if the campaign is not editable (live, paused, completed, cancelled, archived, or scheduled within 5 minutes of send). Returns 409 `duplicate_campaign_schedule` or `duplicate_campaign_send` when a repeat attempt would create another schedule or overlap an active send; poll `/delivery`, cancel, or edit the existing scheduled campaign instead. Parameters: - `id` (path, integer, required) Request body: application/json Responses: - 200: Campaign sent - 409: Template version conflict or duplicate send attempt - 422: Validation failed ##### GET /v1/my/templates Summary: List all templates (paginated) Returns a summary view with section counts (not full design). Parameters: - `page` (query, integer, optional) - `per` (query, integer, optional) - `scope` (query, string, optional) - Use `standalone` for reusable library templates. Omit or use `all` to retain the complete designed-template result. - `include_previews` (query, boolean, optional) - When set, each summary includes rendered preview_html (cached per template version) Responses: - 200: Template summaries ##### POST /v1/my/templates Summary: Create a standalone template Creates one standalone template. Idempotency-Key is required; an exact retry returns the original template, while reuse with changed input returns 409. Parameters: - `Idempotency-Key` (header, string, required) Request body: application/json Responses: - 200: Exact idempotent replay of an existing template - 201: Created template - 400: Bad request - 409: Idempotency-Key reused with changed input - 422: Validation failed ##### GET /v1/my/templates/{id} Summary: Get a template with full design Parameters: - `id` (path, integer, required) Responses: - 200: Full template including design - 404: Resource not found ##### DELETE /v1/my/templates/{id} Summary: Delete an unused standalone template Parameters: - `id` (path, integer, required) - `if_version` (query, integer, required) - Required optimistic concurrency token. Use the current template.version. Responses: - 200: Template deleted - 404: Resource not found - 409: Template version conflict or deletion blocked by message/flow history ##### PATCH /v1/my/templates/{id} Summary: Update a template Parameters: - `id` (path, integer, required) Request body: application/json Responses: - 200: Updated template - 409: Template version conflict - 422: Validation failed ##### GET /v1/my/templates/{id}/render Summary: Render an existing template to HTML Renders the template through the shared preview renderer. Parameters: - `id` (path, integer, required) Responses: - 200: Rendered HTML - 404: Resource not found - 422: Validation failed ##### POST /v1/my/templates/{id}/send_test Summary: Send a test email Send a test email for the given template. Provide `email` for an explicit recipient, `contact_id` to use a contact's email (with merge-tag personalization), or `sample_contact_id` to personalize an explicit test recipient without sending to the contact. `contact_id` and `sample_contact_id` are mutually exclusive. Omit all recipient inputs to use the brand's saved test recipients, falling back to the account owner. Test emails go to the account's own people (owner, members, an active managing account's people) and to addresses at the account's verified domains. Up to 10 other addresses per 30 days are allowed; past that, each refused recipient is reported in `results` with `status: failed` and the reason in `error`, and the other recipients still receive the test. Supply one fresh `Idempotency-Key` for each user-initiated send and reuse that exact key for transport retries. Parameters: - `id` (path, integer, required) - `Idempotency-Key` (header, string, required) Request body: application/json Responses: - 200: Test email sent - 404: Resource not found - 422: Validation failed ##### POST /v1/my/templates/preview Summary: Render an email design to HTML Request body: application/json Responses: - 200: Rendered HTML - 400: Bad request ##### GET /v1/my/templates/spec Summary: Get the email component schema Returns the full schema for email design sections including all component types, their props, required fields, and defaults. Sourced from `config/email_components.yml`. Responses: - 200: Email component schema ##### GET /v1/my/templates/library Summary: List the system template library Returns the curated system template catalog. Each template ships two faces: its original art direction (design/preview_html) and a brand-matched variant with the palette overrides stripped so the account's colors flow in (branded_design/branded_preview_html). Sourced from the canonical catalog in `config/email_templates/`, ordered by `config/email_templates/index.yml`. Responses: - 200: Library templates with original and brand-matched variants ##### POST /v1/my/templates/generate Summary: AI-generate an email draft Generate, regenerate, or refine a complete template, campaign-email, or selected flow-email draft through the same authoring spine. This never persists or sends. A flow target addresses exactly one existing email action, never the whole flow. Reusing an idempotency key with changed authoring input returns a conflict. Parameters: - `Idempotency-Key` (header, string, required) - Retry-stable identity for this generation request. Request body: application/json Responses: - 200: Generated email design - 400: Invalid authoring request - 409: Generation or template version conflict - 422: Validation error - 429: Rate limit exceeded - 503: Generation failed ##### POST /v1/my/flow_demo/claim Summary: Save an edited guest flow after sign-in Copies an anonymous demo draft and its scanned Brand Kit into the authenticated account. Only the ready draft of the demo's current revision can be claimed. The guest token is scoped to one 24-hour demo; exact retries return the same flow. The copied flow remains a draft and does not send until separately approved and activated. Parameters: - `Idempotency-Key` (header, string, required) Request body: application/json Responses: - 201: Claimed draft flow and target Brand - 409: The same demo was claimed with different input or by another account - 422: Expired demo, no ready draft for the current revision, invalid graph, or Brand limit reached ##### GET /v1/my/flows Summary: List automation flows (paginated) Returns standalone flows only (excludes campaign-attached flows). Parameters: - `page` (query, integer, optional) - `limit` (query, integer, optional) Responses: - 200: Paginated flows ##### POST /v1/my/flows Summary: Create a flow Creates a draft flow without delivery authority. Idempotency-Key is required; an exact retry returns the original flow, while reuse with changed input returns 409. Approve or activate in a separate request. Parameters: - `Idempotency-Key` (header, string, required) Request body: application/json Responses: - 200: Exact idempotent replay of an existing flow - 201: Flow created - 400: Bad request - 409: Idempotency-Key reused with changed input - 422: Validation failed ##### POST /v1/my/flows/generate Summary: Write a draft flow for the brand from a goal or a template Send a goal or a template_id. A goal is planned into the flow's trigger, waits, checks and emails; a template (one whose `brand_writable` is true in GET /v1/my/flow_templates) keeps its own. Every email is then written for the current brand and the flow is saved as a pending-review draft, using one AI generation. A template that starts on `list_add` needs `contact_list_id`. Idempotency-Key is required: an exact retry returns the original flow, a duplicate sent while the first is still writing returns 409 generation_in_progress, and reuse with a changed request returns 409 idempotency_conflict. Parameters: - `Idempotency-Key` (header, string, required) Request body: application/json Responses: - 200: Exact idempotent replay of an existing flow - 201: Flow written and saved as a draft - 400: Bad request - 409: The first request with this key is still writing, or the key was reused with a changed request - 422: AI generation limit reached (ai_limit_reached), a template that cannot be written - 503: Planning or writing failed; retry with the same key ##### GET /v1/my/flows/{id} Summary: Get a flow Parameters: - `id` (path, integer, required) Responses: - 200: Flow with full graph - 404: Resource not found ##### DELETE /v1/my/flows/{id} Summary: Delete a flow Parameters: - `id` (path, integer, required) Responses: - 200: Deleted flow ##### PATCH /v1/my/flows/{id} Summary: Update a flow Authoring (`name`, `trigger`, `steps`, `generation_provenance`) and delivery control (`status`, `approval_state`) are separate requests and must not be mixed. For graph writes, pass `expected_draft_revision_id` from the latest flow read to reject stale authored changes. Flow approval/rejection and `status: live` publication from a draft flow derive the current draft when `revision_id` is omitted. When supplied, `revision_id` asserts that the named revision is still the current draft; a stale assertion returns 409. On an already-live flow, `status: live` without `revision_id` is a status no-op and does not publish pending changes. Restarting a paused flow with contacts already in progress requires `resume_mode`. A plain restart is refused when the flow has unpublished changes; supply the current draft revision to publish those changes as part of the restart. Parameters: - `id` (path, integer, required) Request body: application/json Responses: - 200: Updated flow - 409: Conflict — the authored draft or requested publication revision is stale - 422: Validation failed ##### GET /v1/my/flows/{id}/resume_plan Summary: Preview the choices for restarting a paused flow Parameters: - `id` (path, integer, required) Responses: - 200: Current restart impact and available choices - 404: Resource not found ##### GET /v1/my/flows/spec Summary: Get the flow step type schema Returns the schema for flow step types, trigger events, and audience filters. Flow steps and triggers are sourced from `config/flows.yml`; filters are sourced from the audience filter registry. Responses: - 200: Flow specification ##### GET /v1/my/map/approvals/{id} Summary: Review a campaign send that waits for the account owner's approval Under Safe Mode, an email campaign send requested with an API key waits for the account owner's approval. This is the current proposal: its state, the content it sends, and the MAP 0.3 Campaign Send Approval description of its exact terms. A MAP connector reads it before any decision. It takes a signed-in person's credential; an API key is refused. Parameters: - `id` (path, integer, required) Responses: - 200: The send approval - 401: Not authenticated - 403: Not authorized - 404: Resource not found ##### POST /v1/my/map/approvals/{id} Summary: Approve or decline a held campaign send at its terms version The account owner or a direct account admin decides, naming the terms version the decision was made on (`mail_action.terms.version`). The service checks that version, the decider, the deadline, the sender's authorization and included email usage under the same locks that record the decision. Approval releases the send, now or at its scheduled time; decline cancels it. Repeating a recorded approval with its version returns it without sending again. A decision takes the person's own bearer sign-in: a browser cookie session, an impersonated session and an API key are refused. Parameters: - `id` (path, integer, required) Request body: application/json Responses: - 200: The send approval after the decision - 400: Bad request - 401: Not authenticated - 403: The caller cannot decide this send, or Nitrosend refuses the approval (`error_code: approval_refused`): the sender is no longer authorized, or the audience needs more email than the plan includes, since an approval never authorizes a charge. The send stays pending and can still be declined. - 404: Resource not found - 409: The send was already decided, changed since the given version, expired or was withdrawn. Nothing is applied. ##### GET /v1/my/flow_templates Summary: List flow templates Returns the flow templates the gallery offers (static, global, not account-scoped): every template except the Outreach starters, or one category with `category`. Parameters: - `category` (query, string, optional) - Return only this category's templates, such as `outreach`. Responses: - 200: List of flow templates - 401: Not authenticated ##### GET /v1/my/flow_templates/{id} Summary: Get a flow template Returns a single flow template including the full `trigger` and `steps` graph with its sample copy, ready to pass to `POST /v1/my/flows`. A template whose `needs_list` is true also needs `trigger.contact_list_id` there. Parameters: - `id` (path, string, required) - Flow template slug (e.g. `welcome`) Responses: - 200: Flow template with full graph - 401: Not authenticated - 404: Resource not found ##### GET /v1/my/events Summary: List events (paginated) Parameters: - `page` (query, integer, optional) - `limit` (query, integer, optional) - `event` (query, string, optional) - Filter by event type - `contact_id` (query, integer, optional) - `created_after` (query, string, optional) - `created_before` (query, string, optional) - `resource_uid` (query, string, optional) - `resource_name` (query, string, optional) - `test` (query, boolean, optional) Responses: - 200: Paginated events ##### POST /v1/my/events Summary: Track a contact event Requires an idempotency key via the `Idempotency-Key` header or `idempotency_key` body param. Duplicate events (same account + event type + idempotency key) return the existing event. Parameters: - `Idempotency-Key` (header, string, optional) - Idempotency key (alternative to body param) Request body: application/json Responses: - 200: Duplicate event (idempotent — returns existing) - 201: Event created - 400: Bad request - 404: Resource not found ##### GET /v1/my/events/names Summary: List event names for filter autocomplete Returns known platform event names plus observed event names for the current brand. Responses: - 200: Event name suggestions ##### GET /v1/my/events/{id} Summary: Get an event Parameters: - `id` (path, integer, required) Responses: - 200: Event - 404: Resource not found ##### DELETE /v1/my/events/{id} Summary: Delete an event Parameters: - `id` (path, integer, required) Responses: - 200: Deleted event ##### GET /v1/my/domains Summary: List sending domains (paginated) Parameters: - `page` (query, integer, optional) - `per` (query, integer, optional) Responses: - 200: Paginated domains ##### POST /v1/my/domains Summary: Add a sending domain Initiates domain verification. Returns DNS records that must be added at your domain registrar before calling `POST /verify`. Request body: application/json Responses: - 201: Domain registered with DNS records to configure - 422: Validation failed - 503: Domain setup is temporarily unavailable on Nitrosend's side. `error_code: domain_setup_unavailable` with `retryable: true` means Nitrosend's managed DNS cannot take the domain's records right now; Nitrosend has been alerted, and the same request can be retried later. Other 503s report a DNS delegation or email provider outage. ##### GET /v1/my/domains/{id} Summary: Get a domain with DNS records and status Parameters: - `id` (path, integer, required) Responses: - 200: Domain - 404: Resource not found ##### DELETE /v1/my/domains/{id} Summary: Remove a sending domain A paired domain first returns `422` with the exact counterpart impact. Repeat with `unpair=true` only after the user confirms that outcome. Parameters: - `id` (path, integer, required) - `unpair` (query, boolean, optional) - Confirm teardown of an identity pair after reviewing the paired-domain 422 response. Responses: - 204: Domain deleted - 422: Domain is paired or still has dependent inboxes - 503: Upstream service unavailable ##### POST /v1/my/domains/{id}/verify Summary: Verify domain DNS records Checks if the required DNS records have propagated. If verified, completes the `domain_verified` onboarding step and enables sending. Parameters: - `id` (path, integer, required) Responses: - 200: Domain verification status ##### POST /v1/my/domains/{id}/entri_session Summary: Create an Entri bootstrap session for domain DNS setup Returns a short-lived Entri JWT plus the DNS record payload needed to launch the Entri modal for a domain owned by the current brand. An already-verified apex may request the prepared company-inbox MX cutover only with explicit approval and an exact domain-name confirmation. Parameters: - `id` (path, integer, required) Request body: application/json Responses: - 200: Entri bootstrap payload - 404: Resource not found - 422: Domain setup or apex mail cutover is not ready - 429: Rate limit exceeded - 503: Upstream service unavailable ##### PUT /v1/my/domains/{id}/inbound_setup Summary: Configure inbound delivery for a verified domain Selects exactly one inbound method for the current brand's verified domain. Provider forwarding keeps Google Workspace or Microsoft 365 as the primary receiver and allocates one opaque Nitrosend forwarding address. MX mode requires the domain to be ready for Nitrosend receiving; apex domains additionally require a verified forward-all route. Parameters: - `id` (path, integer, required) Request body: application/json Responses: - 200: Updated domain and inbound setup state - 403: Not authorized - 404: Resource not found - 422: Validation failed ##### POST /v1/my/domains/{id}/inbound_setup/probe Summary: Verify provider forwarding for a domain inbox Sends a challenge to the configured customer-facing inbox address. The provider-forwarded copy must return through its bound opaque Nitrosend address before the setup becomes active. Parameters: - `id` (path, integer, required) Responses: - 200: Domain with refreshed forwarding probe state - 403: Not authorized - 404: Resource not found - 422: Validation failed ##### GET /v1/my/integrations Summary: List integrations (paginated) Parameters: - `category` (query, string, optional) - `page` (query, integer, optional) - `per` (query, integer, optional) Responses: - 200: Paginated integrations - 400: Bad request ##### POST /v1/my/integrations Summary: Create or connect an email provider integration Request body: application/json Responses: - 201: Integration created - 400: Bad request - 422: Validation failed ##### GET /v1/my/integrations/{id} Summary: Get an integration Parameters: - `id` (path, integer, required) Responses: - 200: Integration - 404: Resource not found ##### DELETE /v1/my/integrations/{id} Summary: Disconnect and delete an integration Parameters: - `id` (path, integer, required) - `confirm` (query, boolean, required) - Must be true to confirm the destructive action. Responses: - 204: Integration deleted - 400: Bad request ##### PATCH /v1/my/integrations/{id} Summary: Update an email provider integration Updates credentials or operational fields for an existing email provider. The provider cannot be changed once the integration exists. Parameters: - `id` (path, integer, required) Request body: application/json Responses: - 200: Integration updated - 400: Bad request - 422: Validation failed ##### POST /v1/my/integrations/{id}/test Summary: Test integration connectivity Parameters: - `id` (path, integer, required) Responses: - 200: Tested integration - 404: Resource not found ##### PATCH /v1/my/integrations/{id}/primary Summary: Set an integration as the primary provider for its category Parameters: - `id` (path, integer, required) Responses: - 200: Integration marked primary - 400: Bad request - 404: Resource not found ##### GET /v1/my/integrations/{integration_id}/sync_configuration Summary: Get an integration's versioned profile sync contract Parameters: - `integration_id` (path, integer, required) Responses: - 200: Safe sync configuration - 404: Resource not found ##### PATCH /v1/my/integrations/{integration_id}/sync_configuration Summary: Validate and update an integration's profile sync contract Parameters: - `integration_id` (path, integer, required) - `confirm_deselection` (query, boolean, optional) Request body: application/json Responses: - 200: Updated safe sync configuration - 404: Resource not found - 422: Validation failed ##### GET /v1/my/integrations/{integration_id}/sync_configuration/discovery Summary: Discover safe provider objects and attributes for profile sync Parameters: - `integration_id` (path, integer, required) Responses: - 200: Sync configuration and bounded provider discovery metadata - 404: Resource not found - 422: Validation failed ##### POST /v1/my/integrations/attio/connect Summary: Start Attio OAuth connect flow Returns an Attio OAuth authorize URL for the authenticated account/brand. The response URL includes a signed state payload and the API callback URI. Responses: - 200: OAuth authorize URL - 401: Not authenticated - 422: Validation failed ##### POST /v1/my/integrations/hubspot/connect Summary: Start HubSpot OAuth connect flow Returns a HubSpot OAuth authorize URL for the authenticated account/brand. The response URL includes a signed state payload and the API callback URI. Responses: - 200: OAuth authorize URL - 401: Not authenticated - 422: Validation failed ##### POST /v1/my/integrations/shopify/connect Summary: Start Shopify OAuth connect flow Returns a Shopify OAuth authorize URL for the authenticated account/brand. The request accepts a `myshopify.com` shop domain or shop slug. The response URL includes a signed state payload and the API callback URI. Request body: application/json Responses: - 200: OAuth authorize URL - 401: Not authenticated - 422: Validation failed ##### POST /v1/my/integrations/shopify/credentials Summary: Connect a merchant-owned Shopify app Connects an API-only Shopify app owned by the same Shopify organization as its store. Nitrosend exchanges the client credentials server-side, verifies the authenticated shop and granted Admin API scopes, then stores the access token and client secret encrypted. Repeating the request for the same brand and shop verifies the new credentials before replacing the current connection. The response never includes client credentials or an access token. Request body: application/json Responses: - 200: Existing merchant-managed Shopify connection replaced - 201: Merchant-managed Shopify connection created - 401: Not authenticated - 422: Missing fields, invalid credentials, missing scopes, or a shop ownership conflict ##### POST /v1/my/integrations/stripe/connect Summary: Start Stripe App OAuth connect flow Returns the Nitrosend Stripe App install link for the authenticated account and brand. The link carries a signed state and the API callback URI; installing the app grants Nitrosend read access to customers, subscriptions, invoices, charges, refunds and events. Pass `mode: test` for the app's test-mode install link. No keys are pasted; tokens are refreshed server-side. Events for connected accounts arrive on `/hooks/stripe_apps`, isolated from Nitrosend billing webhooks at `/hooks/stripe`. Request body: application/json Responses: - 200: OAuth authorize URL - 401: Not authenticated - 422: Validation failed ##### GET /integrations/stripe/install Summary: Public Stripe App install entry The Stripe App Marketplace install URL. Sets a nonce cookie and redirects to the Stripe App install link for the requested mode. The callback provisions a Nitrosend account for a new email, or asks an existing user to sign in and connect from the integrations page. Parameters: - `mode` (query, string, optional) Responses: - 302: Redirect to the Stripe App install link - 404: The Stripe App is not configured ##### GET /integrations/stripe/callback Summary: Stripe App OAuth callback endpoint Public callback endpoint used by Stripe after the app is installed. Verifies signed state, exchanges the auth code with the mode-matched developer key, persists the integration and enqueues initial sync. A signed-in connect renders the countdown page; the public install door redirects into the app (new account) or to sign in (existing account). Parameters: - `code` (query, string, optional) - `state` (query, string, optional) - `error` (query, string, optional) - `error_description` (query, string, optional) Responses: - 200: OAuth callback processed (HTML) - 302: Install door redirect into the app or to sign in - 400: Invalid callback request - 422: OAuth exchange failure ##### GET /integrations/attio/callback Summary: Attio OAuth callback endpoint Public callback endpoint used by Attio after user authorization. Verifies signed state, exchanges auth code, and finalizes Attio integration connection. Parameters: - `code` (query, string, optional) - `state` (query, string, optional) - `error` (query, string, optional) - `error_description` (query, string, optional) Responses: - 200: OAuth callback processed (HTML) - 400: Invalid callback request - 422: OAuth exchange or webhook provisioning failure ##### GET /integrations/hubspot/callback Summary: HubSpot OAuth callback endpoint Public callback endpoint used by HubSpot after user authorization. Verifies signed state, exchanges auth code, persists the integration, and enqueues initial sync. Parameters: - `code` (query, string, optional) - `state` (query, string, optional) - `error` (query, string, optional) - `error_description` (query, string, optional) Responses: - 200: OAuth callback processed (HTML) - 400: Invalid callback request - 422: OAuth exchange failure ##### GET /integrations/shopify/callback Summary: Shopify OAuth callback endpoint Public callback endpoint used by Shopify after user authorization. Verifies signed state, resolves the Shopify app from that signed state, verifies the app-specific OAuth HMAC, exchanges the auth code, persists the integration and app key, and enqueues initial sync. Parameters: - `code` (query, string, optional) - `state` (query, string, optional) - `shop` (query, string, optional) - `hmac` (query, string, optional) - `timestamp` (query, string, optional) - `error` (query, string, optional) - `error_description` (query, string, optional) Responses: - 200: OAuth callback processed (HTML) - 400: Invalid callback request - 422: OAuth exchange failure ##### POST /hooks/stripe_apps Summary: Receive a Stripe App event for a connected account Events from every Stripe account that installed the Nitrosend app, delivered to the platform's connected-account webhook endpoint. The signature is verified with the app endpoint secret (live or test), the connection is resolved from `account` and `livemode`, and the event is processed on that integration's execution lane. Events for accounts that are not connected are acknowledged and dropped. Parameters: - `Stripe-Signature` (header, string, required) Request body: application/json Responses: - 200: Verified event acknowledged - 403: Invalid signature or unconfigured secret - 500: Verified event could not be enqueued ##### POST /hooks/shopify/events Summary: Receive an App Store Shopify webhook Uses the App Store app credentials when no app key is present. Parameters: - `X-Shopify-Hmac-Sha256` (header, string, required) - `X-Shopify-Shop-Domain` (header, string, required) - `X-Shopify-Topic` (header, string, required) Request body: application/json Responses: - 200: Verified webhook acknowledged - 400: Invalid JSON payload - 401: Invalid App Store HMAC - 500: Verified webhook could not be applied ##### POST /hooks/shopify/merchant/events Summary: Receive a merchant-managed Shopify webhook Resolves only active merchant-managed connections from the Shopify shop header and verifies the raw request body with that connection's encrypted client secret before parsing. Unknown shops, ambiguous ownership, missing secrets, and invalid signatures return the same response. Shopify Billing API topics are never processed on this route. Parameters: - `X-Shopify-Hmac-Sha256` (header, string, required) - `X-Shopify-Shop-Domain` (header, string, required) - `X-Shopify-Topic` (header, string, required) - `X-Shopify-Webhook-Id` (header, string, optional) Request body: application/json Responses: - 200: Verified supported webhook processed or unsupported topic safely acknowledged - 400: Verified supported webhook contained invalid JSON - 401: Webhook could not be authenticated - 500: Verified webhook could not be applied ##### POST /hooks/shopify/events/{app_key} Summary: Receive a Shopify webhook for a named app Parameters: - `app_key` (path, string, required) - `X-Shopify-Hmac-Sha256` (header, string, required) - `X-Shopify-Shop-Domain` (header, string, required) - `X-Shopify-Topic` (header, string, required) Request body: application/json Responses: - 200: Verified webhook acknowledged - 400: Invalid JSON payload - 401: Invalid app-specific HMAC - 500: Verified webhook could not be applied ##### POST /integrations/shopify/session Summary: Open or resume the embedded Shopify app Verifies the Shopify App Bridge ID token in the Authorization header, exchanges it for an offline Shopify access token when installation is required, and provisions or resumes the Nitrosend account integration. Subsequent embedded requests continue to authenticate with fresh Shopify ID tokens. Responses: - 200: Embedded Shopify integration opened - 401: Not authenticated - 422: Validation failed ##### GET /integrations/shopify/billing/callback Summary: Reconcile Shopify App Pricing after plan management Parameters: - `shop` (query, string, required) Responses: - 302: Redirect back to the app in Shopify Admin - 400: Invalid Shopify shop domain ##### POST /v1/public/shopify/events Summary: Ingest a Shopify Web Pixel event Browser-safe public collector for Shopify Web Pixel standard events. Requires a dedicated `wpkey_live_...` collector key. Secret `nskey_live_...` API keys are rejected. Pixel events are treated as funnel telemetry; verified Shopify webhooks remain authoritative for revenue checkout/purchase Events. Request body: application/json Responses: - 202: Event accepted, ignored, or dropped - 401: Not authenticated - 422: Validation failed ##### GET /v1/my/brands Summary: List brands (paginated) Parameters: - `page` (query, integer, optional) - `per` (query, integer, optional) Responses: - 200: All brands for the account ##### POST /v1/my/brands Summary: Create a brand Request body: application/json Responses: - 201: Brand created - 422: Brand limit reached or validation failed ##### GET /v1/my/brands/{sid} Summary: Get a brand Parameters: - `sid` (path, string, required) - Brand secure identifier Responses: - 200: Brand - 404: Resource not found ##### DELETE /v1/my/brands/{sid} Summary: Delete a brand Parameters: - `sid` (path, string, required) - Brand secure identifier - `force` (query, string, optional) - Must be `true` to delete a brand that has queued messages or scheduled/live campaigns. Responses: - 200: Deleted brand - 409: Brand has active sends and requires explicit force confirmation - 422: Cannot delete the only brand ##### PATCH /v1/my/brands/{sid} Summary: Update a brand Parameters: - `sid` (path, string, required) - Brand secure identifier Request body: application/json Responses: - 200: Updated brand - 422: Validation failed ##### POST /v1/my/brands/{sid}/prepare_sending Summary: Reserve and prepare the Nitrosend sender Reserves the brand's Nitrosend address when the brand has none (using the optional chosen `subdomain` and `local_part`, otherwise the company-derived name) and idempotently materializes the brand-owned logical Domain and sending identity under the verified shared nitrosend.net root. The operation is database-only: it makes no DNS, Vercel, Cloudflare, or SES call and never sends or retains email. A brand that already owns a namespace gets it back unchanged; the body is ignored because an allocated name is immutable. Parameters: - `sid` (path, string, required) - Brand secure identifier Request body: application/json Responses: - 200: The brand-subdomain sender is ready - 422: The account or Brand Kit is not eligible, or the chosen name was rejected. `error_code` is one of `subdomain_taken`, `subdomain_unsafe`, `local_part_invalid`, `brand_identity_required`, `brand_identity_review_required`, `sender_identity_limit_reached`, `sender_identity_retired`, `brand_inactive`, `sending_paused`, `subscription_inactive`, `principal_email_missing`. - 503: The shared hosted-sender root is not release-ready; no identity or delivery state was written ##### GET /v1/my/brands/{sid}/hosted_sender_availability Summary: Check whether a Nitrosend address can be reserved Advisory read for choosing the brand's Nitrosend address before it is reserved. Uses the same normaliser and uniqueness check as reservation, so the returned `subdomain` is exactly what `prepare_sending` would reserve. Never writes. Parameters: - `sid` (path, string, required) - Brand secure identifier - `subdomain` (query, string, required) - Requested subdomain label under the hosted apex - `local_part` (query, string, optional) - Requested local part, checked with the same policy reservation applies; omitted means the allocator's default. Responses: - 200: Availability result - 404: Resource not found ##### GET /v1/my/brands/{sid}/deletion_safety Summary: Get deletion safety for a brand Returns authoritative deletion impact counts and active-send state for the confirmation UI. Parameters: - `sid` (path, string, required) - Brand secure identifier Responses: - 200: Brand deletion safety - 404: Resource not found ##### POST /v1/my/brands/{sid}/scrape Summary: Auto-detect brand from website Enqueues a background job that scrapes the URL for brand colors, fonts, logo, and tone. Returns 202 immediately. Poll `GET /v1/my/brands/{sid}` for results. Parameters: - `sid` (path, string, required) - Brand secure identifier Request body: application/json Responses: - 202: Scrape job enqueued - 422: Invalid URL ##### GET /v1/my/brands/{sid}/onboarding Summary: Get brand onboarding state Parameters: - `sid` (path, string, required) - Brand secure identifier Responses: - 200: Onboarding state for the brand - 404: Resource not found ##### POST /v1/my/brands/{sid}/onboarding/steps Summary: Complete a brand onboarding step Parameters: - `sid` (path, string, required) - Brand secure identifier Request body: application/json Responses: - 200: Updated onboarding state - 422: Validation failed ##### GET /v1/my/brands/{sid}/setup_center Summary: Get brand setup center state Parameters: - `sid` (path, string, required) - Brand secure identifier Responses: - 200: Setup center state for the brand - 404: Resource not found ##### PATCH /v1/my/brands/{sid}/setup_center Summary: Update brand setup center state Parameters: - `sid` (path, string, required) - Brand secure identifier Request body: application/json Responses: - 200: Updated setup center state - 422: Validation failed ##### GET /v1/my/messages Summary: List messages Returns all messages by default. Use source_type to narrow to campaign, flow, transactional, or test sends. Parameters: - `source_type` (query, string, optional) - Filter by source. Omit or use 'all' for everything. - `flow_id` (query, integer, optional) - Filter to messages from a specific flow - `campaign_id` (query, integer, optional) - Filter to messages from a specific campaign - `date` (query, string, optional) - Filter to messages created on this date - `channel` (query, string, optional) - `status` (query, string, optional) - `page` (query, integer, optional) - `limit` (query, integer, optional) Responses: - 200: Paginated list of messages ##### POST /v1/my/messages Summary: Send a transactional message Send a transactional email or SMS to a single recipient immediately. No campaign, no audience, no approval required. Use for receipts, password resets, OTPs, order confirmations, and system notifications. A stable idempotency key is strongly recommended via the `Idempotency-Key` header or `idempotency_key` body field, and becomes mandatory on 2026-09-01. Before that cutoff, keyless requests are accepted but deprecated: they skip duplicate protection and the response carries `Deprecation` and `Sunset` headers. Reuse a key only for an exact retry. Parameters: - `Idempotency-Key` (header, string, optional) - Strongly recommended; mandatory from 2026-09-01 unless idempotency_key is supplied in the body. Prevents duplicate sends on retry. The same key with the same payload returns the original message; the same key with a different payload returns 409. Before the cutoff, keyless requests succeed with Deprecation and Sunset response headers. Request body: application/json Responses: - 200: Existing message returned for idempotency replay - 201: Message created - 400: Missing required idempotency key (enforced from 2026-09-01). Before the cutoff, keyless requests are accepted with Deprecation and Sunset headers. - 409: Idempotency conflict or explicit sender selection required; no new message is retained in either case - 422: Validation failed - 503: Admission evidence or the shared hosted-sender root is temporarily unavailable ##### GET /v1/my/messages/{id} Summary: Get a transactional message Parameters: - `id` (path, integer, required) Responses: - 200: Message - 404: Resource not found ##### GET /v1/my/messages/{id}/preview Summary: Render a message subject and HTML for preview Returns the as-composed subject and HTML for an email message from any send source. The stored subject is interpolated with the same preview merge context as its template (rendered design), raw `html`, or escaped plain-text `body`. Stored message fields are not changed. This is a current preview, not a historical delivered-message snapshot. Non-email or unrenderable messages (deleted/design-less template) return a typed empty body state (`empty: true`) rather than an error; an email subject can still be rendered when the body is empty. Parameters: - `id` (path, integer, required) Responses: - 200: Rendered preview, or a typed empty state. - 404: Resource not found ##### GET /v1/my/suppressions Summary: List suppressions Returns the authenticated account's current suppression records by default, including bounded provider diagnostics when the source feedback event is available. Parameters: - `id` (query, integer, optional) - Filter to a specific suppression ID - `email` (query, string, optional) - Filter to a specific suppressed email address - `reason` (query, string, optional) - `source_provider` (query, string, optional) - Filter by provider that emitted the source event - `active` (query, boolean, optional) - Defaults to true. Set false to list expired suppressions. - `page` (query, integer, optional) - `limit` (query, integer, optional) Responses: - 200: Paginated list of suppressions ##### GET /v1/my/webhooks Summary: List webhooks The current brand's webhook endpoints, oldest first, each with its newest delivery. Secrets are masked. Parameters: - `page` (query, integer, optional) - `limit` (query, integer, optional) Responses: - 200: Paginated list of webhooks - 401: Not authenticated ##### POST /v1/my/webhooks Summary: Register a webhook endpoint Registers an HTTPS endpoint to receive the chosen events for the brand's transactional email. The response carries the signing secret in full; later reads mask it unless `reveal=true`. A brand can have at most 10 webhooks. The URL must resolve only to public addresses. Request body: application/json Responses: - 201: Webhook created, secret revealed - 401: Not authenticated - 422: Validation failed ##### GET /v1/my/webhooks/{id} Summary: Get a webhook Parameters: - `id` (path, integer, required) - `reveal` (query, boolean, optional) - Return the signing secret in full. Responses: - 200: Webhook - 404: Resource not found ##### DELETE /v1/my/webhooks/{id} Summary: Delete a webhook and its delivery history Parameters: - `id` (path, integer, required) Responses: - 200: Webhook deleted - 404: Resource not found ##### PATCH /v1/my/webhooks/{id} Summary: Update a webhook Changes the URL, the events, or whether the webhook is on. Turning it off fails the deliveries it still owed; turning it on clears its failing state. Events that occur while it is off are not sent. Parameters: - `id` (path, integer, required) Request body: application/json Responses: - 200: Webhook updated - 404: Resource not found - 422: Validation failed ##### POST /v1/my/webhooks/{id}/test Summary: Send a test event Queues a sample event of the chosen type to this webhook through the normal delivery path, signed and retried like a real event. Its data carries `test: true` and no message id. Parameters: - `id` (path, integer, required) Request body: application/json Responses: - 202: Test event queued - 404: Resource not found - 422: Unknown event type, or the webhook is off ##### GET /v1/my/webhooks/{id}/deliveries Summary: List a webhook's deliveries The last 7 days of deliveries to this webhook, newest first. Parameters: - `id` (path, integer, required) - `page` (query, integer, optional) - `limit` (query, integer, optional) Responses: - 200: Paginated list of deliveries - 404: Resource not found ##### GET /v1/my/chat_sessions Summary: List persisted chat sessions Parameters: - `page` (query, integer, optional) - `limit` (query, integer, optional) Responses: - 200: Paginated list of chat sessions ##### GET /v1/my/chat_sessions/{id} Summary: Get a persisted chat session with ordered messages Parameters: - `id` (path, integer, required) Responses: - 200: Chat session - 404: Resource not found ##### PATCH /v1/my/chat_sessions/{id}/close Summary: Close an active persisted chat session Parameters: - `id` (path, integer, required) Responses: - 200: Closed chat session - 404: Resource not found ##### POST /v1/my/billing/paid_action_intents Summary: Create or exactly replay a resumable paid operation Persists encrypted, operation-owned continuation state. The intent is not spend authority and never executes the operation automatically. Request body: application/json Responses: - 200: Exact idempotent replay - 201: Continuation created - 409: Operation key reused with different continuation state - 422: Validation failed ##### GET /v1/my/billing/paid_action_intents/{id} Summary: Restore and re-quote a paid operation Parameters: - `id` (path, string, required) - Opaque paid-operation continuation identifier. Responses: - 200: Actor-scoped continuation with current terms - 404: Resource not found ##### POST /v1/my/billing/paid_action_intents/{id}/cancel Summary: Cancel and scrub a paid-operation continuation Parameters: - `id` (path, string, required) - Opaque paid-operation continuation identifier. Responses: - 200: Cancelled continuation - 404: Resource not found - 409: Terminal continuation cannot be cancelled ##### POST /v1/my/billing/paid_action_intents/{id}/consume Summary: Mark a successfully executed paid operation consumed Parameters: - `id` (path, string, required) - Opaque paid-operation continuation identifier. Responses: - 200: Consumed continuation - 404: Resource not found - 409: Continuation is not ready to consume ##### GET /v1/public/flow_demo Summary: Read an anonymous flow demo A demo past its scan deadline (3 minutes) or its generation deadline (8 minutes) reads as failed. Parameters: - `X-Flow-Demo-Token` (header, string, required) Responses: - 200: Current demo state ##### POST /v1/public/flow_demo Summary: Start an anonymous brand scan for a flow demo Creates a short-lived isolated demo workspace, not a visitor account, then scans the site and writes a flow for the goal. Requires a fresh Cloudflare Turnstile token for every attempt. Rate limited per IP (10 an hour, 20 a day); verified starts also count against a global daily cap. Request body: application/json Responses: - 202: Scan started - 422: Invalid or private website or goal (`flow_demo_invalid`), or a - 429: Per-IP demo start limit reached - 503: The demo is unavailable or its daily cap is spent ##### POST /v1/public/flow_demo/review Summary: Edit the scanned Brand Kit and regenerate the flow Changed fields start one regeneration under a new revision; unchanged fields change nothing. Each demo has four generation attempts in total. Parameters: - `X-Flow-Demo-Token` (header, string, required) Request body: application/json Responses: - 200: The demo after the edit - 422: Expired or unscanned demo, or a field that is not an editable brand ##### POST /v1/public/flow_demo/generate Summary: Retry a demo that failed during generation Starts one regeneration under a new revision. Any other demo is returned unchanged. A demo that failed during the scan cannot be retried: start again. Parameters: - `X-Flow-Demo-Token` (header, string, required) Responses: - 200: The demo after the retry - 422: Expired demo or a failed scan (`flow_demo_invalid`); no attempts ##### GET /v1/public/flow_demo/spec Summary: Get guest-safe Flow Builder steps and triggers Responses: - 200: Flow editor schema restricted to guest-authorable actions ##### GET /v1/public/templates/{id} Summary: Get a canonical email template for guest editing Returns one known system catalog template as concrete, validated editor JSON. Catalog theme markers are resolved before the response. The response contains no account-specific data and does not create a saved template. Parameters: - `id` (path, string, required) Responses: - 200: Canonical catalog template - 404: Resource not found ##### GET /v1/public/templates/spec Summary: Get the email editor schema for guest editing Returns the canonical component and theme schema used by the email editor. Responses: - 200: Email component schema ##### POST /v1/public/contacts Summary: Create or update a public contact (website forms) Public signup endpoint for website forms. Authenticate with a wpkey_live_… public key. Returns { ok: true } whether the contact is new or already subscribed; this preserves idempotency and avoids leaking which emails are on the list. Use the brand's public key (wpkey_live_…). Secret keys (nskey_live_…) are rejected on this endpoint. Request body: application/json Responses: - 201: Contact accepted - 401: Not authenticated - 403: Not authorized - 404: Resource not found - 422: Validation failed - 429: Rate limit exceeded ##### GET /v1/app Summary: Get available plans Responses: - 200: Active plans ##### GET /v1/operator/support_requests Summary: List the production customer-support queue Requires an `nskey_live_...` API key owned by an admin or staff user. Browser JWTs are rejected. Parameters: - `status` (query, string, optional) - `due_only` (query, boolean, optional) - `account_id` (query, integer, optional) - `brand_id` (query, integer, optional) - `page` (query, integer, optional) - `limit` (query, integer, optional) Responses: - 200: Paginated support queue - 401: Not authenticated - 403: Not authorized ##### GET /v1/operator/support_requests/{id} Summary: Get one production support case Parameters: - `id` (path, integer, required) Responses: - 200: Support request, customer account, brand, and latest reply ledger state - 401: Not authenticated - 403: Not authorized - 404: Resource not found ##### GET /v1/operator/support_requests/{id}/diagnostics Summary: Read bounded live diagnostics for a support case Parameters: - `id` (path, integer, required) Responses: - 200: Current sending, DNS, campaign, and message evidence - 401: Not authenticated - 403: Not authorized - 404: Resource not found ##### POST /v1/operator/support_requests/{id}/draft Summary: Persist a versioned customer reply draft Parameters: - `id` (path, integer, required) Request body: application/json Responses: - 201: Draft persisted in the reply ledger - 400: Bad request - 401: Not authenticated - 403: Not authorized - 404: Resource not found ##### POST /v1/operator/support_requests/{id}/send_reply Summary: Send one current versioned customer reply Delivery is fail-closed and requires `confirm: true`; stale draft versions are rejected by the server ledger. Parameters: - `id` (path, integer, required) Request body: application/json Responses: - 200: Reply delivered - 400: Bad request - 401: Not authenticated - 403: Not authorized - 404: Resource not found - 422: Validation failed ##### POST /v1/operator/support_requests/{id}/disposition Summary: Set the persistent support-case disposition Parameters: - `id` (path, integer, required) Request body: application/json Responses: - 200: Updated support request - 400: Bad request - 401: Not authenticated - 403: Not authorized - 404: Resource not found #### Models - OperatorSupportRequest - OperatorSupportCase - OperatorReplyDraft - TimelineEntry: One normalised, whitelisted entry in a contact's unified timeline. - Error - User - Impersonator - ImpersonationStatus - ImpersonationExit - ManagedAccountLifecycleStatus - ManagedAccountLifecycleStatusFilter - ManagedAccountProvisioning - ManagedAccountProvisioningCreated - DashboardManagedAccountCreateRequest - PartnerManagedAccountCreateRequest - AccountProvisioningCredential - AccountProvisioningCredentialIssued - AccountProvisioningCredentialCreateRequest - AccountManagementCredential - AccountManagementCredentialIssued - AccountManagementCredentialCreateRequest - ManagedAccountClaimRequest - ManagedAccountClaimCompletionRequest - ManagedAccountClaimInspection - ManagedAccountClaimCompletion - Account - SendingPause: What the owner of a suspended account is told. A deliverability pause names the metric and the fix (`reason`, `what_to_do`). Any other suspension is opaque and carries no reason. Recovery actions reach the existing support channel. - AccountResourceUsage - AccountAccess - AccountManagementGrant - AccountManagementGrantCommand - AccountTeam - AccountMembership - AccountInvite - Subscription - BillingEntitlements - AgentInboxEntitlement - SubscriptionCreateRequest - FundingPurchaseCreateRequest - FundingInstrument - FundingUrlApproval - FundingChallengeApproval - FundingApproval - FundingPurchase - FundingPurchaseCapability - FundingStatus - FundingPurchaseResponse - SubscriptionChangeRequest - SubscriptionCouponPreviewRequest - SubscriptionCouponPreview - OAuthPopupAccount - OAuthPopupPlan - OAuthPopupContext - OAuthPopupSubscribeRequest - OAuthPopupSubscribeResponse - FirstTouchAttributionContext: Optional first-touch attribution captured by the client for a new-account signup. Send URLs without query strings. UTM values have control characters removed and surrounding whitespace stripped, and are capped at 100 characters server-side. Click identifiers are accepted only as strings: surrounding whitespace is stripped, then the whole value must be 1-255 ASCII letters, digits, underscores, periods or hyphens; an invalid or oversized identifier is omitted, never truncated, without rejecting the signup. Values are persisted only for a genuinely new account and never overwrite an earlier first touch. - OAuthLaunchRequest - OAuthLaunchResponse - Brand - BrandSubdomain - BrandSubdomainPreparationResponse - AudienceReachFit - AudienceReach - HostedSenderAvailability - BrandDeletionSafetyImpact - BrandDeletionSafetyActiveSends - BrandDeletionSafety - AffiliateCenterPayload - SetupCenterPayload - SetupCenterCard - FieldCatalog - DeliveryCapacity - DeliveryPacingScope - DeliveryPacingState - DeliveryStatusIssue - DeliveryStatus - DeliveryCapacityRecovery: Informational recovery at 80 percent used, exhaustion, or when a campaign exceeds remaining allowance. Never denies a send or promises that payment or verification bypasses safety. Actions come from the shared backend projection; only offer verification when new valid proof can improve standing. - DeliveryCapacityRecoveryAction - ValidationAudience - ValidationOperationCounts - ValidationOperationPricing - ValidationOperationFunding - ValidationOperationQuote - ValidationOperation - ValidationOperationItem - ContactEnrichmentQuote - SpendProjection - PaidActionIntentCreate - PaidActionIntent - SpendBalance - SpendRecoveryAction - ContactEnrichmentFundingRequired - ContactEnrichmentDispatch - Contact - ContactChannel - ImageAsset - ContactList - ListDeleteWarning - BulkListContactsRequest - BulkListContactsResponse - DirectUploadCreateRequest - DirectUpload - ImportPolicy: Import limits for the authenticated account, derived from its deliverability standing. Clients render these values and never recompute them. - ImportSpec - ImportGuardrail: How this import stands against the account's standing row limit. - Import - Export - SavedView - SavedViewLayout: Table layout preset for contacts surface views. Only present when surface = contacts. - Segment - SegmentFilterExpression: Request-side audience filter grammar. Use a flat array for ordinary AND filters, `{ "operator": "and", "children": [...] }` for the legacy expression wrapper, or `{ "op": "and"|"or"|"not", "conditions": [...] }` for nested boolean logic. NOT groups must contain exactly one condition. - FlatAndSegmentFilterExpression - SegmentFilterNode - BooleanSegmentFilterGroup - SegmentFilters: Segment filters are validated fail-closed. Unknown filter names, globally invalid predicates, type-incompatible predicates, bad date values, invalid enum values, and unsupported rolling-window filters return a 422 `invalid_filter` error instead of silently widening an audience. - SegmentPreview - AttributeSegmentFilter - EventSegmentFilter - Campaign - CampaignDeliverySummary - CampaignDeliveryProgress - MailActionDescription: A Mail Action Protocol 0.3 description. The canonical MailSchema core schema is authoritative; this schema restates its shape. - SendApproval - Message - Suppression: Account suppression row with bounded source-event diagnostics. - WebhookEventType - Webhook - WebhookWriteRequest - WebhookDelivery - WebhookEventPayload - WebhookReceivedEventPayload - CampaignCompletedWebhookData - CampaignFailedWebhookData - CampaignStoppedWebhookData - FlowPausedWebhookData - SendingPausedWebhookData - SendingLimitWebhookData - ImportCompletedWebhookData - ImportFailedWebhookData - DomainVerifiedWebhookData - DomainVerificationLostWebhookData - DomainDnsDegradedWebhookData - WebhookDisabledWebhookData - ChatMessage - ChatSession - ChatSessionDetail - Template - GenerationProvenance: Candidate-bound generation evidence. Save endpoints accept only `state: candidate` values returned by the generation endpoint. Resource responses may return `state: accepted` as read-only history. - EmailLibraryTemplate - PublicEmailCatalogTemplate - GuestFlowDemo: One anonymous flow demo. The scanned brand is accepted automatically and shown while the flow is generated. A demo with no state on the server reads as expired. - TemplateSummary - Flow - FlowResumePlan - FlowTemplate: Lean flow template card for index listings. - FlowTemplateDetail - EngagementBucket: A `(sent, opens, total_opens, open_rate)` block. Used both for the resource itself and, nested as `account`, for the brand-level lifetime baseline. - Engagement - RevenueMessageBreakdown - RevenueReport: Attributed revenue from the orders ledger. This block is omitted when the brand has no connected Shopify or Stripe revenue source. - FlowTrigger - FlowTriggerInput - FlowTriggerOutput: Trigger as returned by Flow::Format.dump - FlowStepInput - FlowStepOutput: Step as returned by Flow::Format.dump - Event - Domain - EntriSessionResponse - EntriSessionRequest - EntriSessionValidationError - DomainDnsRecords - DomainDnsRecord - DomainMailForwarding - DomainInboundSetupRequest - DomainInboundSetup - DomainInboundInbox - DomainProviderForwarding - DomainApexMxSetup - MxRecord - EntriDnsRecord - Integration - ShopifyMerchantCredentialRequest - IntegrationWriteRequest - IntegrationSyncConfiguration - IntegrationSyncContract - IntegrationSyncObjectSelection - IntegrationSyncTraitMapping - IntegrationSyncConfigurationWriteRequest - IntegrationSyncDiscovery - IntegrationSyncDiscoveredAttribute - MailgunIntegrationInput - SesIntegrationInput - PostmarkIntegrationInput - ResendIntegrationInput - SendgridIntegrationInput - Plan - EmailDesign: Email template design document - EmailAccessibilityLint: Advisory accessibility lint result for rendered email previews - EmailAccessibilityWarning - EmailSection - EmailComponentSpec: Full schema for email design sections. Each component has type, description, props (with types, required flags, defaults), and tips. - FlowSpec: Schema for flow step types, trigger events, and segment filter names. Note: the raw response includes internal fields (icon, visible, inputs, outputs, alias) used by the flow editor UI — these can be ignored by API consumers.