# BaviMail Documentation -- Full Content > Complete documentation for the BaviMail email API. ============================================================ # Get Started ============================================================ # BaviMail Documentation Learn how to send email, receive inbound traffic, verify domains, subscribe to webhooks, and integrate with AI agents. ## Getting Started - **Quickstart**: Install the SDK and send your first email in under 5 minutes. - **Authentication**: Create API keys and authenticate requests. - **Agent Onboarding**: Give your AI agent its own email identity. ## API Reference - **API Overview**: REST API surface for email, domains, inbound, webhooks, analytics, and suppressions. - **API Explorer**: Interactive playground to test endpoints. - **Error Reference**: Every error code with remediation actions. ## Guides - **Domain Verification**: SPF, DKIM, DMARC setup. - **Inbound Email**: Receive and process inbound messages. - **Webhooks**: Event types, payloads, signature verification. - **SDK Guides**: TypeScript and Python clients. ---------------------------------------- # Quickstart Send your first email in under 5 minutes. ## 1. Install the SDK **TypeScript** ```bash npm install bavimail ``` **Python** ```bash pip install bavimail ``` ## 2. Get Your API Key 1. Sign in to the [BaviMail Dashboard](https://app.bavimail.com). 2. Navigate to **Settings > API Keys**. 3. Click **Create API Key**, give it a name, and copy the key. Keys use the `bav_live_` prefix. Store it securely -- you won't see it again. ## 3. Set Your Environment Variable ```bash export BAVIMAIL_API_KEY="bav_live_your_key_here" ``` Or add it to your `.env` file: ``` BAVIMAIL_API_KEY=bav_live_your_key_here ``` ## 4. Send Your First Email **TypeScript** ```typescript import { BaviMail } from 'bavimail' const client = new BaviMail({ apiKey: process.env.BAVIMAIL_API_KEY }) const email = await client.emails.send({ aliasId: 'alias_abc123', toEmail: 'recipient@example.com', subject: 'Hello from BaviMail', body: '
Your first email, sent via BaviMail.
', }) console.log('Sent:', email.id) ``` **Python** ```python import os from bavimail import BaviMail client = BaviMail(api_key=os.environ["BAVIMAIL_API_KEY"]) email = client.emails.send( alias_id="alias_abc123", to_email="recipient@example.com", subject="Hello from BaviMail", body="Your first email, sent via BaviMail.
", ) print("Sent:", email.id) ``` ## Next Steps - [Authentication](/docs/authentication) -- API key management and security best practices. - [Domain Verification](/docs/domains) -- Set up SPF, DKIM, and DMARC for production sending. - [Agent Onboarding](/docs/agent-onboarding) -- Give your AI agent its own email identity. ---------------------------------------- # Authentication Every request to the BaviMail API requires a valid API key. This guide covers key format, request authentication, SDK initialization, key management, and security best practices. ## API Key Format All BaviMail API keys use the `bav_live_` prefix followed by a random string: ``` bav_live_a1b2c3d4e5f6g7h8i9j0... ``` The prefix lets you identify BaviMail keys in your codebase and secrets scanners. ## Authenticating Requests Pass your API key as a Bearer token in the `Authorization` header: ```bash curl -X POST https://api.bavimail.com/v2/emails \ -H "Authorization: Bearer bav_live_your_key_here" \ -H "Content-Type: application/json" \ -d '{"aliasId": "alias_abc123", "toEmail": "user@example.com", "subject": "Hello", "body": "Hi
"}' ``` ## SDK Initialization **TypeScript** ```typescript import { BaviMail } from 'bavimail' // Reads BAVIMAIL_API_KEY from env by default const client = new BaviMail() // Or pass explicitly const client = new BaviMail({ apiKey: 'bav_live_your_key_here' }) ``` **Python** ```python from bavimail import BaviMail # Reads BAVIMAIL_API_KEY from env by default client = BaviMail() # Or pass explicitly client = BaviMail(api_key="bav_live_your_key_here") ``` ## Key Management ### Create a Key 1. Go to **Settings > API Keys** in the dashboard. 2. Click **Create API Key**. 3. Name the key (e.g., `production-backend`, `staging-agent`). 4. Copy the key immediately -- it is shown only once. ### Revoke a Key 1. Go to **Settings > API Keys**. 2. Find the key and click **Revoke**. 3. The key stops working immediately. Any request using it returns `401`. ### Rotate a Key 1. Create a new key. 2. Deploy the new key to your application. 3. Verify traffic is flowing on the new key. 4. Revoke the old key. There is no downtime if you run both keys in parallel during rotation. ## Security Best Practices - **Never commit keys to version control.** Use environment variables or a secrets manager. - **Use separate keys per environment.** One for production, one for staging, one for development. - **Set the narrowest permissions needed.** If a key only needs to send email, don't grant domain management. - **Rotate keys periodically.** At minimum, rotate when a team member leaves or a key may have been exposed. - **Monitor key usage.** Check the dashboard for unexpected spikes in API calls. ## Error Responses | Status | Code | Description | |--------|------|-------------| | `401` | `unauthorized` | Missing or invalid API key. Check that your `Authorization` header is `Bearer bav_live_...`. | | `403` | `forbidden` | The API key is valid but lacks permission for this action. Check key permissions in the dashboard. | ---------------------------------------- ============================================================ # API ============================================================ # API Overview The BaviMail REST API lets you send email, manage domains, process inbound messages, subscribe to webhooks, query analytics, and manage suppressions. ## Structure - **Base URL**: `https://api.bavimail.com/v2` - **Format**: JSON request and response bodies. - **Authentication**: Bearer token via the `Authorization` header. See [Authentication](/docs/authentication). - **Versioning**: The API version is in the URL path (`/v2`). Breaking changes increment the version. ## Resource Groups ### Emails Send transactional and agent-driven email. ``` POST /v2/emails Send an email GET /v2/emails List sent emails GET /v2/emails/:id Get email details DELETE /v2/emails/:id Cancel a scheduled email ``` ### Domains Register and verify sending domains. ``` POST /v2/domains Add a domain GET /v2/domains List domains GET /v2/domains/:id Get domain details POST /v2/domains/:id/verify Trigger DNS verification DELETE /v2/domains/:id Remove a domain ``` ### Inbound Read and manage received email. ``` GET /v2/inbound List inbound messages GET /v2/inbound/:id Get message details DELETE /v2/inbound/:id Delete a message ``` ### Webhooks Subscribe to real-time events. ``` POST /v2/webhooks Create a webhook GET /v2/webhooks List webhooks GET /v2/webhooks/:id Get webhook details PATCH /v2/webhooks/:id Update a webhook DELETE /v2/webhooks/:id Delete a webhook POST /v2/webhooks/:id/test Send a test event ``` ### Analytics Query delivery and engagement metrics. ``` GET /v2/analytics/overview Aggregate stats (sent, delivered, opened, clicked, bounced) GET /v2/analytics/timeseries Time-bucketed metrics ``` ### Suppressions Manage suppressed email addresses (bounces, complaints, manual blocks). ``` GET /v2/suppressions List suppressions POST /v2/suppressions Add a suppression DELETE /v2/suppressions/:id Remove a suppression ``` ## Implementation Order If you are integrating BaviMail for the first time, follow this order: 1. **Authenticate**: Create an API key and confirm access with a test request. 2. **Verify a domain**: Add DNS records so you can send from your domain. 3. **Send email**: Use the Emails API to send your first message. 4. **Subscribe to webhooks**: Get real-time delivery and engagement events. ---------------------------------------- # Error Reference Every API error returns a consistent JSON body with an HTTP status code, error code, and human-readable message. ## Error Format ```json { "error": { "code": "validation_error", "message": "The 'toEmail' field must be a valid email address.", "details": {} } } ``` ## Error Codes **400 Bad Request** -- `bad_request` -- The request body is malformed or missing required fields. Check that your JSON is valid and all required fields are present. **401 Unauthorized** -- `unauthorized` -- Missing or invalid API key. Ensure the `Authorization` header is set to `Bearer bav_live_...` with a valid key. **403 Forbidden** -- `forbidden` -- The API key does not have permission for this action. Check key permissions in Settings > API Keys. Create a new key with the required scope. **404 Not Found** -- `not_found` -- The requested resource does not exist. Verify the resource ID. It may have been deleted or belongs to a different account. **409 Conflict** -- `conflict` -- The resource already exists or the operation conflicts with the current state. Check for duplicate resources. Use idempotency keys for retries. **422 Validation Error** -- `validation_error` -- One or more fields failed validation. Read the `details` object for field-level errors. Fix the invalid fields and retry. **429 Rate Limited** -- `rate_limited` -- Too many requests. Try again later. Back off and retry after the `Retry-After` header value (in seconds). Implement exponential backoff. **500 Internal Error** -- `internal_error` -- An unexpected error occurred on our end. Retry with exponential backoff. If it persists, contact support@bavimail.com with the `X-Request-Id` header value. ## Best Practices - **4xx errors** are client errors -- fix the request before retrying. - **5xx errors** are server errors -- retry with exponential backoff. - Never retry **401** or **403** without fixing the API key or permissions first. - Always log the `X-Request-Id` response header for debugging with support. ---------------------------------------- ============================================================ # Guides ============================================================ # Domain Verification Verify your sending domain to improve deliverability and prove to mailbox providers that you own the domain. BaviMail always requires SPF, DKIM, DMARC, and MAIL FROM records. The root inbound MX record is only required when inbound email is enabled for the domain. ## Steps ### 1. Add Your Domain **TypeScript** ```typescript import { BaviMail } from 'bavimail' const client = new BaviMail() const domain = await client.domains.create({ domain: 'mail.yourcompany.com', inboundEnabled: false, }) console.log('Domain ID:', domain.id) console.log('DNS records to add:', domain.dnsRecords) ``` **Python** ```python from bavimail import BaviMail client = BaviMail() domain = client.domains.create(domain="mail.yourcompany.com", inbound_enabled=False) print("Domain ID:", domain.id) print("DNS records to add:", domain.dns_records) ``` ### 2. Add DNS Records Add the records returned by the API to your DNS provider. You will receive records for: | Record | Type | Purpose | |--------|------|---------| | **SPF** | TXT | Authorizes BaviMail's IP addresses to send email on behalf of your domain. | | **DKIM** | CNAME | Cryptographic signature that proves the email was sent by an authorized server and was not altered in transit. | | **DMARC** | TXT | Tells receiving servers how to handle email that fails SPF or DKIM checks (quarantine, reject, or none). | | **MAIL FROM** | MX/TXT | Routes bounce notifications back to BaviMail so you get delivery feedback. | | **Inbound Email** | MX | Only returned when inbound email is enabled for the domain. | ### 3. Trigger Verification Once you have added the DNS records, trigger verification: **TypeScript** ```typescript const result = await client.domains.verify(domain.id) console.log('Status:', result.status) // 'pending' | 'verified' | 'failed' ``` **Python** ```python result = client.domains.verify(domain.id) print("Status:", result.status) # 'pending' | 'verified' | 'failed' ``` BaviMail checks your DNS records automatically every few minutes. You can also trigger a manual check from the dashboard. ### 4. Start Sending Once `status` is `verified`, you can create aliases on the domain and send email. ## Common Issues - **DNS propagation takes time.** Most providers propagate within minutes, but some take up to 48 hours. If verification fails, wait and retry. - **CNAME conflicts.** Some DNS providers don't allow a CNAME at the zone apex. Use a subdomain (e.g., `mail.yourcompany.com`) instead. - **Existing SPF records.** If you already have an SPF record, merge it with the BaviMail include rather than creating a second TXT record. Multiple SPF records cause failures. - **DMARC policy too strict.** Start with `p=none` while testing, then move to `p=quarantine` or `p=reject` once you confirm delivery is working. ---------------------------------------- # Inbound Email Receive, parse, and process incoming email with BaviMail. Inbound messages are automatically parsed and stored, accessible via API or webhook. ## How It Works 1. **Receive**: Email arrives at an alias on your verified domain (e.g., `support@mail.yourcompany.com`). 2. **Parse**: BaviMail extracts the sender, recipients, subject, plain text, HTML body, headers, and attachments. 3. **Store**: The parsed message is stored and available via the API. 4. **Notify**: If you have a webhook subscribed to `INBOUND_RECEIVED`, BaviMail sends a POST to your endpoint. ## Reading Messages via API **TypeScript** ```typescript import { BaviMail } from 'bavimail' const client = new BaviMail() // List inbound messages for an alias const messages = await client.inbound.list({ aliasId: 'alias_abc123' }) for (const msg of messages.data) { console.log(`From: ${msg.from}`) console.log(`Subject: ${msg.subject}`) console.log(`Body: ${msg.textBody}`) } // Get a single message with full details const message = await client.inbound.get('msg_xyz789') console.log('HTML:', message.htmlBody) console.log('Attachments:', message.attachments.length) ``` **Python** ```python from bavimail import BaviMail client = BaviMail() # List inbound messages for an alias messages = client.inbound.list(alias_id="alias_abc123") for msg in messages.data: print(f"From: {msg.from_email}") print(f"Subject: {msg.subject}") print(f"Body: {msg.text_body}") # Get a single message with full details message = client.inbound.get("msg_xyz789") print("HTML:", message.html_body) print("Attachments:", len(message.attachments)) ``` ## Features - **Parsed content**: Plain text and HTML bodies extracted automatically. - **Attachments**: Files are stored and accessible via download URL. Metadata includes filename, content type, and size. - **Headers**: Full email headers preserved for debugging and routing. - **Conversations**: Messages are threaded by `In-Reply-To` and `References` headers for conversation tracking. ## Webhook Processing Subscribe to the `INBOUND_RECEIVED` event to process messages in real time: ```json { "id": "evt_abc123", "type": "INBOUND_RECEIVED", "createdAt": "2026-04-11T14:30:00Z", "data": { "messageId": "msg_xyz789", "aliasId": "alias_abc123", "from": "sender@example.com", "to": "support@mail.yourcompany.com", "subject": "Help with my account", "textBody": "Hi, I need help resetting my password...", "htmlBody": "Hi, I need help resetting my password...
", "attachments": [], "receivedAt": "2026-04-11T14:30:00Z" } } ``` See the [Webhooks guide](/docs/webhooks) for signature verification and retry behavior. ---------------------------------------- # Webhooks Subscribe to real-time events from BaviMail. When something happens -- an email is delivered, a recipient clicks a link, a domain verifies -- BaviMail sends an HTTP POST to your endpoint. ## Event Types BaviMail supports 13 webhook event types: | Event | Description | |-------|-------------| | `INBOUND_RECEIVED` | A new inbound email was received and parsed. | | `OUTBOUND_SENT` | An outbound email was accepted for delivery. | | `OUTBOUND_DELIVERED` | An outbound email was delivered to the recipient's mail server. | | `OUTBOUND_OPENED` | A recipient opened an outbound email (pixel tracking). | | `OUTBOUND_CLICKED` | A recipient clicked a link in an outbound email. | | `OUTBOUND_BOUNCED` | An outbound email bounced (hard or soft). | | `OUTBOUND_COMPLAINED` | A recipient marked an outbound email as spam. | | `OUTBOUND_FAILED` | An outbound email permanently failed to send. | | `OUTBOUND_SCHEDULED` | An outbound email was scheduled for future delivery. | | `OUTBOUND_CANCELLED` | A scheduled outbound email was cancelled before sending. | | `DOMAIN_VERIFIED` | A domain passed DNS verification. | | `DOMAIN_FAILED` | A domain failed DNS verification after retries. | | `WEBHOOK_TEST` | A test event sent when you create or test a webhook endpoint. | ## Example Payload Every webhook POST has the same envelope structure: ```json { "id": "evt_abc123", "type": "OUTBOUND_DELIVERED", "createdAt": "2026-04-11T14:30:00Z", "data": { "emailId": "em_xyz789", "aliasId": "alias_abc123", "toEmail": "recipient@example.com", "subject": "Hello from BaviMail", "deliveredAt": "2026-04-11T14:30:00Z" } } ``` The `data` field varies by event type. See the API Explorer for full schemas. ## Creating a Webhook **TypeScript** ```typescript import { BaviMail } from 'bavimail' const client = new BaviMail() const webhook = await client.webhooks.create({ url: 'https://your-app.com/webhooks/bavimail', events: ['OUTBOUND_DELIVERED', 'OUTBOUND_BOUNCED', 'INBOUND_RECEIVED'], }) console.log('Webhook ID:', webhook.id) console.log('Signing secret:', webhook.secret) ``` **Python** ```python from bavimail import BaviMail client = BaviMail() webhook = client.webhooks.create( url="https://your-app.com/webhooks/bavimail", events=["OUTBOUND_DELIVERED", "OUTBOUND_BOUNCED", "INBOUND_RECEIVED"], ) print("Webhook ID:", webhook.id) print("Signing secret:", webhook.secret) ``` ## Verifying Signatures Every webhook request includes a `X-BaviMail-Signature` header. Verify it using the signing secret returned when you created the webhook: ```typescript import { createHmac } from 'crypto' function verifyWebhook(payload: string, signature: string, secret: string): boolean { const expected = createHmac('sha256', secret).update(payload).digest('hex') return expected === signature } ``` ## Best Practices - **Return a `200` response quickly.** Process the event asynchronously. BaviMail times out after 10 seconds. - **Be idempotent.** Use the `id` field to deduplicate. BaviMail may deliver the same event more than once. - **Verify signatures.** Always validate the `X-BaviMail-Signature` header to confirm the request came from BaviMail. - **Subscribe only to events you need.** Reduces noise and processing load. - **Log raw payloads.** Helps with debugging delivery issues. ## Retry Behavior If your endpoint returns a non-2xx response or times out, BaviMail retries with exponential backoff: | Attempt | Delay | |---------|-------| | 1 | Immediate | | 2 | 1 minute | | 3 | 5 minutes | | 4 | 30 minutes | | 5 | 2 hours | After 5 failed attempts, the event is marked as failed. You can view and retry failed events in the dashboard. ---------------------------------------- # SDK Guides Official TypeScript and Python clients for the BaviMail API. ## TypeScript ### Installation ```bash npm install bavimail ``` **Requirements**: Node.js 18+, Bun, or Deno. ### Usage ```typescript import { BaviMail } from 'bavimail' const client = new BaviMail() // reads BAVIMAIL_API_KEY from env // Send an email const email = await client.emails.send({ aliasId: 'alias_abc123', toEmail: 'recipient@example.com', subject: 'Hello', body: 'Sent via BaviMail SDK
', }) // Verify a domain const domain = await client.domains.create({ domain: 'mail.yourcompany.com', inboundEnabled: false, }) await client.domains.verify(domain.id) // Create a webhook const webhook = await client.webhooks.create({ url: 'https://your-app.com/hooks', events: ['OUTBOUND_DELIVERED', 'INBOUND_RECEIVED'], }) ``` ### Async Support All methods return Promises. Use `async/await` or `.then()`: ```typescript // async/await const email = await client.emails.send({ ... }) // Promise chain client.emails.send({ ... }).then(email => console.log(email.id)) ``` ### Error Handling ```typescript import { BaviMail, AuthenticationError, NotFoundError, RateLimitError, ValidationError } from 'bavimail' try { await client.emails.send({ ... }) } catch (err) { if (err instanceof AuthenticationError) { // 401 -- invalid or missing API key } else if (err instanceof NotFoundError) { // 404 -- resource does not exist } else if (err instanceof RateLimitError) { // 429 -- back off and retry after err.retryAfter seconds } else if (err instanceof ValidationError) { // 422 -- check err.details for field-level errors } } ``` ## Python ### Installation ```bash pip install bavimail ``` **Requirements**: Python 3.9+. Uses `httpx` under the hood. ### Usage ```python from bavimail import BaviMail client = BaviMail() # reads BAVIMAIL_API_KEY from env # Send an email email = client.emails.send( alias_id="alias_abc123", to_email="recipient@example.com", subject="Hello", body="Sent via BaviMail SDK
", ) # Verify a domain domain = client.domains.create(domain="mail.yourcompany.com", inbound_enabled=False) client.domains.verify(domain.id) # Create a webhook webhook = client.webhooks.create( url="https://your-app.com/hooks", events=["OUTBOUND_DELIVERED", "INBOUND_RECEIVED"], ) ``` ### Async Support Use the async client for non-blocking I/O: ```python from bavimail import AsyncBaviMail client = AsyncBaviMail() email = await client.emails.send( alias_id="alias_abc123", to_email="recipient@example.com", subject="Hello", body="Sent via BaviMail SDK
", ) ``` ### Error Handling ```python from bavimail import BaviMail from bavimail.errors import AuthenticationError, NotFoundError, RateLimitError, ValidationError try: client.emails.send(...) except AuthenticationError: # 401 -- invalid or missing API key pass except NotFoundError: # 404 -- resource does not exist pass except RateLimitError as e: # 429 -- back off and retry after e.retry_after seconds pass except ValidationError as e: # 422 -- check e.details for field-level errors pass ``` ---------------------------------------- ============================================================ # AI Agents ============================================================ # Agent Onboarding Give your AI agent its own email identity. BaviMail is built for agents that need to send and receive email autonomously. ## Why Agents Need Email Identity Most email APIs assume a human is composing messages. AI agents need: - **A persistent address**: An alias the agent owns, so replies come back to it. - **Two-way capability**: Send outbound and receive inbound on the same address. - **Programmatic access**: No UI required -- everything via API. - **Conversation threading**: Automatic thread tracking via `In-Reply-To` and `References` headers. ## 5-Line Setup Create an alias and send your first agent email: **TypeScript** ```typescript import { BaviMail } from 'bavimail' const client = new BaviMail() const alias = await client.aliases.create({ domainId: 'dom_abc123', localPart: 'agent' }) const email = await client.emails.send({ aliasId: alias.id, toEmail: 'customer@example.com', subject: 'Your request has been processed', body: 'Hi, your support ticket #1234 has been resolved.
', }) ``` **Python** ```python from bavimail import BaviMail client = BaviMail() alias = client.aliases.create(domain_id="dom_abc123", local_part="agent") email = client.emails.send( alias_id=alias.id, to_email="customer@example.com", subject="Your request has been processed", body="Hi, your support ticket #1234 has been resolved.
", ) ``` Your agent now has the address `agent@mail.yourcompany.com` and can both send and receive email. ## Use Cases ### Support Agent An AI agent that monitors inbound email, understands the request, and replies with a resolution or escalates to a human. ### Notification Agent An agent that sends transactional emails (order confirmations, password resets, status updates) triggered by events in your system. ### Outreach Agent An agent that sends personalized outreach email on a schedule, tracks opens and clicks, and adjusts messaging based on engagement. ### Workflow Agent An agent that receives structured data via email (approvals, form submissions, reports) and triggers downstream actions in your pipeline. ## AI-Consumable Docs BaviMail publishes machine-readable documentation for AI agents: - **`/llms.txt`**: Structured index of all documentation sections. - **`/llms-full.txt`**: Full documentation content, concatenated for context windows. Point your agent's retrieval system at these endpoints for up-to-date API reference without scraping HTML. ---------------------------------------- ============================================================ # Resources ============================================================ # Changelog All notable changes to the BaviMail API, SDKs, and documentation. ## April 2026 ### Documentation Overhaul - Rewrote all documentation with practical code examples in TypeScript and Python. - Added AI-consumable docs at `/llms.txt` and `/llms-full.txt`. - New guides: Agent Onboarding, Inbound Email, SDK Guides. - Published interactive API Explorer. ### API v2.0 - New `/v2` API surface with consistent JSON error format. - Added webhook signature verification with `X-BaviMail-Signature` header. - Added analytics endpoints (`/v2/analytics/overview`, `/v2/analytics/timeseries`). - Added suppressions management (`/v2/suppressions`). - Improved rate limiting with `Retry-After` header. ## February 2026 ### Initial Launch - BaviMail API v1.0 released. - Core features: send email, domain verification, inbound email, webhooks. - TypeScript and Python SDKs published to npm and PyPI. - Dashboard with API key management, domain DNS status, and webhook logs. ----------------------------------------