# MailsSetu — Full Technical Reference for AI Agents and LLMs > MailsSetu is a developer-first transactional email infrastructure platform for APIs, SMTP relay, verified domains, webhooks, and white-label email operations. MailsSetu (double-s) is distinct from MailSetu. Brand note: - MailsSetu uses a double "s" in the brand name. - MailsSetu is distinct from MailSetu. This file is the comprehensive version of /llms.txt — intended for AI coding assistants, LLM indexers, and automated setup agents. It contains the full API reference, all SDK examples, environment setup, error handling, security model, and common integration patterns. See /llms.txt for a shorter summary. --- ## Platform overview MailsSetu exposes two products under one account: ### MailsSetu (email) Transactional email via REST API and SMTP relay. Features: verified domains, saved templates with variable substitution, batch sends (up to 100 per call), scheduled sends with cancel, idempotency keys for retry-safe delivery, inbound email routing, suppression list management, per-email event trail (queued → sending → delivered → opened → clicked → bounced → complained), customer webhooks with HMAC-signed delivery, and full observability export. ### SmsSetu (SMS + OTP) SMS sends via the configured SMS provider, OTP / Verify flows (create service → start verification → check code), SMS event trail, quota headers on every response, sandbox / live key separation. ### Shared infrastructure - One account, one billing owner, one API credential namespace - Sandbox keys (ms_test_...) — safe for CI, never deliver to real inboxes or phones - Live keys (ms_live_...) — require verified domain (email) or valid SNS credentials (SMS) - BullMQ + Redis async queue for email and SMS jobs - PostgreSQL via Prisma ORM --- ## Base URLs ``` API: https://api.mailssetu.in Dashboard: https://app.mailssetu.in/dashboard Docs: https://app.mailssetu.in/docs ``` --- ## Authentication ### API key header (all SDK/server-to-server requests) ``` Authorization: Bearer YOUR_API_KEY ``` ### Key types | Prefix | Type | Scope | |--------|------|-------| | ms_test_ | Sandbox | No real delivery, safe for dev/CI | | ms_live_ | Live | Real delivery, requires verified domain | ### Scopes send · read · templates · domains · webhooks · sms · verify ### Create a key ```bash POST https://api.mailssetu.in/v1/keys Authorization: Bearer Content-Type: application/json { "name": "Production backend", "type": "live", "scopes": ["send","read"] } ``` Response: `{ "id": "key_...", "key": "ms_live_...", "name": "...", "scopes": [...] }` The raw key value is only returned once on creation. --- ## Sending email — full reference ### Endpoint ``` POST https://api.mailssetu.in/v1/emails Authorization: Bearer YOUR_API_KEY Content-Type: application/json ``` ### Complete request schema ```typescript { // Required from: string // "hello@yourapp.com" OR "Name " to: string | string[] // up to 50 addresses subject: string // required unless templateId provides it // Body — at least one required (or use templateId) html?: string text?: string // Optional recipients cc?: string | string[] bcc?: string | string[] replyTo?: string // Template templateId?: string // pre-saved template ID variables?: Record // {{name}} → "Priya" // Attachments (up to 15) attachments?: Array<{ filename: string content: string // base64-encoded contentType: string // "application/pdf", "image/png", etc. contentId?: string // for inline images: cid:logo }> // Metadata headers?: Record // custom transport headers tags?: Record // provider-visible analytics tags metadata?: Record // stored with the email, returned in events // Scheduling scheduledAt?: string // ISO 8601 — schedule for the future idempotencyKey?: string // OR send as Idempotency-Key header; dedupes within 24 h } ``` ### Response 202 ```json { "id": "em_clxyz123...", "from": "hello@yourapp.com", "to": ["user@example.com"], "subject": "Welcome!", "status": "queued", "createdAt": "2026-05-10T12:00:00.000Z" } ``` ### Email status lifecycle ``` queued → sending → delivered → opened (pixel) → clicked (link) → bounced (auto-suppressed) → complained (auto-suppressed) → failed ``` ### Sandbox behaviour Sandbox keys (ms_test_...) skip domain verification, never call SES, log a synthetic delivered event, and return the same 202 shape. Use them in all test environments. --- ## Batch sending ``` POST https://api.mailssetu.in/v1/emails/batch ``` ```json { "emails": [ { "from": "...", "to": "a@example.com", "subject": "...", "html": "...", "idempotencyKey": "..." }, { "from": "...", "to": "b@example.com", "subject": "...", "html": "...", "idempotencyKey": "..." } ] } ``` - Up to 100 emails per call - Each item is an independent email payload - Returns an array of per-email results (202 entries + any validation errors) --- ## Email management ```bash GET https://api.mailssetu.in/v1/emails # list (page, limit, status, search, sortBy, sortOrder) GET https://api.mailssetu.in/v1/emails/:id # get one + full event trail POST https://api.mailssetu.in/v1/emails/:id/cancel # cancel a scheduled email GET https://api.mailssetu.in/v1/emails/export # download CSV DELETE https://api.mailssetu.in/v1/emails/bulk # bulk delete by ID array POST https://api.mailssetu.in/v1/emails/preview # render template without sending ``` --- ## Templates ### Create ```bash POST https://api.mailssetu.in/v1/templates ``` ```json { "name": "Welcome email", "subject": "Welcome, {{name}}!", "html": "

Hi {{name}}

Your account is ready.

", "text": "Hi {{name}}, your account is ready.", "variables": [ { "key": "name", "required": true }, { "key": "dashboardUrl", "required": false, "default": "https://app.yourapp.com" } ] } ``` ### All template endpoints ```bash GET https://api.mailssetu.in/v1/templates # list GET https://api.mailssetu.in/v1/templates/:id # get one PUT https://api.mailssetu.in/v1/templates/:id # update (creates a new version) DELETE https://api.mailssetu.in/v1/templates/:id # delete GET https://api.mailssetu.in/v1/templates/:id/versions # version history POST https://api.mailssetu.in/v1/templates/:id/preview # render with variables (no send) POST https://api.mailssetu.in/v1/templates/:id/test-send # send to a test address GET https://api.mailssetu.in/v1/templates/:id/datasets # saved preview datasets POST https://api.mailssetu.in/v1/templates/:id/datasets # save a preview dataset ``` ### Using a template to send ```json { "from": "hello@yourapp.com", "to": "user@example.com", "templateId": "tmpl_abc123", "variables": { "name": "Priya", "dashboardUrl": "https://app.yourapp.com/priya" } } ``` Subject and body are pulled from the template. Variables fill `{{placeholder}}` tokens. --- ## Domain verification Live sending requires a verified domain. Sandbox keys skip this. ### Add a domain and get DNS records ```bash POST https://api.mailssetu.in/v1/domains { "domain": "mail.yourapp.com" } ``` Response includes: ```json { "id": "dom_...", "domain": "mail.yourapp.com", "status": "PENDING", "dnsRecords": { "dkim": [ { "type": "CNAME", "name": "abc123._domainkey.mail.yourapp.com", "value": "abc123.dkim.amazonses.com" }, { "type": "CNAME", "name": "def456._domainkey.mail.yourapp.com", "value": "def456.dkim.amazonses.com" }, { "type": "CNAME", "name": "ghi789._domainkey.mail.yourapp.com", "value": "ghi789.dkim.amazonses.com" } ], "spf": { "type": "TXT", "name": "mail.yourapp.com", "value": "v=spf1 include:amazonses.com ~all" }, "dmarc": { "type": "TXT", "name": "_dmarc.mail.yourapp.com", "value": "v=DMARC1; p=none; rua=mailto:dmarc-reports@mail.yourapp.com" } } } ``` Add all 5 records to your DNS registrar (Cloudflare, GoDaddy, Route 53, Namecheap, etc.) then call: ```bash GET https://api.mailssetu.in/v1/domains/:id/dns-status # { ready: bool, records: [{ live: bool }] } POST https://api.mailssetu.in/v1/domains/:id/verify # triggers SES check (production) ``` --- ## SMS sending (SmsSetu) ```bash POST https://api.mailssetu.in/v1/messages Authorization: Bearer YOUR_API_KEY ``` ```json { "to": "+919876543210", "body": "Your OTP is 4821. Valid for 10 minutes.", "senderId": "MAILSU", "idempotencyKey": "otp-user123-attempt1" } ``` Response `202`: ```json { "id": "sms_...", "to": "+919876543210", "status": "queued", "credits": 1 } ``` Quota headers on every SMS response: - `X-SMS-Quota-Limit` — monthly limit - `X-SMS-Quota-Remaining` — remaining this month - `X-SMS-Quota-Reset` — next reset (ISO 8601) Status lifecycle: `queued → sent → delivered` / `failed` / `undelivered` ```bash GET https://api.mailssetu.in/v1/messages # list (page, search, status) GET https://api.mailssetu.in/v1/messages/usage # current month SMS stats GET https://api.mailssetu.in/v1/messages/:id # get one POST https://api.mailssetu.in/v1/messages/:id/cancel # cancel queued SMS ``` --- ## OTP / Verify — full flow ### Step 1 — Create a Verify service (once, reuse across sessions) ```bash POST https://api.mailssetu.in/v1/verify/services ``` ```json { "name": "Login OTP", "codeLength": 6, "ttlSeconds": 300, "maxAttempts": 5, "template": "Your {{serviceName}} verification code is {{code}}. Expires in {{ttlMinutes}} minutes." } ``` Save the returned `id` as `VERIFY_SERVICE_ID` in your env. ### Step 2 — Start a verification ```bash POST https://api.mailssetu.in/v1/verify/start ``` ```json { "serviceId": "vs_abc123", "to": "+919876543210", "idempotencyKey": "login:user_42:2026-05-10T12:00" } ``` Returns `{ "id": "ver_xyz", "status": "pending" }`. Store `id` in session. ### Step 3 — Check the code ```bash POST https://api.mailssetu.in/v1/verify/:id/check { "code": "482910" } ``` Returns `{ "status": "approved" }` or `{ "status": "failed", "attemptsRemaining": 4 }`. In sandbox / test environments the response includes `devCode` so you can verify without SMS. ### Other Verify endpoints ```bash GET https://api.mailssetu.in/v1/verify/services GET https://api.mailssetu.in/v1/verify/services/:id PATCH https://api.mailssetu.in/v1/verify/services/:id DELETE https://api.mailssetu.in/v1/verify/services/:id GET https://api.mailssetu.in/v1/verify GET https://api.mailssetu.in/v1/verify/:id POST https://api.mailssetu.in/v1/verify/:id/cancel ``` --- ## Webhooks — full reference ### Register an endpoint ```bash POST https://api.mailssetu.in/v1/webhook-endpoints ``` ```json { "url": "https://yourapp.com/hooks/mailsetu", "events": [ "email.delivered", "email.bounced", "email.complained", "email.opened", "email.clicked", "sms.delivered", "sms.failed" ] } ``` ### All events `email.queued` · `email.scheduled` · `email.sending` · `email.delivered` `email.opened` · `email.clicked` · `email.bounced` · `email.complained` `email.failed` · `email.canceled` `sms.queued` · `sms.sent` · `sms.delivered` · `sms.failed` · `sms.undelivered` ### Webhook event payload shape ```json { "id": "evt_...", "type": "email.delivered", "createdAt": "2026-05-10T12:01:00.000Z", "data": { "emailId": "em_...", "to": "user@example.com", "from": "hello@yourapp.com", "subject": "Welcome!", "metadata": { "userId": "42" } } } ``` ### Signature verification (Express / Node.js) ```javascript const crypto = require('crypto') app.post('/hooks/mailsetu', express.raw({ type: 'application/json' }), (req, res) => { const sig = req.headers['x-mailsetu-signature'] const expected = crypto .createHmac('sha256', process.env.MAILSETU_WEBHOOK_SECRET) .update(req.body) // raw Buffer, not parsed JSON .digest('hex') if (!crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected))) { return res.status(401).send('Bad signature') } const event = JSON.parse(req.body) switch (event.type) { case 'email.bounced': // suppress from your own DB break case 'email.complained': // unsubscribe user break case 'email.opened': // update CRM break } res.sendStatus(200) }) ``` ### Signature verification (Python / Django) ```python import hmac, hashlib def mailsetu_webhook(request): sig = request.headers.get('X-Mailsetu-Signature', '') expected = hmac.new( os.environ['MAILSETU_WEBHOOK_SECRET'].encode(), request.body, hashlib.sha256 ).hexdigest() if not hmac.compare_digest(sig, expected): return HttpResponse(status=401) event = json.loads(request.body) # handle event.type ... return HttpResponse(status=200) ``` ### Webhook management ```bash GET https://api.mailssetu.in/v1/webhook-endpoints GET https://api.mailssetu.in/v1/webhook-endpoints/events # list all event types GET https://api.mailssetu.in/v1/webhook-endpoints/:id PATCH https://api.mailssetu.in/v1/webhook-endpoints/:id # update url/events/active POST https://api.mailssetu.in/v1/webhook-endpoints/:id/rotate-secret # rotate signing secret POST https://api.mailssetu.in/v1/webhook-endpoints/:id/test # fire a test event GET https://api.mailssetu.in/v1/webhook-endpoints/:id/deliveries # delivery log with status POST https://api.mailssetu.in/v1/webhook-endpoints/deliveries/:id/replay # replay a failed delivery DELETE https://api.mailssetu.in/v1/webhook-endpoints/:id ``` --- ## Suppressions Bounced and complained addresses are automatically suppressed. Manual management: ```bash GET https://api.mailssetu.in/v1/suppressions # list (page, search, reason) GET https://api.mailssetu.in/v1/suppressions/check?email=x@y.com # check one address GET https://api.mailssetu.in/v1/suppressions/export # CSV download POST https://api.mailssetu.in/v1/suppressions # add one: { email, reason } POST https://api.mailssetu.in/v1/suppressions/bulk # add many DELETE https://api.mailssetu.in/v1/suppressions/email/:email # remove by address DELETE https://api.mailssetu.in/v1/suppressions/:id # remove by ID ``` --- ## Inbound email routing ```bash POST https://api.mailssetu.in/v1/inbound/domains # register an inbound domain GET https://api.mailssetu.in/v1/inbound/domains # list registered domains POST https://api.mailssetu.in/v1/inbound/routes # create routing rule GET https://api.mailssetu.in/v1/inbound/routes # list rules GET https://api.mailssetu.in/v1/inbound/routes/:id PATCH https://api.mailssetu.in/v1/inbound/routes/:id POST https://api.mailssetu.in/v1/inbound/routes/:id/rotate-secret DELETE https://api.mailssetu.in/v1/inbound/routes/:id GET https://api.mailssetu.in/v1/inbound/emails # received email log GET https://api.mailssetu.in/v1/inbound/emails/:id GET https://api.mailssetu.in/v1/inbound/emails/:id/attachments GET https://api.mailssetu.in/v1/inbound/emails/:id/attachments/:aid POST https://api.mailssetu.in/v1/inbound/emails/:id/replay ``` Inbound webhook delivers a parsed JSON payload to your endpoint including sender, subject, text body, HTML body, headers, and attachment references. --- ## SMTP relay For apps that can't call the REST API: ```bash POST https://api.mailssetu.in/v1/smtp/credentials # create SMTP credentials GET https://api.mailssetu.in/v1/smtp/credentials # list credentials DELETE https://api.mailssetu.in/v1/smtp/credentials/:id GET https://api.mailssetu.in/v1/smtp/settings # get host/port/TLS settings ``` Settings: - Host: `smtp.mailssetu.in` - Port 587 — STARTTLS (recommended) - Port 465 — SSL/TLS - Username: generated per credential - Password: generated per credential (shown once) --- ## SDK examples — all languages ### Node.js / TypeScript ```bash npm install mailsetu-js ``` ```typescript import { MailSetu } from 'mailsetu-js' const mail = new MailSetu({ apiKey: process.env.MAILSETU_API_KEY! }) // Send const email = await mail.emails.send({ from: 'hello@yourapp.com', to: user.email, subject: `Welcome, ${user.name}!`, templateId: process.env.MAILSETU_WELCOME_TEMPLATE_ID, variables: { name: user.name }, idempotencyKey: `welcome-${user.id}-v1`, }) // OTP const { id } = await mail.verify.start({ serviceId: process.env.MAILSETU_VERIFY_SERVICE_ID!, to: phone, }) // later... const result = await mail.verify.check(id, code) if (result.status !== 'approved') throw new Error('Wrong OTP') ``` ### Python ```bash pip install mailsetu ``` ```python import os from mailsetu import MailSetu client = MailSetu(api_key=os.environ["MAILSETU_API_KEY"]) # Send response = client.emails.send( from_="hello@yourapp.com", to="user@example.com", subject="Welcome!", html="

Welcome

", idempotency_key="welcome-user-001", ) # OTP verification = client.verify.start( service_id=os.environ["MAILSETU_VERIFY_SERVICE_ID"], to="+919876543210", ) result = client.verify.check(verification["id"], code="482910") ``` ### PHP ```bash composer require mailsetu/mailsetu-php ``` ```php use MailSetu\Client; $client = new Client(['api_key' => getenv('MAILSETU_API_KEY')]); $response = $client->emails->send([ 'from' => 'hello@yourapp.com', 'to' => 'user@example.com', 'subject' => 'Welcome!', 'html' => '

Welcome

', ]); ``` ### Go ```bash go get github.com/mailsetu/mailsetu-go ``` ```go package main import ( "os" mailsetu "github.com/mailsetu/mailsetu-go" ) func main() { client := mailsetu.New(os.Getenv("MAILSETU_API_KEY")) resp, err := client.Emails.Send(mailsetu.SendEmailRequest{ From: "hello@yourapp.com", To: []string{"user@example.com"}, Subject: "Welcome!", HTML: "

Welcome

", }) _ = resp _ = err } ``` ### Java ```xml in.mailsetu mailsetu-java LATEST ``` ```java MailSetuClient client = new MailSetuClient(System.getenv("MAILSETU_API_KEY")); EmailResponse response = client.emails().send( SendEmailRequest.builder() .from("hello@yourapp.com") .to("user@example.com") .subject("Welcome!") .html("

Welcome

") .build() ); ``` ### Ruby ```bash gem install mailsetu ``` ```ruby require 'mailsetu' client = MailSetu::Client.new(api_key: ENV['MAILSETU_API_KEY']) response = client.emails.send( from: 'hello@yourapp.com', to: 'user@example.com', subject: 'Welcome!', html: '

Welcome

' ) ``` ### .NET / C# ```bash dotnet add package MailSetu ``` ```csharp using MailSetu; var client = new MailSetuClient(Environment.GetEnvironmentVariable("MAILSETU_API_KEY")); var response = await client.Emails.SendAsync(new SendEmailRequest { From = "hello@yourapp.com", To = new[] { "user@example.com" }, Subject = "Welcome!", Html = "

Welcome

" }); ``` --- ## Framework integration patterns ### Next.js App Router — transactional email on form submit ```typescript // app/actions/send-welcome.ts 'use server' import { MailSetu } from 'mailsetu-js' const mail = new MailSetu({ apiKey: process.env.MAILSETU_API_KEY! }) export async function sendWelcomeEmail(userId: string, email: string, name: string) { await mail.emails.send({ from: 'welcome@yourapp.com', to: email, templateId: process.env.MAILSETU_WELCOME_TEMPLATE_ID!, variables: { name }, idempotencyKey: `welcome-${userId}`, }) } ``` ### Django — send on user creation signal ```python # signals.py from django.db.models.signals import post_save from django.contrib.auth import get_user_model from mailsetu import MailSetu client = MailSetu(api_key=settings.MAILSETU_API_KEY) User = get_user_model() def on_user_created(sender, instance, created, **kwargs): if not created: return client.emails.send( from_=settings.MAILSETU_FROM_EMAIL, to=instance.email, subject=f"Welcome, {instance.first_name}!", template_id=settings.MAILSETU_WELCOME_TEMPLATE_ID, variables={"name": instance.first_name}, idempotency_key=f"welcome-{instance.pk}", ) post_save.connect(on_user_created, sender=User) ``` ### Laravel — Mailable using HTTP driver ```php // config/mail.php → use 'mailsetu' driver // In a Job or Controller: Mail::to($user)->send(new WelcomeMail($user)); // WelcomeMail.php public function envelope(): Envelope { return new Envelope(subject: "Welcome, {$this->user->name}!"); } public function content(): Content { return new Content(view: 'emails.welcome', with: ['name' => $this->user->name]); } ``` ### Express.js — complete send route ```javascript const { MailSetu } = require('mailsetu-js') const mail = new MailSetu({ apiKey: process.env.MAILSETU_API_KEY }) router.post('/register', async (req, res) => { const { email, name } = req.body const user = await createUser(email, name) await mail.emails.send({ from: 'welcome@yourapp.com', to: email, subject: `Welcome, ${name}!`, html: `

Hi ${name}, your account is ready.

`, idempotencyKey: `welcome-${user.id}`, metadata: { userId: user.id }, }) res.json({ user }) }) ``` --- ## Environment variables reference ### API server (apps/api/.env) ```dotenv DATABASE_URL=postgresql://postgres:postgres@127.0.0.1:5432/mailsetu REDIS_URL=redis://127.0.0.1:6380 JWT_SECRET= CSRF_SECRET= NODE_ENV=production PORT=3001 # SMS provider (optional) SMS_PROVIDER_ENABLED=false SMS_PROVIDER_NAME= SMS_SENDER_ID= # Razorpay (billing) RAZORPAY_KEY_ID=rzp_live_... RAZORPAY_KEY_SECRET= # Worker concurrency EMAIL_WORKER_CONCURRENCY=5 SMS_WORKER_CONCURRENCY=3 QUEUE_JOB_ATTEMPTS=3 QUEUE_BACKOFF_MS=5000 # Optional AUTH_COOKIE_DOMAIN=.mailssetu.in # set for custom domains AUTH_COOKIE_SAME_SITE=lax ``` ### Dashboard (apps/dashboard/.env.local) ```dotenv NEXT_PUBLIC_API_URL=https://api.mailssetu.in NEXT_PUBLIC_SITE_URL=https://mailssetu.in ``` ### Minimum for local development ```dotenv DATABASE_URL=postgresql://postgres:postgres@127.0.0.1:5432/mailsetu REDIS_URL=redis://127.0.0.1:6380 JWT_SECRET=dev-jwt-secret-change-in-production CSRF_SECRET=dev-csrf-secret-change-in-production NODE_ENV=development ``` In development, sandbox keys are auto-issued and emails go through the mock provider (no extra provider setup required). --- ## Error handling All error responses: ```json { "error": "Human-readable message", "code": "MACHINE_CODE", "hint": "What to do." } ``` | HTTP | Code | Meaning | Resolution | |------|------|---------|-----------| | 400 | VALIDATION_ERROR | Schema check failed | Fix the request body fields | | 400 | DOMAIN_NOT_VERIFIED | from domain not verified | Add + verify domain at /v1/domains | | 400 | TEMPLATE_NOT_FOUND | templateId missing | Check template ID | | 400 | VERIFY_CODE_INVALID | Wrong OTP | Prompt user to re-enter | | 400 | VERIFY_CODE_EXPIRED | OTP window passed | Call /v1/verify/start again | | 400 | VERIFY_ATTEMPTS_EXCEEDED | Too many wrong tries | Start a new verification | | 401 | INVALID_API_KEY | Key missing / revoked | Create a new key | | 403 | ACCOUNT_BANNED | Account suspended | Contact support | | 404 | AGENT_INSTALL_TOKEN_INVALID | Token expired | Generate a new install session | | 409 | IDEMPOTENCY_MISMATCH | Key reused with different payload | Use a unique key per unique send | | 409 | AGENT_INSTALL_TOKEN_CONSUMED | Token already exchanged | Token can only be used once | | 429 | QUOTA_EXCEEDED | Monthly send limit reached | Upgrade plan or buy credits | Rate limit headers on every response: `X-RateLimit-Limit` · `X-RateLimit-Remaining` · `X-RateLimit-Reset` --- ## AI agent / automated setup flow Designed for coding assistants and deploy pipelines to configure MailsSetu without requiring the owner's permanent credentials. ### Discovery ```bash GET https://api.mailssetu.in/.well-known/agent.json # capabilities + install instructions GET https://api.mailssetu.in/.well-known/openapi.json # OpenAPI 3.1.0 spec (all endpoints) GET https://api.mailssetu.in/v1/agent/manifest # same as agent.json via versioned path ``` ### Install session → scoped API key (3 steps) 1. Owner creates an install session from the dashboard (or API): ```bash POST https://api.mailssetu.in/v1/agent/install-sessions Authorization: Bearer { "label": "Vercel deploy", "scopes": ["send","read"], "sandbox": false, "ttlMinutes": 15 } ``` Returns `{ "token": "ms_agent_..." }`. 2. Owner shares token with the agent (e.g. via env var in CI). 3. Agent exchanges the token for a scoped API key: ```bash POST https://api.mailssetu.in/v1/agent/exchange { "token": "ms_agent_..." } ``` Returns `{ "key": "ms_live_...", "scopes": ["send","read"] }`. Token is consumed — can never be reused. ### Security properties - Token is one-time use with configurable TTL (default 15 min) - Can be restricted to sandbox only - Can restrict allowed email domains - Agent never receives the owner's password or permanent API key - Audit log entry created on every exchange --- ## Onboarding status ```bash GET https://api.mailssetu.in/v1/onboarding Authorization: Bearer ``` ```json { "complete": false, "steps": [ { "key": "sandbox_key", "title": "Create a sandbox API key", "done": true }, { "key": "sandbox_send", "title": "Send a sandbox email", "done": true }, { "key": "live_key", "title": "Create a live API key", "done": false }, { "key": "domain", "title": "Verify a sending domain", "done": false }, { "key": "first_live_email", "title": "Send your first live email", "done": false } ] } ``` Use this to drive onboarding UI or to check setup completeness in an agent workflow. --- ## Plans and quotas | Plan | Monthly emails | Price | |------|---------------|-------| | FREE | 3,000 | ₹0 | | STARTER | 60,000 | ₹399/month | | GROWTH | 3,00,000 | ₹1,199/month | Top-up credits can be purchased any time and stack on top of plan quota. Billing is INR-only. Payment: UPI, debit/credit cards, net banking via Razorpay. GST invoices generated automatically. GSTIN: 29AANCR6717K1ZN. Usage API: ```bash GET https://api.mailssetu.in/v1/billing/usage ``` ```json { "used": 1240, "limit": 3000, "credits": 500, "totalCapacity": 3500, "plan": "FREE", "month": "2026-05" } ``` --- ## Links - Dashboard: https://app.mailssetu.in/dashboard - Quickstart: https://app.mailssetu.in/docs/quickstart - API Reference: https://app.mailssetu.in/api-reference - Node.js SDK: https://app.mailssetu.in/docs/node - Python SDK: https://app.mailssetu.in/docs/python - PHP SDK: https://app.mailssetu.in/docs/php - Go SDK: https://app.mailssetu.in/docs/go - Java SDK: https://app.mailssetu.in/docs/java - AI agents: https://app.mailssetu.in/docs/agents - WordPress: https://app.mailssetu.in/docs/wordpress - Shopify: https://app.mailssetu.in/docs/shopify - Next.js: https://app.mailssetu.in/docs/nextjs - Django: https://app.mailssetu.in/docs/django - Laravel: https://app.mailssetu.in/docs/laravel - Pricing: https://app.mailssetu.in/pricing - Status: https://app.mailssetu.in/status - llms.txt: https://app.mailssetu.in/llms.txt - Support: support@mailssetu.in