# Download attachment by position Source: https://inbound.new/docs/api-reference/attachments/download-attachment-by-position https://inbound.new/openapi.json get /api/e2/attachments/{id}/parts/{index} Download an attachment by its position in the email (the `index` from List email attachments; webhook `downloadUrl`s use this form). Works for attachments without a filename and for several attachments sharing one name. # Download email attachment Source: https://inbound.new/docs/api-reference/attachments/download-email-attachment https://inbound.new/openapi.json get /api/e2/attachments/{id}/{filename} Download an attachment by filename. If several attachments share the name, the first is returned; use Download attachment by position to get a specific one. # List email attachments Source: https://inbound.new/docs/api-reference/attachments/list-email-attachments https://inbound.new/openapi.json get /api/e2/attachments/{id} List every attachment in a received email, including ones without a filename, with a download URL for each. # Revoke the current API key Source: https://inbound.new/docs/api-reference/authentication/revoke-the-current-api-key https://inbound.new/openapi.json post /api/e2/auth/revoke-key Permanently revokes the API key used to authenticate this request. # Create new domain Source: https://inbound.new/docs/api-reference/domains/create-new-domain https://inbound.new/openapi.json post /api/e2/domains Add a new domain for email receiving. Automatically initiates verification and returns required DNS records. Subdomains inherit verification from their verified parent domain. # Delete domain Source: https://inbound.new/docs/api-reference/domains/delete-domain https://inbound.new/openapi.json delete /api/e2/domains/{id} Delete a domain and all associated resources including email addresses, DNS records, and SES configurations. Root domains with subdomains must have subdomains deleted first. # Enable DKIM Source: https://inbound.new/docs/api-reference/domains/enable-dkim https://inbound.new/openapi.json post /api/e2/domains/{id}/dkim Start DKIM signing for a domain and return the three CNAME records to add to its DNS. Until they are published, mail is still sent and signed by Amazon SES. Safe to call again: it returns the existing records, or restarts setup after a failure. # Get domain by ID Source: https://inbound.new/docs/api-reference/domains/get-domain-by-id https://inbound.new/openapi.json get /api/e2/domains/{id} Get detailed information about a specific domain including DNS records. Use `?check=true` for a live verification check. # List all domains Source: https://inbound.new/docs/api-reference/domains/list-all-domains https://inbound.new/openapi.json get /api/e2/domains Get paginated list of domains for authenticated user with optional filtering. # Update domain catch-all and subdomain settings Source: https://inbound.new/docs/api-reference/domains/update-domain-catch-all-and-subdomain-settings https://inbound.new/openapi.json patch /api/e2/domains/{id} Update catch-all email settings for a domain. Catch-all receives emails sent to any address on your domain. Set includeSubdomains on a verified root domain to also receive mail for every subdomain (publish the returned wildcard MX record); subdomain mail is delivered to the root domain's catch-all endpoint. Domain must be verified first. # Create email address Source: https://inbound.new/docs/api-reference/email-addresses/create-email-address https://inbound.new/openapi.json post /api/e2/email-addresses Create a new email address for an authenticated user's domain, optionally routing to a webhook or endpoint. # Delete email address Source: https://inbound.new/docs/api-reference/email-addresses/delete-email-address https://inbound.new/openapi.json delete /api/e2/email-addresses/{id} Delete an email address. Returns cleanup status. # Get email address Source: https://inbound.new/docs/api-reference/email-addresses/get-email-address https://inbound.new/openapi.json get /api/e2/email-addresses/{id} Get a specific email address by ID with detailed information including routing configuration # List all email addresses Source: https://inbound.new/docs/api-reference/email-addresses/list-all-email-addresses https://inbound.new/openapi.json get /api/e2/email-addresses Get paginated list of email addresses for authenticated user with optional filtering by domain, active status, and receipt rule configuration # Update email address Source: https://inbound.new/docs/api-reference/email-addresses/update-email-address https://inbound.new/openapi.json put /api/e2/email-addresses/{id} Update an email address's routing (endpoint/webhook) or active status. Cannot have both endpoint and webhook. # Cancel scheduled email Source: https://inbound.new/docs/api-reference/emails/cancel-scheduled-email https://inbound.new/openapi.json delete /api/e2/emails/{id} Cancel a scheduled email by ID. Only works for emails that haven't been sent yet. # Get email by ID Source: https://inbound.new/docs/api-reference/emails/get-email-by-id https://inbound.new/openapi.json get /api/e2/emails/{id} Retrieve a single email by ID. Works for sent, received, and scheduled emails. # List all emails Source: https://inbound.new/docs/api-reference/emails/list-all-emails https://inbound.new/openapi.json get /api/e2/emails List all email activity (sent, received, and scheduled) with comprehensive filtering options. # Pause scheduled email Source: https://inbound.new/docs/api-reference/emails/pause-scheduled-email https://inbound.new/openapi.json post /api/e2/emails/{id}/pause Pause a scheduled email by ID. The email is removed from the delivery queue but kept in the database and can be resumed later with POST /emails/:id/resume. Only works for emails with status "scheduled". # Reply to an email Source: https://inbound.new/docs/api-reference/emails/reply-to-an-email https://inbound.new/openapi.json post /api/e2/emails/{id}/reply Reply to an email or thread. Accepts either an email ID or thread ID (replies to latest message in thread). Supports reply all functionality. # Resume paused scheduled email Source: https://inbound.new/docs/api-reference/emails/resume-paused-scheduled-email https://inbound.new/openapi.json post /api/e2/emails/{id}/resume Resume a paused scheduled email by ID. The email is re-queued for its original send time, or sent shortly if that time has already passed. # Retry email delivery Source: https://inbound.new/docs/api-reference/emails/retry-email-delivery https://inbound.new/openapi.json post /api/e2/emails/{id}/retry Retry delivery of a received email. Can retry to a specific endpoint, retry a specific failed delivery, or retry to all configured endpoints. # Send an email Source: https://inbound.new/docs/api-reference/emails/send-an-email https://inbound.new/openapi.json post /api/e2/emails Send an email immediately or schedule it for later using the scheduled_at parameter. Supports HTML/text content, attachments, and custom headers. # Update email Source: https://inbound.new/docs/api-reference/emails/update-email https://inbound.new/openapi.json patch /api/e2/emails/{id} Update metadata for a received email. Supports marking emails as read/unread and archived/unarchived. # Create new endpoint Source: https://inbound.new/docs/api-reference/endpoints/create-new-endpoint https://inbound.new/openapi.json post /api/e2/endpoints Create a new endpoint (webhook, email, or email_group) for the authenticated user # Delete endpoint Source: https://inbound.new/docs/api-reference/endpoints/delete-endpoint https://inbound.new/openapi.json delete /api/e2/endpoints/{id} Delete an endpoint and clean up associated resources (email addresses become store-only, domains lose catch-all config, group entries and delivery history are deleted) # Get endpoint details Source: https://inbound.new/docs/api-reference/endpoints/get-endpoint-details https://inbound.new/openapi.json get /api/e2/endpoints/{id} Get detailed information about a specific endpoint including delivery stats, recent deliveries, associated emails, and catch-all domains # List all endpoints Source: https://inbound.new/docs/api-reference/endpoints/list-all-endpoints https://inbound.new/openapi.json get /api/e2/endpoints Get paginated list of endpoints for authenticated user with optional filtering by type, active status, sort order, and search by name # Test endpoint Source: https://inbound.new/docs/api-reference/endpoints/test-endpoint https://inbound.new/openapi.json post /api/e2/endpoints/{id}/test Test an endpoint by sending a test payload. For webhooks, supports inbound, discord, and slack formats. For email endpoints, simulates the forwarding process. # Update endpoint Source: https://inbound.new/docs/api-reference/endpoints/update-endpoint https://inbound.new/openapi.json put /api/e2/endpoints/{id} Update an existing endpoint's name, description, active status, config, or webhook format # Error Codes Source: https://inbound.new/docs/api-reference/errors Comprehensive list of API error codes and their meanings # Error Codes The Inbound API returns standard HTTP status codes to indicate success or failure. Error responses include a JSON object with an `error` field describing the issue. ## HTTP Status Codes | Status Code | Meaning | Description | | - | - | - | | `200` | Success | Request completed successfully | | `400` | Bad Request | Invalid request parameters or missing required fields | | `401` | Unauthorized | Invalid or missing API key | | `404` | Not Found | Requested resource does not exist | | `429` | Too Many Requests | Rate limit exceeded (100 requests/second per account across E2 endpoints) | | `500` | Internal Server Error | Server error occurred | ## Common Error Types ### Authentication Errors (401) * **`Unauthorized`** - Missing or invalid API key * **`Invalid API key`** - API key format is incorrect or expired ### Validation Errors (400) #### Missing Required Fields * **`Missing required fields: from, to, and subject are required`** - Email sending * **`Missing required fields: apiKey and to are required`** - Demo requests * **`Missing required fields`** - General validation failure #### Invalid Formats * **`Invalid email format`** - Email address doesn't match expected pattern * **`Invalid email format: [email]`** - Specific email address validation * **`Invalid webhook URL format`** - Webhook URL validation failure * **`Invalid scheduled_at`** - Date/time format is incorrect * **`Invalid schedule time`** - Scheduled time is in the past or too far in future #### Configuration Errors * **`Invalid endpoint type`** - Unsupported endpoint type specified * **`Invalid configuration`** - Endpoint configuration validation failed * **`Webhook URL is required`** - Missing webhook URL for webhook endpoints * **`Forward-to email address is required`** - Missing email for forwarding endpoints * **`Invalid forward-to email address format`** - Invalid forwarding email format #### Parameter Errors * **`Invalid limit parameter`** - List limit must be 1-100 * **`Invalid offset parameter`** - Offset must be non-negative * **`Invalid email ID provided`** - Email ID format is incorrect * **`Invalid webhook format`** - Unsupported webhook payload format ### Resource Errors (404) * **`Email not found`** - Email doesn't exist or user lacks access * **`Endpoint not found`** - Endpoint doesn't exist or access denied * **`Scheduled email not found`** - Scheduled email not found or unauthorized * **`Original email not found`** - Cannot reply to non-existent email ### Rate Limiting (429) * **`Too Many Requests`** - Too many requests (limit: 100 per second per account across E2 endpoints) * **`Rate limit exceeded. Maximum 100 requests per second. Retry after 1 seconds.`** - Example rate limit message; the retry delay varies ### Server Errors (500) * **`Failed to send email`** - Email sending operation failed * **`Failed to schedule email`** - Email scheduling failed * **`Failed to cancel scheduled email`** - Cancellation operation failed * **`Failed to test endpoint`** - Endpoint testing failed * **`Email sending limit reached. Please upgrade your plan to send more emails.`** - Account limits ## Error Response Format All error responses follow this structure: ```json theme={null} { "error": "Error description", "message": "Additional context (optional)", "details": "Technical details (optional)" } ``` ## Rate Limit Headers When approaching or exceeding rate limits, responses include headers: | Header | Description | | - | - | | `ratelimit-limit` | Maximum requests per window | | `ratelimit-remaining` | Requests remaining in current window | | `ratelimit-reset` | Seconds until limit resets | | `retry-after` | Seconds to wait before retry (429 only) | # Check if rule matches email Source: https://inbound.new/docs/api-reference/guard/check-if-rule-matches-email https://inbound.new/openapi.json post /api/e2/guard/{id}/check Test a guard rule against a specific email to see if it would match. # Create guard rule Source: https://inbound.new/docs/api-reference/guard/create-guard-rule https://inbound.new/openapi.json post /api/e2/guard Create an active email filtering rule for the authenticated account. For an `explicit` rule, configure one or more of `subject`, `from`, `to`, `hasAttachment`, and `hasWords`. Different configured criteria are combined with AND. Within a criterion, `OR` matches any value and `AND` requires every value. Address criteria support exact, case-insensitive addresses and whole-domain patterns such as `*@example.com`. The `to` criterion matches the actual delivered recipient, which makes it suitable for limiting a rule to one inbox. Subject and body criteria use case-insensitive substring matching. Rules are evaluated from highest priority to lowest, and the first matching rule wins. A matching rule can `allow`, `block`, or `route` the email to an active endpoint owned by the account. If action is omitted, it defaults to `allow`. For an `ai_prompt` rule, provide a non-empty natural-language `prompt` describing when the rule should match. # Delete guard rule Source: https://inbound.new/docs/api-reference/guard/delete-guard-rule https://inbound.new/openapi.json delete /api/e2/guard/{id} Delete a guard rule by ID. # Generate rule from natural language Source: https://inbound.new/docs/api-reference/guard/generate-rule-from-natural-language https://inbound.new/openapi.json post /api/e2/guard/generate Use AI to convert a natural language description into an explicit guard rule configuration. # Get guard rule Source: https://inbound.new/docs/api-reference/guard/get-guard-rule https://inbound.new/openapi.json get /api/e2/guard/{id} Get a specific guard rule by ID. # List guard rules Source: https://inbound.new/docs/api-reference/guard/list-guard-rules https://inbound.new/openapi.json get /api/e2/guard Get all guard rules for the authenticated user with optional filtering and pagination. # Update guard rule Source: https://inbound.new/docs/api-reference/guard/update-guard-rule https://inbound.new/openapi.json put /api/e2/guard/{id} Update an existing guard rule. # Get thread by ID Source: https://inbound.new/docs/api-reference/inbox/get-thread-by-id https://inbound.new/openapi.json get /api/e2/mail/threads/{id} Retrieve a complete email thread (conversation) with all messages. **What You Get:** - Thread metadata (subject, participants, timestamps) - All messages in the thread (both inbound and outbound) - Messages sorted chronologically by thread position **Message Types:** - `inbound` - Emails you received - `outbound` - Emails you sent (includes delivery status) # List inbox threads Source: https://inbound.new/docs/api-reference/inbox/list-inbox-threads https://inbound.new/openapi.json get /api/e2/mail/threads List email threads (conversations) for your inbox with cursor-based pagination. This is the primary endpoint for building an inbox UI. **What is a Thread?** A thread groups related emails together based on the In-Reply-To and References headers, similar to how Gmail groups conversations. Each thread contains both inbound (received) and outbound (sent) messages. **Use with /mail/threads/:id:** Use this endpoint to list threads, then use `GET /mail/threads/:id` to fetch all messages in a specific thread. # Introduction Source: https://inbound.new/docs/api-reference/introduction Simple email processing API for receiving and managing emails programmatically Welcome to Inbound! Get started with sending emails, receiving emails, and building complete email workflows in minutes. ## Quick Start Sign up at [inbound.new](https://inbound.new) and generate an API key from your dashboard. Install our SDK for the best developer experience: ```bash theme={null} npm install inboundemail ``` Start sending emails immediately: ```typescript SDK (typescript) theme={null} import { Inbound } from 'inboundemail' const inbound = new Inbound(process.env.INBOUND_API_KEY!) const { data, error } = await inbound.emails.send({ from: 'Inbound User ', to: 'youremail@inbound.new', subject: 'Welcome!', html: '

Thanks for signing up!

', tags: [{ name: 'campaign', value: 'welcome' }] }) console.log(`Email sent: ${data?.id}`) ```
## Authentication All API requests to Inbound v2 require authentication using an API key. This guide explains how to obtain and use your API key securely. ### Base URL All authenticated requests are made to: ```bash theme={null} https://inbound.new/api/e2 ``` ### Getting Your API Key Log in to your Inbound dashboard and navigate to the Settings page. Access your API keys from the dashboard Click "Create API Key" and give it a descriptive name for easy identification. Copy your API key immediately and store it securely. You won't be able to see it again. API keys are sensitive credentials. Never expose them in client-side code or commit them to version control. ### Using Your API Key Include your API key in the `Authorization` header of all API requests: ```bash theme={null} Authorization: Bearer your_api_key_here ``` ### Security Best Practices * **Environment Variables**: Always store API keys in environment variables * **Key Rotation**: Regularly rotate your API keys for enhanced security * **Environment Separation**: Use separate API keys for different environments * **Monitoring**: Monitor your API key usage in the dashboard Learn about [rate limits](/docs/api-reference/rate-limits) to optimize your API usage and avoid hitting limits. ## How Inbound Works Inbound enables you to be able to send & receive emails programmatically. #### Sending Emails You can send emails using the SDK or the API. Our SDK is *very* similar to the [Resend SDK](https://resend.com/docs/api-reference). So if you are migrating from Resend, you shouldn't have any trouble. Example: ```typescript Node.js theme={null} const { data, error } = await inbound.emails.send({ from: 'Inbound User ', to: 'youremail@inbound.new', subject: 'Welcome!', html: '

Thanks for signing up!

', tags: [{ name: 'campaign', value: 'welcome' }] }) console.log(`Email sent: ${data?.id}`) ``` #### Receiving Emails In inbound you can receive emails using endpoints. You can attach endpoints to individual email addresses or you can setup catch-all for a domain, where all any incoming email that doesn't match any of the specific email addresses will be routed to the catch-all endpoint. Example: ```typescript theme={null} switch (email) { case 'support@yourdomain.com': endpoint1 // Specific endpoint for support case 'sales@yourdomain.com': endpoint2 // Specific endpoint for sales default: // catch-all endpoint3 // Fallback endpoint for all other emails } ``` ## API Features Send transactional emails with Resend-compatible API Process incoming emails via webhooks with TypeScript types Send replies to received emails with full threading support Add and verify domains for email receiving ## Error Handling API errors are returned with appropriate HTTP status codes: ```json theme={null} { "error": "Invalid or missing API key" } ``` Common status codes: * `200` - Success * `400` - Bad Request (invalid parameters) * `401` - Unauthorized (invalid API key) * `404` - Not Found (resource not found) * `429` - Too Many Requests (rate limit exceeded) * `500` - Internal Server Error ## Next Steps Ready to start building with Inbound? Here are the essential resources to get you started: Complete guide to API authentication and security Understand API limits and optimization strategies Start sending emails with the API Install and configure the TypeScript SDK Reference for all API error codes and responses # Authenticate a mailbox Source: https://inbound.new/docs/api-reference/mailboxes/authenticate-a-mailbox https://inbound.new/openapi.json post /api/e2/mailboxes/authenticate # Authenticate for SMTP Source: https://inbound.new/docs/api-reference/mailboxes/authenticate-for-smtp https://inbound.new/openapi.json post /api/e2/mailboxes/authenticate-smtp # Create a managed mailbox Source: https://inbound.new/docs/api-reference/mailboxes/create-a-managed-mailbox https://inbound.new/openapi.json post /api/e2/mailboxes # Delete a managed mailbox Source: https://inbound.new/docs/api-reference/mailboxes/delete-a-managed-mailbox https://inbound.new/openapi.json delete /api/e2/mailboxes/{id} # Download a mailbox attachment Source: https://inbound.new/docs/api-reference/mailboxes/download-a-mailbox-attachment https://inbound.new/openapi.json get /api/e2/mailboxes/{id}/messages/{messageId}/attachments/{index} Download an attachment of a received message by its position (0-based). # Get a mailbox Source: https://inbound.new/docs/api-reference/mailboxes/get-a-mailbox https://inbound.new/openapi.json get /api/e2/mailboxes/{id} Get a mailbox's identity, scopes and unread count. Authenticate with the account API key, or with the mailbox password and the ID `me`. # Get a mailbox message Source: https://inbound.new/docs/api-reference/mailboxes/get-a-mailbox-message https://inbound.new/openapi.json get /api/e2/mailboxes/{id}/messages/{messageId} Get a received or sent message with its full text and HTML body. # Get a mailbox thread Source: https://inbound.new/docs/api-reference/mailboxes/get-a-mailbox-thread https://inbound.new/openapi.json get /api/e2/mailboxes/{id}/threads/{threadId} Get every message of a conversation that is visible to the mailbox, oldest first. # List mailbox messages Source: https://inbound.new/docs/api-reference/mailboxes/list-mailbox-messages https://inbound.new/openapi.json get /api/e2/mailboxes/{id}/messages List messages in a mailbox folder, newest first. Only mail within the mailbox's scopes is visible. # List managed mailboxes Source: https://inbound.new/docs/api-reference/mailboxes/list-managed-mailboxes https://inbound.new/openapi.json get /api/e2/mailboxes # Mark a mailbox message read or archived Source: https://inbound.new/docs/api-reference/mailboxes/mark-a-mailbox-message-read-or-archived https://inbound.new/openapi.json patch /api/e2/mailboxes/{id}/messages/{messageId} Requires a read_write mailbox. Changes are shared with the dashboard. # Rotate a mailbox password Source: https://inbound.new/docs/api-reference/mailboxes/rotate-a-mailbox-password https://inbound.new/openapi.json post /api/e2/mailboxes/{id}/rotate-password # Update a managed mailbox Source: https://inbound.new/docs/api-reference/mailboxes/update-a-managed-mailbox https://inbound.new/openapi.json put /api/e2/mailboxes/{id} # Check for onboarding demo reply Source: https://inbound.new/docs/api-reference/onboarding/check-for-onboarding-demo-reply https://inbound.new/openapi.json get /api/e2/onboarding/check-reply Check if the user has replied to their onboarding demo email. Used during onboarding to detect reply. # Send onboarding demo email Source: https://inbound.new/docs/api-reference/onboarding/send-onboarding-demo-email https://inbound.new/openapi.json post /api/e2/onboarding/demo Send a demo email during onboarding to verify email setup. User must reply to complete onboarding. # Rate Limits Source: https://inbound.new/docs/api-reference/rate-limits Understand rate limits and how to increase them for the Inbound API The response headers describe your current rate limit following every request in conformance with the [sixth IETF standard draft](https://datatracker.ietf.org/doc/html/draft-ietf-httpapi-ratelimit-headers-06): | Header name | Description | | - | - | | `ratelimit-limit` | Maximum number of requests allowed within a window. | | `ratelimit-remaining` | How many requests you have left within the current window. | | `ratelimit-reset` | How many seconds until the limits are reset. | | `retry-after` | How many seconds you should wait before making a follow-up request. | ## Default Rate Limits The default maximum rate limit is **100 requests per second per account**, shared across authenticated E2 API endpoints. This single budget covers: * **Email sending endpoints**: 100 requests per second * **Email management endpoints**: 100 requests per second * **Domain/configuration endpoints**: 100 requests per second * **All other authenticated E2 endpoints**: 100 requests per second This number can be increased for trusted senders upon request. ## Rate Limit Response After exceeding the rate limit, you'll receive a `429 Too Many Requests` response error code: ```json theme={null} { "error": "Too Many Requests", "message": "Rate limit exceeded. Maximum 100 requests per second. Retry after 1 seconds.", "statusCode": 429 } ``` ## Increasing Rate Limits If you have specific requirements that exceed the default limits, you can request a rate increase: Reach out to our support team at [support@inbound.new](mailto:support@inbound.new) with your requirements. Include details about your use case, expected volume, and business requirements. Our team will review your request and work with you to find an appropriate solution. ## Rate Limit by Endpoint Rate limits are applied per account. If you're using multiple accounts, each account has its own rate limit bucket. **Important**: The SDK will return rate limit errors directly - you must handle them in your application code. ## Next Steps Learn how to authenticate your API requests Start sending emails with proper rate limiting See all API error codes including rate limit responses # Security Source: https://inbound.new/docs/api-reference/security How to securely verify webhook requests from Inbound ## Overview When receiving webhooks from Inbound, it's important to verify that requests are legitimate. Inbound includes a verification token in the `X-Webhook-Verification-Token` header for each webhook request. ## Webhook Verification Every webhook request includes security headers that allow you to verify the request authenticity: | Header | Description | | - | - | | `X-Webhook-Verification-Token` | Unique verification token for your endpoint | | `X-Endpoint-ID` | ID of the endpoint that triggered this webhook | | `X-Webhook-Event` | Event type (e.g., `email.received`) | | `X-Webhook-Timestamp` | ISO 8601 timestamp of when the webhook was sent | ## Using the SDK Verification Helper The SDK provides a `verifyWebhook` helper function that automatically fetches your endpoint configuration and compares the verification token: ```typescript app/api/webhook/route.ts theme={null} import { Inbound, verifyWebhookFromHeaders } from 'inboundemail' const inbound = new Inbound(process.env.INBOUND_API_KEY!) export async function POST(request: Request) { // Verify webhook authenticity const isValid = await verifyWebhookFromHeaders(request.headers, inbound) if (!isValid) { return new Response('Unauthorized', { status: 401 }) } // Process webhook payload const payload = await request.json() const email = payload.email // Your webhook handling logic here console.log('Received email:', email.subject) return new Response('OK', { status: 200 }) } ``` ```typescript server.ts theme={null} import express from 'express' import { Inbound, verifyWebhookFromHeaders } from 'inboundemail' const app = express() const inbound = new Inbound(process.env.INBOUND_API_KEY!) app.post('/webhook', async (req, res) => { // Verify webhook authenticity const isValid = await verifyWebhookFromHeaders(req.headers, inbound) if (!isValid) { return res.status(401).json({ error: 'Unauthorized' }) } // Process webhook payload const email = req.body.email // Your webhook handling logic here console.log('Received email:', email.subject) res.status(200).json({ success: true }) }) ``` ```typescript theme={null} import { Inbound, verifyWebhook } from 'inboundemail' const inbound = new Inbound(process.env.INBOUND_API_KEY!) export async function POST(request: Request) { const endpointId = request.headers.get('X-Endpoint-ID') const verificationToken = request.headers.get('X-Webhook-Verification-Token') // Manually verify with extracted headers const isValid = await verifyWebhook(endpointId, verificationToken, inbound) if (!isValid) { return new Response('Unauthorized', { status: 401 }) } // Process webhook... } ``` ## How Verification Works 1. **Verification Token**: Each endpoint has a unique verification token stored in its configuration 2. **Header Transmission**: Inbound sends this token in the `X-Webhook-Verification-Token` header with every webhook request 3. **SDK Verification**: The `verifyWebhook` function fetches your endpoint config via the API and compares tokens 4. **Security**: If tokens don't match, the request should be rejected Always verify webhook requests in production to prevent unauthorized access to your endpoints. The verification token is automatically generated when you create an endpoint. You can view it in your endpoint configuration via the API. ## Manual Verification (Without SDK) If you're not using the SDK, you can manually verify webhooks by fetching the endpoint configuration: ```typescript theme={null} async function verifyWebhookManually( endpointId: string, verificationToken: string, apiKey: string ): Promise { // Fetch endpoint from API const response = await fetch(`https://inbound.new/api/e2/endpoints/${endpointId}`, { headers: { 'Authorization': `Bearer ${apiKey}` } }) if (!response.ok) { return false } const endpoint = await response.json() const config = typeof endpoint.config === 'string' ? JSON.parse(endpoint.config) : endpoint.config // Compare tokens return config.verificationToken === verificationToken } ``` ## Getting Your Verification Token You can retrieve the verification token for your endpoint via the API: ```typescript theme={null} import { Inbound } from 'inboundemail' const inbound = new Inbound(process.env.INBOUND_API_KEY!) // Get endpoint configuration const { data: endpoint } = await inbound.endpoint.get('your-endpoint-id') if (endpoint) { const config = typeof endpoint.config === 'string' ? JSON.parse(endpoint.config) : endpoint.config console.log('Verification token:', config.verificationToken) } ``` ## Best Practices ### Always Verify in Production Never skip webhook verification in production environments. Unverified webhooks can expose your application to security risks. ### Handle Verification Failures Always return appropriate error responses when verification fails: ```typescript theme={null} if (!isValid) { // Log the failed attempt for monitoring console.warn('Webhook verification failed', { endpointId: request.headers.get('X-Endpoint-ID'), timestamp: new Date().toISOString() }) // Return 401 Unauthorized return new Response('Unauthorized', { status: 401 }) } ``` ### Store API Keys Securely Never hardcode API keys or commit them to version control: * Use environment variables * Use secrets management services in production * Rotate API keys regularly ## Next Steps Learn about webhook payload structure and types View endpoint API documentation Understand authentication and verification errors Learn more about webhook handling in the SDK # Webhook Structure Source: https://inbound.new/docs/api-reference/webhook Webhook payload structure ## Overview When emails arrive at your configured addresses, Inbound sends a webhook to your endpoint with the complete email data. ```typescript theme={null} import type { InboundWebhookPayload } from 'inboundemail' ``` ## Webhook Payload Structure We have fully a complete typed webhook payload for you to use in your endpoints. ```typescript theme={null} const payload: InboundWebhookPayload = { event: 'email.received', timestamp: '2024-01-15T10:30:00Z', email: { id: 'inbnd_abc123def456ghi', messageId: '', from: { text: 'John Doe ', addresses: [{ name: 'John Doe', address: 'john@example.com' }] }, to: { text: 'support@yourdomain.com', addresses: [{ name: null, address: 'support@yourdomain.com' }] }, recipient: 'support@yourdomain.com', subject: 'Help with my order', receivedAt: '2024-01-15T10:30:00Z', parsedData: { messageId: '', date: new Date('2024-01-15T10:30:00Z'), subject: 'Help with my order', from: { text: 'John Doe ', addresses: [{ name: 'John Doe', address: 'john@example.com' }] }, to: { text: 'support@yourdomain.com', addresses: [{ name: null, address: 'support@yourdomain.com' }] }, cc: null, bcc: null, replyTo: null, inReplyTo: undefined, references: undefined, textBody: 'Hello, I need help with my recent order...', htmlBody: '

Hello, I need help with my recent order...

', raw: 'From: john@example.com\r\nTo: support@yourdomain.com\r\n...', attachments: [ { filename: 'order-receipt.pdf', contentType: 'application/pdf', size: 45678, contentId: '', contentDisposition: 'attachment', downloadUrl: 'https://inbound.new/api/e2/attachments/inbnd_abc123def456ghi/parts/0' } ], headers: {}, priority: undefined }, cleanedContent: { html: '

Hello, I need help with my recent order...

', text: 'Hello, I need help with my recent order...', hasHtml: true, hasText: true, attachments: [ { filename: 'order-receipt.pdf', contentType: 'application/pdf', size: 45678, contentId: '', contentDisposition: 'attachment', downloadUrl: 'https://inbound.new/api/e2/attachments/inbnd_abc123def456ghi/parts/0' } ], headers: {} } }, endpoint: { id: 'endp_xyz789', name: 'Support Webhook', type: 'webhook' } } ``` ## Webhook Security Always verify webhook requests before processing them to prevent unauthorized access to your endpoints. ### Verification Headers Every webhook request includes security headers that you should verify: | Header | Description | | - | - | | `X-Webhook-Verification-Token` | Unique verification token for your endpoint | | `X-Endpoint-ID` | ID of the endpoint that triggered this webhook | | `X-Webhook-Event` | Event type (e.g., `email.received`) | | `X-Webhook-Timestamp` | ISO 8601 timestamp of when the webhook was sent | ### Verifying Webhooks with the SDK The SDK provides a simple helper function to verify webhook requests: ```typescript theme={null} import { Inbound, verifyWebhookFromHeaders } from 'inboundemail' const inbound = new Inbound(process.env.INBOUND_API_KEY!) export async function POST(request: Request) { // Verify webhook authenticity before processing const isValid = await verifyWebhookFromHeaders(request.headers, inbound) if (!isValid) { return new Response('Unauthorized', { status: 401 }) } // Process the verified webhook payload const payload: InboundWebhookPayload = await request.json() const { email } = payload // Your webhook handling logic here console.log('Received verified email:', email.subject) return new Response('OK', { status: 200 }) } ``` ### Complete Example with Verification Here's a complete example that combines webhook verification with payload processing: ```typescript theme={null} import { Inbound, verifyWebhookFromHeaders, type InboundWebhookPayload } from 'inboundemail' const inbound = new Inbound(process.env.INBOUND_API_KEY!) export async function POST(request: Request) { // Step 1: Verify webhook authenticity const isValid = await verifyWebhookFromHeaders(request.headers, inbound) if (!isValid) { console.warn('Webhook verification failed', { endpointId: request.headers.get('X-Endpoint-ID'), timestamp: new Date().toISOString() }) return new Response('Unauthorized', { status: 401 }) } // Step 2: Parse verified payload const payload: InboundWebhookPayload = await request.json() const { email, endpoint } = payload // Step 3: Process the email console.log(`Received email from ${email.from.addresses[0].address}`) console.log(`Subject: ${email.subject}`) console.log(`Endpoint: ${endpoint.name}`) // Step 4: Handle attachments if present if (email.parsedData.attachments?.length > 0) { for (const attachment of email.parsedData.attachments) { // Download attachment using the provided downloadUrl const response = await fetch(attachment.downloadUrl, { headers: { 'Authorization': `Bearer ${process.env.INBOUND_API_KEY}` } }) if (response.ok) { const fileBuffer = await response.arrayBuffer() // Process attachment... } } } return new Response('OK', { status: 200 }) } ``` The `verifyWebhookFromHeaders` function automatically fetches your endpoint configuration from the API and compares the verification token. For more details, see the [Security guide](/docs/api-reference/security). ## Attachment Downloads Attachments include a `downloadUrl` field that provides direct access to download the file using your API key for authentication. ### Downloading Attachments Each attachment in the webhook payload includes a `downloadUrl` that you can use to download the file: ```typescript theme={null} // Process attachments from webhook export async function POST(request: NextRequest) { const payload: InboundWebhookPayload = await request.json() const { email } = payload // Check for attachments if (email.parsedData.attachments.length > 0) { for (const attachment of email.parsedData.attachments) { console.log(`Attachment: ${attachment.filename}`) console.log(`Download URL: ${attachment.downloadUrl}`) // Download the attachment const response = await fetch(attachment.downloadUrl, { headers: { 'Authorization': `Bearer ${process.env.INBOUND_API_KEY}` } }) if (response.ok) { const fileBuffer = await response.arrayBuffer() // Process the file... } } } return NextResponse.json({ success: true }) } ``` The download URL follows the format: `https://inbound.new/api/e2/attachments/{emailId}/parts/{index}`, where `index` is the attachment's position in the `attachments` array. This works for attachments without a filename and for several attachments with the same name. Older URLs in the `{emailId}/{filename}` form keep working and return the first attachment with that name. Authentication via API key in the Authorization header is required to download attachments. # Email received Source: https://inbound.new/docs/api-reference/webhooks/email-received https://inbound.new/openapi.json webhook emailReceived When emails arrive at your configured addresses, Inbound sends a webhook to your endpoint with the complete email data. ## Webhook Payload Structure We provide a fully typed webhook payload for you to use in your endpoints: ```typescript import type { InboundWebhookPayload } from 'inboundemail' ``` ### Example Payload ```typescript const payload: InboundWebhookPayload = { event: 'email.received', timestamp: '2024-01-15T10:30:00Z', email: { id: 'inbnd_abc123def456ghi', messageId: '', from: { text: 'John Doe ', addresses: [{ name: 'John Doe', address: 'john@sender.com' }] }, to: { text: 'support@yourdomain.com', addresses: [{ name: null, address: 'support@yourdomain.com' }] }, recipient: 'support@yourdomain.com', subject: 'Help with my order', receivedAt: '2024-01-15T10:30:00Z', parsedData: { messageId: '', date: new Date('2024-01-15T10:30:00Z'), subject: 'Help with my order', from: { /* ... */ }, to: { /* ... */ }, cc: null, bcc: null, replyTo: null, textBody: 'Hello, I need help with my recent order...', htmlBody: '

Hello, I need help with my recent order...

', attachments: [ { filename: 'order-receipt.pdf', contentType: 'application/pdf', size: 45678, contentId: '', contentDisposition: 'attachment', downloadUrl: 'https://inbound.new/api/e2/attachments/inbnd_abc123/parts/0' } ] } }, endpoint: { id: 'endp_xyz789', name: 'Support Webhook', type: 'webhook' } } ``` ## Webhook Security Always verify webhook requests before processing them to prevent unauthorized access. ### Verification Headers Every webhook request includes security headers: | Header | Description | |--------|-------------| | `X-Webhook-Verification-Token` | Unique verification token for your endpoint | | `X-Endpoint-ID` | ID of the endpoint that triggered this webhook | | `X-Webhook-Event` | Event type (e.g., `email.received`) | | `X-Webhook-Timestamp` | ISO 8601 timestamp of when the webhook was sent | ### Verifying with the SDK ```typescript import { Inbound, verifyWebhookFromHeaders } from 'inboundemail' const inbound = new Inbound(process.env.INBOUND_API_KEY!) export async function POST(request: Request) { // Verify webhook authenticity before processing const isValid = await verifyWebhookFromHeaders(request.headers, inbound) if (!isValid) { return new Response('Unauthorized', { status: 401 }) } // Process the verified webhook payload const payload: InboundWebhookPayload = await request.json() const { email } = payload console.log('Received verified email:', email.subject) return new Response('OK', { status: 200 }) } ``` ## Downloading Attachments Each attachment includes a `downloadUrl` for direct file access: ```typescript // Download attachments from webhook payload for (const attachment of email.parsedData.attachments) { const response = await fetch(attachment.downloadUrl, { headers: { 'Authorization': `Bearer ${process.env.INBOUND_API_KEY}` } }) if (response.ok) { const fileBuffer = await response.arrayBuffer() // Process the file... } } ``` > **Note:** Authentication via API key in the Authorization header is required to download attachments. # Better Auth Source: https://inbound.new/docs/integrations/better-auth Automatically send transactional emails for authentication events with Better Auth This plugin is in beta and may not be stable. The Inbound Better Auth plugin automatically sends transactional emails for important authentication events like password changes, new device sign-ins, and account creation. ## Installation The plugin is included in the `inboundemail` package in version `0.20.0` and above. You'll also need `better-auth` installed: ```bash theme={null} npm install inboundemail better-auth ``` ## Quick Start ### Server Setup ```typescript theme={null} import { betterAuth } from 'better-auth'; import { inboundEmailPlugin } from 'inboundemail/better-auth'; export const auth = betterAuth({ // ... your Better Auth config plugins: [ inboundEmailPlugin({ client: { apiKey: process.env.INBOUND_API_KEY! }, from: 'security@yourdomain.com', }), ], }); ``` ### Client Setup (Optional) For proper type inference on the client side: ```typescript theme={null} import { createAuthClient } from 'better-auth/react'; import { inboundEmailClientPlugin } from 'inboundemail/better-auth/client'; export const authClient = createAuthClient({ plugins: [inboundEmailClientPlugin()], }); ``` ## Supported Events The plugin automatically sends emails for these authentication events: | Event | Description | Default | | - | - | - | | `password-changed` | User changed their password | Enabled | | `email-changed` | User changed their email address | Enabled | | `new-device-sign-in` | Sign-in from a new device/browser | Enabled | | `account-created` | New account registration | Enabled | | `password-reset-requested` | Password reset was requested | Disabled | | `two-factor-enabled` | 2FA was enabled on account | Enabled | | `two-factor-disabled` | 2FA was disabled on account | Enabled | ## Configuration Options ```typescript theme={null} inboundEmailPlugin({ // Required: Inbound client or API key client: { apiKey: process.env.INBOUND_API_KEY! }, // Or pass an existing client instance: // client: new Inbound({ apiKey: '...' }), // Required: Default "from" address for all emails from: 'security@yourdomain.com', // Optional: Include device info in new sign-in emails (default: true) includeDeviceInfo: true, // Optional: Configure specific events events: { 'password-changed': { enabled: true, from: 'alerts@yourdomain.com', // Override from address template: customTemplate, // Custom template function }, 'password-reset-requested': { enabled: true, // Enable this disabled-by-default event }, }, // Optional: Lifecycle hooks onBeforeSend: async (event, context, email) => { // Return false to cancel sending return true; }, onAfterSend: async (event, context, result) => { console.log(`Sent ${event} email:`, result.id); }, onError: async (event, context, error) => { console.error(`Failed to send ${event} email:`, error); }, }); ``` ## Custom Templates You can customize the email content for any event type: ```typescript theme={null} inboundEmailPlugin({ client: { apiKey: process.env.INBOUND_API_KEY! }, from: 'security@yourdomain.com', events: { 'password-changed': { template: (ctx) => ({ subject: `🔐 Password Changed - ${ctx.timestamp}`, html: `

Password Updated

Hi ${ctx.name || 'there'},

Your password was changed on ${new Date(ctx.timestamp).toLocaleString()}.

${ctx.otherSessionsRevoked ? '

All other sessions have been signed out.

' : ''}

If this wasn't you, please contact support immediately.

`, text: `Hi ${ctx.name || 'there'}, your password was changed...`, }), }, }, }); ``` ### Template Context by Event Type Each event type receives different context data: #### `password-changed` ```typescript theme={null} { email: string; name?: string | null; userId: string; timestamp: string; // ISO 8601 otherSessionsRevoked: boolean; } ``` #### `email-changed` ```typescript theme={null} { email: string; // Old email (notification sent here) name?: string | null; userId: string; timestamp: string; oldEmail: string; newEmail: string; } ``` #### `new-device-sign-in` ```typescript theme={null} { email: string; name?: string | null; userId: string; timestamp: string; ipAddress?: string | null; userAgent?: string | null; device?: { browser?: string; // e.g., "Chrome", "Safari" os?: string; // e.g., "macOS", "Windows" type?: 'desktop' | 'mobile' | 'tablet' | 'unknown'; }; location?: { city?: string; country?: string; }; } ``` #### `account-created` ```typescript theme={null} { email: string; name?: string | null; userId: string; timestamp: string; method: 'email' | 'social' | 'magic-link' | 'passkey'; provider?: string; // e.g., "google", "github" for social } ``` #### `password-reset-requested` ```typescript theme={null} { email: string; name?: string | null; userId: string; timestamp: string; token?: string; resetUrl?: string; } ``` #### `two-factor-enabled` / `two-factor-disabled` ```typescript theme={null} { email: string; name?: string | null; userId: string; timestamp: string; method?: string; // e.g., "totp" } ``` ### Organization Events These events are triggered when using Better Auth's organization plugin. #### `organization-invitation-sent` Sent to the invitee when they are invited to join an organization. ```typescript theme={null} { email: string; // Invitee's email inviterName?: string | null; inviterEmail?: string | null; organizationName: string; organizationId: string; role: string; inviteLink?: string; timestamp: string; expiresAt?: string; } ``` #### `organization-invitation-accepted` Sent when a user accepts an organization invitation. ```typescript theme={null} { email: string; name?: string | null; userId: string; organizationName: string; organizationId: string; role: string; timestamp: string; notifyEmail: string; // Org admin to notify } ``` #### `organization-member-removed` Sent to a user when they are removed from an organization. ```typescript theme={null} { email: string; name?: string | null; userId: string; organizationName: string; organizationId: string; removedByName?: string | null; reason?: string; timestamp: string; } ``` #### `organization-role-changed` Sent when a member's role in an organization is updated. ```typescript theme={null} { email: string; name?: string | null; userId: string; organizationName: string; organizationId: string; previousRole: string; newRole: string; changedByName?: string | null; timestamp: string; } ``` ### Social Account Events These events are triggered when users link or unlink social/OAuth accounts. #### `social-account-linked` Sent when a user connects a social account (Google, GitHub, etc.). ```typescript theme={null} { email: string; name?: string | null; userId: string; providerName: string; // e.g., "Google", "GitHub" providerId: string; // e.g., "google", "github" providerAccountId?: string; timestamp: string; } ``` #### `social-account-unlinked` Sent when a user disconnects a social account. ```typescript theme={null} { email: string; name?: string | null; userId: string; providerName: string; providerId: string; timestamp: string; ``` ## Pre-built React Email Templates The SDK includes beautiful, responsive React Email templates for all auth events. Install `@react-email/components` to use them: ```bash theme={null} npm install @react-email/components ``` Then import and use the pre-built templates: ```tsx theme={null} import { inboundEmailPlugin } from 'inboundemail/better-auth'; import { BetterAuthPasswordChanged, BetterAuthEmailChanged, BetterAuthNewDeviceSignin, BetterAuthMagicLink, BetterAuthPasswordReset, BetterAuthVerifyEmail, } from 'inboundemail/better-auth/react-email'; inboundEmailPlugin({ client: { apiKey: process.env.INBOUND_API_KEY! }, from: 'security@yourdomain.com', events: { 'password-changed': { template: (ctx) => ({ subject: 'Your password was changed', react: ( ), }), }, 'new-device-sign-in': { template: (ctx) => ({ subject: 'New sign-in detected', react: ( ), }), }, }, }); ``` ### Available Templates #### Authentication Templates | Template | Description | Props | | - | - | - | | `BetterAuthPasswordChanged` | Password change notification | `userEmail`, `timestamp`, `appName`, `supportEmail`, `logoUrl`, `secureAccountLink` | | `BetterAuthEmailChanged` | Email change notification | `oldEmail`, `newEmail`, `appName`, `supportEmail`, `logoUrl`, `revertLink` | | `BetterAuthNewDeviceSignin` | New device login alert | `userEmail`, `deviceInfo`, `appName`, `supportEmail`, `logoUrl`, `secureAccountLink` | | `BetterAuthMagicLink` | Magic link sign-in | `magicLink`, `userEmail`, `appName`, `expirationMinutes`, `logoUrl` | | `BetterAuthPasswordReset` | Password reset email | `resetLink`, `userEmail`, `appName`, `expirationMinutes`, `logoUrl` | | `BetterAuthVerifyEmail` | Email verification OTP | `verificationCode`, `userEmail`, `appName`, `expirationMinutes`, `logoUrl` | #### Organization Templates | Template | Description | Props | | - | - | - | | `BetterAuthOrganizationInvitation` | Organization invitation | `inviterName`, `inviterEmail`, `organizationName`, `role`, `inviteLink`, `expiresAt`, `appName`, `logoUrl` | | `BetterAuthOrganizationMemberJoined` | Member joined notification | `memberName`, `memberEmail`, `organizationName`, `role`, `timestamp`, `appName`, `logoUrl` | | `BetterAuthOrganizationMemberRemoved` | Member removed notification | `userName`, `organizationName`, `removedByName`, `reason`, `timestamp`, `appName`, `supportEmail`, `logoUrl` | | `BetterAuthOrganizationRoleChanged` | Role change notification | `userName`, `organizationName`, `previousRole`, `newRole`, `changedByName`, `timestamp`, `appName`, `supportEmail`, `logoUrl` | #### Social Account Templates | Template | Description | Props | | - | - | - | | `BetterAuthSocialAccountLinked` | Social account connected | `userName`, `userEmail`, `providerName`, `timestamp`, `appName`, `supportEmail`, `logoUrl`, `secureAccountLink` | | `BetterAuthSocialAccountUnlinked` | Social account disconnected | `userName`, `userEmail`, `providerName`, `timestamp`, `appName`, `supportEmail`, `logoUrl`, `secureAccountLink` | ### Customizing the Design System The `betterAuthDesignSystem` export provides a complete design token system you can use for consistent styling: ```typescript theme={null} import { betterAuthDesignSystem } from 'inboundemail/better-auth/react-email'; const ds = betterAuthDesignSystem; // Colors ds.colors.background.outside // '#FFFFFF' - outer background ds.colors.background.inside // '#FFFFFF' - inner background ds.colors.text.primary // '#121212' - headings ds.colors.text.secondary // '#444444' - body text ds.colors.text.tertiary // '#666666' - muted text ds.colors.text.quaternary // '#767676' - footer text ds.colors.border.main // '#E7E5E4' - borders // Typography ds.typography.fontFamily.sans // 'Gesit, Inter, -apple-system, ...' ds.typography.heading // { fontSize: '20px', fontWeight: 600, ... } ds.typography.body // { fontSize: '14px', fontWeight: 400, ... } ds.typography.small // { fontSize: '12px', ... } // Buttons ds.buttons.primary.backgroundColor // '#121212' ds.buttons.primary.color // '#FFFFFF' ds.buttons.secondary.backgroundColor // '#FFFFFF' // Spacing ds.spacing.xs // '4px' ds.spacing.sm // '8px' ds.spacing.md // '12px' ds.spacing.lg // '16px' ds.spacing.xl // '24px' // Card styling ds.card.backgroundColor // '#FFFFFF' ds.card.padding // '20px' ds.card.border // { color: '#E7E5E4', style: 'solid', width: '1px' } ``` You can override these tokens when creating your own templates: ```tsx theme={null} const myDesignSystem = { ...betterAuthDesignSystem, colors: { ...betterAuthDesignSystem.colors, text: { ...betterAuthDesignSystem.colors.text, primary: '#1a1a2e', // Custom dark blue }, }, }; ``` ## Custom React Email Templates Create your own templates using `@react-email/components`: ```tsx theme={null} import { Body, Button, Container, Head, Heading, Html, Preview, Section, Tailwind, Text, } from '@react-email/components'; import { betterAuthDesignSystem } from 'inboundemail/better-auth/react-email'; interface MyCustomEmailProps { userName: string; actionUrl: string; } export const MyCustomEmail = ({ userName, actionUrl }: MyCustomEmailProps) => { const ds = betterAuthDesignSystem; return ( Action required for your account
Hello, {userName}! Please take action on your account.
); }; ``` Then use it in your plugin configuration: ```tsx theme={null} import { MyCustomEmail } from './emails/my-custom-email'; inboundEmailPlugin({ client: { apiKey: process.env.INBOUND_API_KEY! }, from: 'security@yourdomain.com', events: { 'password-changed': { template: (ctx) => ({ subject: 'Password Changed', react: , }), }, }, }); ``` ## Disabling Events To disable specific events: ```typescript theme={null} inboundEmailPlugin({ client: { apiKey: process.env.INBOUND_API_KEY! }, from: 'security@yourdomain.com', events: { 'account-created': { enabled: false }, 'new-device-sign-in': { enabled: false }, }, }); ``` ## How It Works The plugin uses Better Auth's `after` hooks to intercept successful authentication operations: * **Password changes**: Hooks into `/change-password` endpoint * **Email changes**: Hooks into `/change-email` endpoint * **Sign-ins**: Hooks into `/sign-in/email`, `/sign-in/social`, `/sign-in/magic-link`, `/sign-in/passkey` * **Sign-ups**: Hooks into `/sign-up/email`, `/sign-up/social` * **2FA changes**: Hooks into `/two-factor/enable`, `/two-factor/disable` New device detection works by tracking unique combinations of IP address and user agent per user. The first sign-in from a new device/browser triggers a notification email. ## TypeScript Support All types are exported for full TypeScript support: ```typescript theme={null} import type { InboundEmailPluginOptions, AuthEventType, AuthEventContext, EmailContent, EmailTemplateFunction, PasswordChangedContext, EmailChangedContext, NewDeviceSignInContext, AccountCreatedContext, } from 'inboundemail/better-auth'; ``` # MCP Server Source: https://inbound.new/docs/integrations/mcp Connect Claude, ChatGPT, Cursor, OpenCode and other AI agents to Inbound, and give agents their own mailboxes Inbound's MCP (Model Context Protocol) server lets AI assistants manage your domains, endpoints, addresses, emails and Guard rules. It can also create **mailboxes**: virtual email accounts that an agent can read, reply from and send from. **Server URL:** `https://inbound.new/mcp` ## Connect ### With OAuth (recommended) Add the URL to your client. When it connects, it opens Inbound in your browser; sign in and approve access. No API key needed. ```bash Claude Code theme={null} claude mcp add --transport http inbound https://inbound.new/mcp ``` ```json Cursor (.cursor/mcp.json) theme={null} { "mcpServers": { "inbound": { "type": "http", "url": "https://inbound.new/mcp" } } } ``` ```json OpenCode (opencode.json) theme={null} { "mcp": { "inbound": { "type": "remote", "url": "https://inbound.new/mcp" } } } ``` In Claude.ai and ChatGPT, add a custom connector with the same URL. OAuth grants full access to your account, the same as an API key. The approval screen names the app and where it will send you back to; only approve apps you started the connection from. ### With an API key For scripts and clients without OAuth, send an API key from [settings](https://inbound.new/settings) as a bearer token: ```json theme={null} { "mcpServers": { "inbound": { "type": "http", "url": "https://inbound.new/mcp", "headers": { "Authorization": "Bearer YOUR_API_KEY" } } } } ``` The `x-inbound-api-key` header also works. Clients that only support STDIO can bridge with `npx mcp-remote https://inbound.new/mcp --header "Authorization:Bearer ${INBOUND_API_KEY}"`. ## Give an agent its own mailbox 1. Ask your assistant to create a mailbox, e.g. *"Create a mailbox for [agent@yourdomain.com](mailto:agent@yourdomain.com) called Support Agent"*. This calls `create_mailbox`, which returns a password shown once. 2. Connect the agent to `https://inbound.new/mcp` with `Authorization: Bearer `. 3. The agent now has one email account: it can check its inbox, read, reply, send and archive. It can't see other mail in your account or send as any other address. The Inbound API enforces this. The same mailbox password works over [IMAP](/docs/mailboxes/connect-imap) and [SMTP](/docs/mailboxes/connect-smtp), and the mailbox appears in the [dashboard](https://inbound.new/mailboxes). The address must be on a verified domain; new accounts can use their instant `*.inbnd.dev` domain. ### Mailbox tools | Tool | Description | | - | - | | `whoami` | The mailbox's address, sending identity, scopes and unread count | | `list_messages` | Inbox, archive or sent folder, with `unread_only` and search | | `read_message` | Full message; marks it read | | `update_message` | Mark read/unread, archive/unarchive | | `get_thread` | The whole conversation | | `reply` | Reply to a received message, threaded | | `send_email` | Send a new email as the mailbox | | `download_attachment` | Fetch an attachment | ## Account tools Connected with OAuth or an API key, the server exposes 50 tools: | Area | Tools | | - | - | | Domains | `list_domains`, `get_domain`, `create_domain`, `update_domain`, `delete_domain`, `enable_domain_dkim` | | Endpoints | `list_endpoints`, `get_endpoint`, `create_endpoint`, `update_endpoint`, `delete_endpoint`, `test_endpoint` | | Addresses | `list_email_addresses`, `get_email_address`, `create_email_address`, `update_email_address`, `delete_email_address` | | Emails | `list_emails`, `get_email`, `send_email`, `reply_to_email`, `update_email`, `cancel_scheduled_email`, `pause_scheduled_email`, `resume_scheduled_email`, `retry_email_delivery`, `list_threads`, `get_thread`, `list_attachments`, `download_attachment` | | Mailboxes | `list_mailboxes`, `create_mailbox`, `update_mailbox`, `delete_mailbox`, `rotate_mailbox_password`, `get_mailbox`, `list_mailbox_messages`, `read_mailbox_message`, `update_mailbox_message`, `get_mailbox_thread`, `send_from_mailbox`, `reply_from_mailbox`, `download_mailbox_attachment` | | Guard | `list_guard_rules`, `get_guard_rule`, `create_guard_rule`, `update_guard_rule`, `delete_guard_rule`, `check_guard_rule`, `generate_guard_rule` | To expose fewer tools, add `?toolsets=` with any of `domains,endpoints,addresses,emails,mailboxes,guard`, for example `https://inbound.new/mcp?toolsets=mailboxes,emails`. ## Example prompts * "Show me unread email to [support@mydomain.com](mailto:support@mydomain.com)" * "Create a webhook endpoint for [https://example.com/hooks](https://example.com/hooks) and route [orders@mydomain.com](mailto:orders@mydomain.com) to it" * "Create a mailbox for [agent@mydomain.com](mailto:agent@mydomain.com) and give me its MCP config" * "Reply to the last email from [jane@example.com](mailto:jane@example.com) saying we'll ship Friday" * "Block cold sales outreach with a Guard rule" The server is open source: [inboundemail/mcp](https://github.com/inboundemail/mcp). # Connect with IMAP Source: https://inbound.new/docs/mailboxes/connect-imap Create a managed mailbox credential and read mail over IMAP Connect an email client or application to Inbound using a managed **Mailbox + SMTP** credential. The same login email and generated password also work for [sending with SMTP](/docs/mailboxes/connect-smtp). ## Connection settings | Setting | Value | | - | - | | Host | `imap.inboundemail.com` | | Port | `993` | | Security | Implicit TLS (SSL/TLS), TLS 1.2 or later | | Username | The credential's **Login email** | | Password | The credential's generated password | | Authentication | Normal password (`LOGIN` or `AUTHENTICATE PLAIN`) | Port `143` and IMAP `STARTTLS` are not offered. Keep certificate verification enabled. Ordinary account API keys and **SMTP only** credentials cannot authenticate to IMAP. SMTP does not automatically save messages to IMAP `Sent`, and received messages removed from `INBOX` reappear. Review [IMAP behavior and limits](/docs/mailboxes/imap-behavior) before configuring a traditional email client. ## Create a mailbox credential Add and verify a domain in [domain settings](https://inbound.new/emails), including its receiving MX records if you want incoming mail. Then open the [Mailboxes & SMTP dashboard](https://inbound.new/mailboxes) and select **Create credential**. Select **Mailbox + SMTP** under **Credential type**. Enter a descriptive **Name** and a **Login email** on an exact domain you own and have verified. The login email is your authentication username; it does not have to match the receiving or sending address. Under **IMAP access**, choose **Read only** to prevent mailbox changes or **Read and write** to save messages in `Sent` or `Drafts`, change flags, and modify supported mailboxes. Read-only credentials can still send through SMTP. Scope-specific folders are always read-only. Select **Exact identity** to restrict sending to one **Exact From address** covered by a scope. The optional dashboard **Display name** does not set the name recipients see; provide that name in your email client's From header instead. Alternatively, select **Any scoped domain** to permit any sender address on each exact domain represented by your scopes. Subdomains are not included. An address scope only limits which mail is received. With **Any scoped domain**, a scope for `support@example.com` still permits sending from `billing@example.com`, `admin@example.com`, or any other address at `example.com`. Choose **Exact identity** to restrict sending to one address. Under **Mail access & sending scopes**, choose **Domain** for an entire verified domain or **Address** for one exact address. For an address scope, enter the local part without `@`, then choose the verified domain. Select **Add** for each scope. At least one scope is required, and scopes determine which received messages are visible through IMAP. Select **Create credential**. The confirmation dialog displays the **Username**, generated **Password**, and the IMAP and SMTP connection settings. Copy the password into a password manager or secret manager before selecting **I saved the password**. The generated password is a mail-scoped API key and is shown only once. Anyone with it can send mail within its sender policy, including through the HTTP email-sending endpoint. If it is lost or exposed, rotate it immediately and update every client. ## Connect from your application Store your managed credential as `INBOUND_MAILBOX_LOGIN` and `INBOUND_MAILBOX_PASSWORD` in your application's secret manager or environment. Both examples open `INBOX` read-only and inspect message headers without changing mailbox state. For the TypeScript example, install ImapFlow: ```bash theme={null} bun add imapflow ``` ```typescript ImapFlow theme={null} import { ImapFlow } from "imapflow"; const login = process.env.INBOUND_MAILBOX_LOGIN; const password = process.env.INBOUND_MAILBOX_PASSWORD; if (!login || !password) { throw new Error("Set INBOUND_MAILBOX_LOGIN and INBOUND_MAILBOX_PASSWORD"); } const client = new ImapFlow({ host: "imap.inboundemail.com", port: 993, secure: true, auth: { user: login, pass: password }, }); await client.connect(); try { const lock = await client.getMailboxLock("INBOX", { readOnly: true }); try { const totalMessages = client.mailbox ? client.mailbox.exists : 0; if (totalMessages === 0) { console.log("INBOX is empty"); } else { const firstMessage = Math.max(1, totalMessages - 9); const range = `${firstMessage}:${totalMessages}`; for await (const message of client.fetch(range, { envelope: true })) { console.log({ uid: message.uid, from: message.envelope?.from, subject: message.envelope?.subject, }); } } } finally { lock.release(); } } finally { await client.logout(); } ``` ```python Python theme={null} import imaplib import os import ssl from email import policy from email.parser import BytesParser login = os.environ["INBOUND_MAILBOX_LOGIN"] password = os.environ["INBOUND_MAILBOX_PASSWORD"] context = ssl.create_default_context() with imaplib.IMAP4_SSL( "imap.inboundemail.com", 993, ssl_context=context ) as client: client.login(login, password) status, _ = client.select("INBOX", readonly=True) if status != "OK": raise RuntimeError("Could not open INBOX") status, message_ids = client.search(None, "ALL") if status != "OK": raise RuntimeError("Could not search INBOX") for message_id in message_ids[0].split()[-10:]: status, parts = client.fetch( message_id, "(BODY.PEEK[HEADER.FIELDS (FROM SUBJECT DATE)])", ) if status != "OK": continue for part in parts: if isinstance(part, tuple) and isinstance(part[1], bytes): headers = BytesParser(policy=policy.default).parsebytes(part[1]) print({ "from": str(headers.get("From", "")), "subject": str(headers.get("Subject", "")), "date": str(headers.get("Date", "")), }) ``` `BODY.PEEK` reads the requested headers without marking a message as seen. Opening `INBOX` in read-only mode also works with both **Read only** and **Read and write** credentials. ## Troubleshooting ### Authentication fails Use the managed **Login email** and generated `mail_` or legacy `imap_` password, not an ordinary account API key or your dashboard password. Confirm that the credential is enabled and that its password hasn't been rotated. **SMTP only** credentials cannot authenticate to IMAP. Authentication also fails when the login email's domain, or every scope domain, is no longer verified. After 10 failed attempts for the same login from the same IP address within 15 minutes, further attempts are rejected, even with the correct password, until the window ends. A `NO [TEMPFAIL]` response means the authentication service was unavailable; retry later. ### The connection closes right away The server allows 20 simultaneous connections per client IP address and replies `* BYE Too many connections from this address` above that. Close unused sessions or lower your client's connection count. Connections that stay idle for 60 seconds before logging in are closed. See [connection limits and timeouts](/docs/mailboxes/imap-behavior#connection-limits-and-timeouts). ### The inbox is empty Check that the receiving domain is verified and its receiving MX records are configured and verified in [domain settings](https://inbound.new/emails). Confirm the message's recipient matches one of the credential's domain or address scopes. The login email alone does not grant access to messages outside those scopes. ### TLS or certificate validation fails Use port `993` with implicit TLS (often labeled SSL/TLS), not STARTTLS, and make sure the client supports TLS 1.2 or later. Keep hostname and certificate validation enabled. Test the TLS handshake without providing a username or password: ```bash theme={null} openssl s_client -connect imap.inboundemail.com:993 \ -servername imap.inboundemail.com \ -verify_hostname imap.inboundemail.com \ -verify_return_error ``` After the handshake, type `a CAPABILITY` to see the advertised capabilities and `b LOGOUT` to close the connection. ### A domain is missing from the scope selector [Add and verify the domain](https://inbound.new/emails) before creating or editing a credential, and use an exact owned, verified domain for the login email. Select **Add** after configuring each scope. Learn more about [scopes and permissions](/docs/mailboxes/scopes-and-permissions), [managing credentials](/docs/mailboxes/manage-credentials), and [IMAP behavior](/docs/mailboxes/imap-behavior). For sending problems, see [Send with SMTP](/docs/mailboxes/connect-smtp#reply-codes). # Send with SMTP Source: https://inbound.new/docs/mailboxes/connect-smtp Send email through Inbound from any SMTP client using a managed credential Any SMTP client or library can send mail through Inbound with a managed **Mailbox + SMTP** or **SMTP only** credential. Messages submitted over SMTP go through the same sending pipeline as the [send email API](/docs/api-reference/emails/send-an-email), so the same account sending limits and domain checks apply. If you don't have a credential yet, follow [Create a mailbox credential](/docs/mailboxes/connect-imap#create-a-mailbox-credential). Choose **SMTP only** if the application never needs to read mail. ## Connection settings | Setting | Value | | - | - | | Host | `smtp.inboundemail.com` | | Port `465` | Implicit TLS (TLS from the first byte) | | Port `587` | STARTTLS (upgrade to TLS before authenticating) | | Username | The credential's **Login email** | | Password | The credential's generated password | | Authentication | `PLAIN` or `LOGIN` (normal password) | Both ports require TLS 1.2 or later. On port `587`, the server only offers `AUTH` after `STARTTLS` has completed, and authentication attempted before TLS is rejected with `538`. Keep certificate verification enabled. Ordinary account API keys and your dashboard password cannot authenticate to SMTP. ## Allowed senders Each credential has a sender policy. The gateway checks both the envelope sender (`MAIL FROM`) and the address in the message's `From` header against it: | Sender policy | Allowed `MAIL FROM` and `From` addresses | | - | - | | **Exact identity** (`identity`) | Only the configured sending address | | **Any scoped domain** (`scoped_domains`) | Any address on an exact domain in the credential's scopes. Subdomains are not included. | An empty envelope sender (`MAIL FROM:<>`) is accepted, but the `From` header must still be allowed. If the message has no `From` header, the envelope sender is used as the From address. A disallowed sender is rejected with `553`. The display name comes from the `From` header you send. The dashboard's **Display name** field does not change outgoing messages. See [SMTP sender policies](/docs/mailboxes/scopes-and-permissions#smtp-sender-policies) for how scopes and sender policies interact. ## Recipients and Bcc The envelope recipients (`RCPT TO`) decide who receives the message: * Addresses in the `To` and `Cc` headers are delivered and shown only if they are also envelope recipients. Header addresses that are not envelope recipients are removed. * Envelope recipients that don't appear in `To` or `Cc` are delivered as Bcc and are not visible to other recipients. * A `Bcc` header in the message is ignored. Nodemailer and Python's `send_message` build the envelope from `To`, `Cc`, and `Bcc` automatically, so Bcc works as expected with both. ## How messages are rebuilt Inbound parses each submitted message and sends it again through the email API; it does not relay the raw bytes. The delivered message keeps: * `From`, `To`, `Cc`, `Reply-To`, and `Subject` * The plain-text and HTML bodies * Attachments, including inline images referenced by `Content-ID` * `In-Reply-To`, `References`, and custom `X-` headers (except `X-SES-*`) Other headers, such as your own `Message-ID` or `Date`, are not preserved. Line breaks and control characters are removed from header values. ## Limits | Limit | Value | | - | - | | Maximum message size | 3 MiB (`3,145,728` bytes), advertised as `SIZE 3145728` | | Recipients per message | 50 distinct envelope recipients | | Simultaneous connections per client IP address | 10 | | Idle connection timeout | 60 seconds | | Failed logins | 10 per login address and IP address, and 50 per IP address, in a 15-minute window | The size limit applies to the complete MIME message. Base64 encoding makes attachments about a third larger than the original files. If the client sends a `SIZE` parameter above the limit, the message is rejected before `DATA`. When a login is throttled, further attempts from that address fail with `421` until the 15-minute window ends, even with the correct password. Authentication requests are also rate-limited by the API. SMTP sends count toward your account's sending limits and [API rate limits](/docs/api-reference/rate-limits). SMTP does not save a copy in the IMAP `Sent` folder; see [Sent mail is not saved automatically](/docs/mailboxes/imap-behavior#sent-mail-is-not-saved-automatically). ## Supported extensions The server advertises `PIPELINING`, `8BITMIME`, `SIZE`, `STARTTLS` (port `587`, before TLS), and `AUTH PLAIN LOGIN` (after TLS). `SMTPUTF8`, `DSN`, and `ENHANCEDSTATUSCODES` are not supported: * Addresses must be ASCII. A non-ASCII local part is rejected with `553`. Non-ASCII display names, subjects, and bodies work normally when MIME-encoded. * `MAIL FROM` accepts only the `SIZE`, `BODY`, and `AUTH` parameters, and `RCPT TO` accepts none. Others, such as the DSN parameters `RET`, `ENVID`, `NOTIFY`, and `ORCPT`, are rejected with `555`. In Nodemailer, leave the `dsn` option unset. After `STARTTLS`, send `EHLO` again before `AUTH`. SMTP libraries do this automatically. ## Reply codes Reply text includes an enhanced status code, such as `5.7.8`, even though `ENHANCEDSTATUSCODES` is not advertised. | Code | Meaning | What to do | | - | - | - | | `250` | Accepted. The reply text includes `Queued as` and the Inbound email ID. | Nothing | | `421` | Too many connections from your IP address, too many failed logins, or the connection is closing | Reduce concurrent connections, or wait out the 15-minute login window. Reconnect with backoff. | | `451` | Temporary failure: rate limited, the same message is already being processed, the gateway is busy, or the email API is unavailable | Retry later with backoff | | `452` | More than 50 recipients | Send the remaining recipients in another message | | `454` | The authentication service is unavailable | Retry later with backoff | | `530` | `MAIL FROM` was sent before authenticating | Authenticate first | | `535` | Invalid login email or password, disabled credential, or the domain or scope is no longer verified | Check the credential. Repeated failures lead to `421`. | | `538` | `AUTH` was attempted before TLS | Use port `465`, or run `STARTTLS` on port `587` first | | `550` | The email API rejected the message (for example, not authorized or invalid content), or the message has no sender or recipients | Read the reply text and fix the message or account. Don't retry unchanged. | | `552` | The message is larger than 3 MiB | Reduce the size or remove attachments | | `553` | The sender isn't allowed by the credential's sender policy, or an address contains non-ASCII characters | Use an allowed From and `MAIL FROM` address | | `555` | Unsupported `MAIL FROM` or `RCPT TO` parameter, such as DSN or `SMTPUTF8` | Remove the parameter | Inbound derives an idempotency key from the credential, envelope sender, recipients, and exact message bytes. If a client resubmits an identical message that was already sent, for example after a dropped connection, Inbound returns the original result instead of sending it twice. Libraries that generate a new `Message-ID` or `Date` for each attempt produce a different message, so this only covers retries of the same bytes. ## Examples Store the credential as `INBOUND_MAILBOX_LOGIN` and `INBOUND_MAILBOX_PASSWORD`. Replace `support@example.com` with an address your sender policy allows. The TypeScript example uses Nodemailer (`bun add nodemailer`); the Python example uses only the standard library. ```typescript Nodemailer theme={null} import nodemailer from "nodemailer"; const transport = nodemailer.createTransport({ host: "smtp.inboundemail.com", port: 465, secure: true, // use port: 587, secure: false, requireTLS: true for STARTTLS auth: { user: process.env.INBOUND_MAILBOX_LOGIN, pass: process.env.INBOUND_MAILBOX_PASSWORD, }, }); const info = await transport.sendMail({ from: "Support ", to: "recipient@example.com", subject: "Hello from Inbound", text: "Sent over SMTP.", }); console.log(info.response); ``` ```python Python theme={null} import os import smtplib import ssl from email.message import EmailMessage message = EmailMessage() message["From"] = "Support " message["To"] = "recipient@example.com" message["Subject"] = "Hello from Inbound" message.set_content("Sent over SMTP.") context = ssl.create_default_context() with smtplib.SMTP("smtp.inboundemail.com", 587) as smtp: smtp.starttls(context=context) smtp.login( os.environ["INBOUND_MAILBOX_LOGIN"], os.environ["INBOUND_MAILBOX_PASSWORD"], ) smtp.send_message(message) ``` For port `465` in Python, use `smtplib.SMTP_SSL("smtp.inboundemail.com", 465, context=context)` and skip `starttls()`. ### Mail client settings | Field | Value | | - | - | | Outgoing server | `smtp.inboundemail.com` | | Port and security | `465` with SSL/TLS, or `587` with STARTTLS | | Authentication | Normal password | | Username | Your credential's login email | | Password | Your credential's generated password | Set the account's email address to an address your sender policy allows. To receive mail in the same client, add the [IMAP settings](/docs/mailboxes/connect-imap#connection-settings). ## Test the connection Check TLS and the advertised extensions without sending credentials: ```bash theme={null} openssl s_client -connect smtp.inboundemail.com:587 \ -starttls smtp \ -servername smtp.inboundemail.com \ -verify_return_error ``` After the handshake, type `EHLO example.com`. The reply should list `AUTH PLAIN LOGIN` and `SIZE 3145728`. Type `QUIT` to close the connection. # IMAP Behavior Source: https://inbound.new/docs/mailboxes/imap-behavior Folders, supported IMAP features, synchronization, limits, and known limitations Inbound presents received email through a managed IMAP mailbox rather than a conventional standalone mail store. `INBOX` and scope folders are views of the mail Inbound has received, so some operations behave differently than on a traditional IMAP server. ## Default folders Each mailbox credential includes these folders: | Folder | Special-use attribute | Behavior | | - | - | - | | `INBOX` | None | Incoming mail from every configured scope | | `Sent` | `\Sent` | For client-saved sent messages; not populated automatically | | `Drafts` | `\Drafts` | For client-saved drafts | | `Trash` | `\Trash` | For client-organized messages | | `Junk` | `\Junk` | For client-organized messages; incoming mail is not filtered into it | The personal namespace is `""` and the hierarchy separator is `/`. `INBOX` and the special-use folders cannot be deleted, and `INBOX` cannot be renamed. With a `read_write` credential you can create, rename, and delete other folders. Renaming a folder also renames its subfolders. Creating or renaming onto an existing name returns `NO [ALREADYEXISTS]`. Folders, flags, and appended messages belong to one credential. Other credentials on the same account don't see them. ### Scope folders Every configured scope also appears as a folder under `Scopes`: ```text theme={null} INBOX Sent Drafts Trash Junk Scopes/*@example.com Scopes/support@another-example.com ``` `Scopes` is a non-selectable container (`\Noselect`), and its name is reserved. A domain scope appears as `Scopes/*@example.com`; an address scope appears as `Scopes/support@example.com`. Scope folders are filtered views of the same messages as `INBOX`. They always open read-only, even for `read_write` credentials. You cannot append, move, or copy messages into a scope folder, change flags in it, or expunge from it. A `read_write` credential can copy messages from a scope folder into a writable folder. ## Capabilities The server advertises exactly these capabilities: ```text theme={null} IMAP4rev1 APPENDLIMIT=1048576 AUTH=PLAIN CHILDREN ENABLE ID IDLE MOVE NAMESPACE SASL-IR SPECIAL-USE UIDPLUS UNSELECT UTF8=ACCEPT WITHIN ``` Supported commands: * Authentication: `LOGIN` and `AUTHENTICATE PLAIN` (with `SASL-IR`). A `PLAIN` authorization identity different from the login email is rejected. * Folders: `LIST`, `LSUB`, `NAMESPACE`, `SELECT`, `EXAMINE`, `STATUS`, `CREATE`, `RENAME`, `DELETE`, `SUBSCRIBE`, `UNSUBSCRIBE`. * Messages: `FETCH`, `SEARCH`, `STORE`, `COPY`, `MOVE`, and their `UID` forms, plus `EXPUNGE`, `UID EXPUNGE`, `APPEND`, `CLOSE`, `UNSELECT`, `CHECK`, `NOOP`, and `IDLE`. * `ENABLE UTF8=ACCEPT`. Other `ENABLE` arguments are ignored. `COPY` and `APPEND` return `COPYUID` and `APPENDUID`. `MOVE` returns `COPYUID` followed by an untagged `EXPUNGE` for every moved message, and moving a message into the folder it's already in returns `NO`. `EXPUNGE` and `UID EXPUNGE` send an untagged `EXPUNGE` for each removed message. `SUBSCRIBE` and `UNSUBSCRIBE` succeed but aren't stored; `LSUB` returns every folder. Extensions that aren't advertised are not supported, including `STARTTLS` (use implicit TLS on port `993`), `CONDSTORE`, `QRESYNC`, `QUOTA`, `COMPRESS=DEFLATE`, `SORT`, `THREAD`, `ACL`, OAuth (`XOAUTH2` or `OAUTHBEARER`), and the Gmail-only `XLIST`. Requests that would enable `CONDSTORE`, such as `SELECT (CONDSTORE)` or `SEARCH MODSEQ`, are rejected. ### Search `SEARCH` supports sequence sets, `UID`, flag and keyword keys (such as `SEEN`, `UNSEEN`, `DELETED`, `FLAGGED`, `KEYWORD`), `BEFORE`/`ON`/`SINCE`, `SENTBEFORE`/`SENTON`/`SENTSINCE`, `LARGER`/`SMALLER`, `OLDER`/`YOUNGER` (`WITHIN`), `HEADER`, `FROM`, `TO`, `CC`, `BCC`, `SUBJECT`, `BODY`, `TEXT`, `OR`, and `NOT`. * Text searches are case-insensitive substring matches. For received mail they match Inbound's parsed fields: `SUBJECT`, the address headers, and the plain-text body. `TEXT` checks the subject and plain-text body, so text that exists only in an HTML part may not match. For appended messages they match the raw message. * `HEADER` searches on other header names match the raw message. * `SENTBEFORE`, `SENTON`, and `SENTSINCE` use the parsed `Date` header of received mail and don't match appended messages. `BEFORE`, `ON`, `SINCE`, `OLDER`, and `YOUNGER` use the internal date, which is when Inbound received the message. * `\Recent` is never set, so `RECENT` and `NEW` match nothing. * Search keys the server doesn't implement are ignored instead of rejected, which can return more messages than requested. Filter results in your application when exact matching matters. ## New-message notifications During `IDLE`, the server sends `EXISTS` as soon as new mail is synchronized into the selected folder. Outside `IDLE`, new messages are reported on the next command, such as `NOOP`. Active sessions are also refreshed about every 60 seconds, which catches messages that don't trigger an immediate notification, such as mail where the scoped address was a secondary envelope recipient. An `IDLE` command ends after 30 minutes: the server sends `* BYE IDLE terminated` and closes the connection. Reissue `IDLE` before then (most clients restart it every 29 minutes) or reconnect automatically. ## Flags and read state Writable credentials can set standard flags (`\Seen`, `\Answered`, `\Flagged`, `\Deleted`, `\Draft`) and keywords in writable folders. Fetching message content from a writable folder sets `\Seen` unless the client uses `BODY.PEEK`. Read-only credentials, `EXAMINE`, and scope folders never set `\Seen`. `STORE` without `.SILENT` returns the updated flags. Flags set on a received message in `INBOX` also apply to the same message in the credential's scope folders. Flags on copies in other folders, such as a custom folder, are independent. IMAP flags are separate from the dashboard's read state. Marking a message read in an email client does not mark it read in the dashboard, and the reverse is also true. ## Known limitations ### Other sessions don't see changes immediately Only new messages are pushed to other open sessions. If one session expunges, moves, or changes flags, other sessions on the same folder see the change only after they select the folder again. Clients that keep several connections open, or several devices using the same credential, can briefly show stale messages or flags. ### Received-message deletion and moves `EXPUNGE` and `MOVE` do not delete the underlying received email. `INBOX` and scope folders are rebuilt from received mail, so a received message expunged or moved out of `INBOX` reappears there, with a new UID, the next time the folder is synchronized (for example, on the next `SELECT` or when new mail arrives). Don't use IMAP deletion as permanent deletion. Moving a received message into a custom folder, `Trash`, or `Junk` does create a copy there that stays until you expunge it. Messages created with `APPEND`, such as sent copies and drafts, are stored separately and are permanently removed when their last copy is expunged. ### Sent mail is not saved automatically Sending through SMTP does not create a message in `Sent`. Many email clients save their own copy with `APPEND`, which needs a `read_write` mailbox credential. SMTP accepts messages up to 3 MiB, but `APPEND` accepts at most 1 MiB. A larger email can send successfully while saving its sent copy fails with `NO [TOOBIG]`. SMTP-only credentials cannot save a sent copy. ### Guard-filtered messages Messages blocked by Guard are not synchronized, and their content cannot be fetched. If a message is blocked after it was already synchronized, it is removed from the credential's folders the next time the client lists or opens a folder. Email clients may still show content they cached earlier. ## Scope changes and revoked access When a credential's scopes, permissions, sender settings, enabled state, or password change, its active IMAP sessions receive `* BYE Mailbox credentials or scopes changed` and must reconnect. On the next folder listing or selection, folders for removed scopes disappear, and received messages that no longer match any remaining scope are removed from all of the credential's folders. Appended messages are not affected. Each login rechecks that the login email's domain is verified, that at least one scope domain is verified, and that an exact sender identity is still covered by a verified scope. If not, authentication fails. A domain losing its verified status does not disconnect an established IMAP session. To end current access, disable the credential, change its scopes or permissions, rotate its password, or delete it. ## Message and storage limits | Limit | Value | | - | - | | Maximum `APPEND` size | 1 MiB (`1,048,576` bytes), advertised as `APPENDLIMIT` | | Appended storage per account | 250 MiB | | Appended messages per account | 5,000 | An `APPEND` over 1 MiB is rejected with `NO [TOOBIG]`. The storage and message-count limits are shared by all of the account's credentials and folders; exceeding them returns `NO [OVERQUOTA]`. For SMTP limits, see [Send with SMTP](/docs/mailboxes/connect-smtp#limits). ## Connection limits and timeouts | Limit | Value | | - | - | | TLS | Implicit TLS on port `993`, TLS 1.2 or later | | Simultaneous connections per client IP address | 20 | | Inactivity timeout before login | 60 seconds | | Inactivity timeout after login | About 30 minutes | | Maximum `IDLE` duration | 30 minutes | | Failed logins | 10 per login address and IP address, and 50 per IP address, in a 15-minute window | Connections over the per-IP limit receive `* BYE Too many connections from this address`. Once a login is throttled, attempts return `NO [AUTHENTICATIONFAILED]` until the window ends, even with the correct password. Authentication requests are also rate-limited by the API, so avoid reconnect loops and retry with backoff. ## Troubleshooting | Symptom | Likely cause | What to check | | - | - | - | | Authentication fails | Wrong login, rotated password, disabled credential, SMTP-only credential, unverified domain, or login throttling | Confirm the login email, current password, credential type, enabled state, and verified domains. Wait 15 minutes after repeated failures. | | An expected message is missing | Recipient is outside the scopes, the message is Guard-blocked, or the folder hasn't been refreshed | Check the recipient, scope domains, and Guard status, then reselect the folder | | A scope folder rejects changes | `Scopes/*` folders are always read-only | Change the message in `INBOX` or copy it to a writable folder | | A received message returns after deletion or moving | `INBOX` is rebuilt from received mail | Don't use IMAP deletion as permanent deletion | | No sent copy appears | SMTP doesn't populate `Sent`, or the client's `APPEND` exceeded 1 MiB or the storage limit | Enable sent-copy saving in the client, use a `read_write` credential, and check message size | | Another device shows stale flags or deleted messages | Other sessions only see changes after reselecting the folder | Reselect the folder or restart the client's sync | | The connection closes after a credential edit | Credential changes end active sessions | Reconnect with the current password and permissions | Understand exact domain matching, sender policies, and read-only scope views. Change access levels, replace scopes, rotate passwords, and disable credentials. # Manage Credentials Source: https://inbound.new/docs/mailboxes/manage-credentials Create, update, disable, rotate, and delete managed mailbox and SMTP credentials Manage mailbox and SMTP credentials from the dashboard or the authenticated REST API. A credential controls one login address, its incoming-mail scopes, its SMTP sender policy, and its enabled state. ## Use the dashboard Go to [Mailboxes & SMTP](https://inbound.new/mailboxes). You need at least one verified domain before creating a credential. Select **Create credential**, then choose **Mailbox + SMTP** or **SMTP only**. Enter a name and login email, select the IMAP access level when applicable, configure the sender policy, and add at least one domain or address scope. After creation, the dashboard displays the login address, generated password, and applicable connection settings. The password is shown only once. Open the credential's actions menu to **Edit**, **Rotate password**, **Disable** or **Enable**, or **Delete** the credential. A generated mailbox or SMTP password cannot be retrieved after the creation or rotation dialog closes. Store it securely. If you lose it, rotate the password and update every client using that credential. ## Authenticate REST requests The dashboard uses your authenticated account session. For programmatic REST requests, use an ordinary account API key from [API Keys](https://inbound.new/api-keys), supplied as a Bearer token: ```bash theme={null} export INBOUND_API_KEY="YOUR_ACCOUNT_API_KEY" ``` Credential-management endpoints require your ordinary account API token. A generated mailbox or SMTP password is for managed mail authentication and cannot be used to list, create, update, rotate, or delete credentials. All examples use the production API base URL: ```text theme={null} https://inbound.new/api/e2 ``` ### Find a verified domain ID A scope references a verified domain's ID, not just its domain name. Obtain `YOUR_DOMAIN_ID` from the dashboard or list your verified domains: ```bash theme={null} curl "https://inbound.new/api/e2/domains?status=verified&limit=50" \ -H "Authorization: Bearer $INBOUND_API_KEY" ``` Use the `id` of the matching domain in the `data` array. When creating or editing a credential definition, its login address must also use an exact owned, verified domain, which does not have to be the same domain as a scope. ## List credentials ```bash theme={null} curl "https://inbound.new/api/e2/mailboxes?limit=20&offset=0" \ -H "Authorization: Bearer $INBOUND_API_KEY" ``` A successful response contains the credentials and offset-based pagination metadata: ```json theme={null} { "data": [ { "id": "YOUR_MAILBOX_ID", "type": "mailbox", "name": "Support inbox", "loginAddress": "imap@example.com", "accessMode": "read_write", "sendingMode": "identity", "sendingName": "Support", "sendingAddress": "support@example.com", "enabled": true, "scopes": [ { "id": "SCOPE_ID", "type": "address", "domainId": "YOUR_DOMAIN_ID", "domain": "example.com", "address": "support@example.com" } ], "createdAt": "2026-01-01T00:00:00.000Z", "updatedAt": "2026-01-01T00:00:00.000Z", "lastUsedAt": null } ], "pagination": { "limit": 20, "offset": 0, "total": 1, "hasMore": false } } ``` `limit` defaults to `50` and cannot exceed `100`. Increase `offset` while `pagination.hasMore` is `true`. ## Create a credential `POST /mailboxes` requires every top-level field shown below, including `sendingName` and `sendingAddress`; nullable values must still be included. Provide between 1 and 100 unique scopes. Accounts can create up to 100 managed credentials by default. ```bash theme={null} curl -X POST "https://inbound.new/api/e2/mailboxes" \ -H "Authorization: Bearer $INBOUND_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "type": "mailbox", "name": "Support inbox", "loginAddress": "imap@example.com", "accessMode": "read_write", "sendingMode": "identity", "sendingName": "Support", "sendingAddress": "support@example.com", "scopes": [ { "type": "address", "domainId": "YOUR_DOMAIN_ID", "address": "support@example.com" } ] }' ``` Replace `YOUR_DOMAIN_ID` with the verified domain ID returned by `GET /domains`. A successful creation returns HTTP `201`: ```json theme={null} { "data": { "id": "YOUR_MAILBOX_ID", "name": "Support inbox", "loginAddress": "imap@example.com" }, "password": "YOUR_GENERATED_MAIL_PASSWORD" } ``` The real `data` object includes the complete credential fields shown in the list response. `password` appears only in this creation response. For a domain-wide scope, use `{"type":"domain","domainId":"YOUR_DOMAIN_ID"}`. For `sendingMode: "scoped_domains"`, provide `"sendingName": null` and `"sendingAddress": null`. SMTP-only credentials use `"type": "smtp"`; `accessMode` is still required in the request and is normalized to `read_write` in responses. An address scope plus `scoped_domains` allows sending from any address on that exact domain. See [scopes and permissions](/docs/mailboxes/scopes-and-permissions#any-scoped-domain) before choosing a sender policy. ## Update a credential `PUT /mailboxes/:id` accepts a partial JSON object. Omitted fields retain their existing values. ```bash theme={null} curl -X PUT "https://inbound.new/api/e2/mailboxes/YOUR_MAILBOX_ID" \ -H "Authorization: Bearer $INBOUND_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Customer support", "accessMode": "read" }' ``` To change scopes, provide the complete replacement scope list: ```bash theme={null} curl -X PUT "https://inbound.new/api/e2/mailboxes/YOUR_MAILBOX_ID" \ -H "Authorization: Bearer $INBOUND_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "scopes": [ { "type": "domain", "domainId": "YOUR_DOMAIN_ID" } ] }' ``` The resulting configuration must remain valid. For example, an exact sending identity must still be covered by the replacement scopes. Successful updates return `{"data": {...}}`. ### Disable or re-enable access ```bash theme={null} curl -X PUT "https://inbound.new/api/e2/mailboxes/YOUR_MAILBOX_ID" \ -H "Authorization: Bearer $INBOUND_API_KEY" \ -H "Content-Type: application/json" \ -d '{"enabled": false}' ``` ```bash theme={null} curl -X PUT "https://inbound.new/api/e2/mailboxes/YOUR_MAILBOX_ID" \ -H "Authorization: Bearer $INBOUND_API_KEY" \ -H "Content-Type: application/json" \ -d '{"enabled": true}' ``` Disabling prevents new IMAP and SMTP authentication while preserving the credential, its folders, drafts, sent copies, flags, and other mailbox state. Re-enabling preserves the existing password unless you also rotate it. ## Rotate a password ```bash theme={null} curl -X POST \ "https://inbound.new/api/e2/mailboxes/YOUR_MAILBOX_ID/rotate-password" \ -H "Authorization: Bearer $INBOUND_API_KEY" ``` A successful response contains only the newly generated password: ```json theme={null} { "password": "YOUR_NEW_MAIL_PASSWORD" } ``` The previous password stops working immediately. The replacement is shown only in this response, so update every IMAP and SMTP client that uses the credential. ## Delete a credential ```bash theme={null} curl -X DELETE "https://inbound.new/api/e2/mailboxes/YOUR_MAILBOX_ID" \ -H "Authorization: Bearer $INBOUND_API_KEY" ``` A successful response is: ```json theme={null} { "success": true } ``` Deletion permanently removes the credential and its credential-specific IMAP folders, drafts, saved sent copies, flags, and locally appended mailbox data. This mailbox state cannot be restored by creating another credential. Disable the credential instead when access should be suspended without losing its state. ## Active sessions and errors Changes to authentication or permissions, including login addresses, access modes, sender settings, scopes, enabled state, and password rotation, cause active IMAP sessions for that credential to be logged out. Clients must reconnect using the current credential settings. Deletion also invalidates active IMAP sessions. A domain verification-status change is different: it affects which scope domains are accepted during subsequent authentication, but does not automatically terminate an established IMAP session. Disable or edit the credential, or rotate its password, when existing sessions must be ended. | HTTP status | Meaning | Recommended action | | - | - | - | | `400` | Invalid credential configuration, scope, or sender identity | Correct the request and verify exact domain ownership | | `401` | Missing or invalid ordinary account API token | Use a valid account API key, not a managed mail password | | `403` | The account cannot perform the request or has reached its credential limit | Check account access and delete unused credentials if necessary | | `404` | The credential does not exist or is not owned by your account | Confirm `YOUR_MAILBOX_ID` | | `409` | The login address is already assigned, or the credential changed during password rotation | Choose a unique login address or retry the password rotation | | `429` | Account API rate limit exceeded | Wait for the `Retry-After` interval before retrying | | `503` | A required request-protection service is temporarily unavailable | Honor `Retry-After` and retry later | Credential-management requests share the account API limit, which defaults to 10 requests per second. See [API rate limits](/docs/api-reference/rate-limits). Choose the right incoming scopes, IMAP access mode, and sender policy. Read mail over IMAP using your generated login and password. Send mail over SMTP using the same credential. # Mailboxes & SMTP Source: https://inbound.new/docs/mailboxes/overview Connect to Inbound with scoped, managed IMAP and SMTP credentials Inbound gives your email clients and applications direct access to email through standard IMAP and SMTP. Create managed credentials in the [Mailboxes & SMTP dashboard](https://inbound.new/mailboxes), choose which verified domains or addresses they can access, and connect using the generated login email and password. ## Connection settings | Protocol | Host | Port | Security | | - | - | - | - | | IMAP | `imap.inboundemail.com` | `993` | TLS | | SMTP | `smtp.inboundemail.com` | `465` | TLS | | SMTP | `smtp.inboundemail.com` | `587` | STARTTLS | Use the credential's **Login email** and generated password. IMAP requires a **Mailbox + SMTP** credential; either credential type can send with SMTP. All connections require TLS 1.2 or later: IMAP uses implicit TLS only, and SMTP on port `587` must upgrade with STARTTLS before authentication. See [Connect with IMAP](/docs/mailboxes/connect-imap) and [Send with SMTP](/docs/mailboxes/connect-smtp) for client settings. ## How it works A managed credential combines three things: 1. **An identity** - A login email and generated password used to authenticate. 2. **Access scopes** - One or more verified domains or exact email addresses the credential can access. 3. **Permissions** - The available protocols, IMAP access level, and authorized SMTP sender addresses. For IMAP-enabled credentials, Inbound provides `INBOX`, `Sent`, `Drafts`, `Trash`, and `Junk`, plus read-only folders for individual scopes. The combined `INBOX` contains received mail covered by the credential's scopes. SMTP uses the same login email and password and enforces the credential's sender policy. The login email must use an exact domain you own and have verified when you create or edit the credential. It is an authentication username and does not need to match the receiving or sending addresses, which are controlled separately by scopes and sender policy. ## Choose a credential type | Credential type | IMAP | SMTP | Best for | | - | - | - | - | | **Mailbox + SMTP** | Read-only or read/write access | Send using the configured sender policy | Email clients and applications that receive and send mail | | **SMTP only** | Not available | Send using the configured sender policy | Applications and services that only send mail | For **Mailbox + SMTP**, select **Read and write** if your client needs to save messages in `Sent` or `Drafts` or change message flags. **Read only** still allows SMTP sending. Scope-specific folders remain read-only with either setting. For send-only applications, create an **SMTP only** credential and use either SMTP configuration above. The same scope and sender-policy rules still apply. Both credential types require at least one scope covering a whole verified domain, such as `*@example.com`, or one exact address, such as `support@example.com`. **Any scoped domain** permits sending from every address on a represented domain, even when the receiving scope covers only one address. For example, a scope for `support@example.com` also permits SMTP sending from `billing@example.com`. Select **Exact identity** to restrict sending to one address. [Add and verify a domain](https://inbound.new/emails) before creating a credential. Only verified domains appear in the scope selector. Receiving mail also requires the domain's receiving MX records to be configured and verified. ## Mail-scoped API keys and security The generated credential password is itself a mail-scoped API key, typically beginning with `mail_`; older credentials can begin with `imap_`. It authenticates IMAP and SMTP alongside the login email and can also authorize HTTP email sending under the same sender policy. It cannot manage account resources. Ordinary account API keys, often stored as `INBOUND_API_KEY`, are a separate credential type. They can access permitted HTTP API resources but cannot authenticate to IMAP or SMTP. When you create or rotate a credential, its password is shown exactly once. Save it in a password manager or secret manager. If it is lost or exposed, use **Rotate password** immediately; the previous password stops working at once. Anyone with the managed password can send mail allowed by its sender policy. Never expose it in source code, commit it to version control, or disable TLS certificate verification. Disabling or deleting a credential prevents further use. ## Next steps Create a credential and configure an email client or application Send from any SMTP client, with limits and reply codes Understand domain scopes, address scopes, sender policies, and IMAP access Edit, rotate, disable, enable, or delete managed credentials Folders, supported IMAP features, limits, and known limitations # Scopes and Permissions Source: https://inbound.new/docs/mailboxes/scopes-and-permissions Control exactly which messages a credential can read and which addresses it can send from Every managed credential combines a credential type, one or more verified-domain scopes, an IMAP access mode, and an SMTP sender policy. These settings answer two separate questions: which incoming messages are visible, and which sender addresses are allowed. ## Credential types | Type | IMAP | SMTP | Intended use | | - | - | - | - | | `mailbox` | Available, subject to `accessMode` | Available, subject to the sender policy | Email clients and applications that receive and send | | `smtp` | Not available | Available, subject to the sender policy | Applications that only send | An SMTP-only credential still requires scopes and a sender policy. Its `accessMode` is returned as `read_write`, but this does not grant IMAP access. ## Domain and address scopes Each credential must include between 1 and 100 unique scopes. Every scope references the ID of an exact domain that your account owns and that has a verified domain record. | Scope type | Example | Incoming mail included | | - | - | - | | `domain` | `*@example.com` | Addresses on `example.com` | | `address` | `support@example.com` | Only `support@example.com` | A domain scope matches the exact domain, not its subdomains. For example, `*@example.com` includes `billing@example.com`, but does not include `billing@mail.example.com`. To include `mail.example.com`, add that exact subdomain as its own verified domain record and configure a separate scope for it. Address scopes must match their referenced domain exactly. Multiple distinct scopes can be combined, including scopes from different verified domains; duplicate scopes are rejected. Domain wildcard scopes omit the reserved `dmarc@` address; add an explicit address scope if that address needs to be included. A subdomain can inherit parts of its parent domain's verification, but inheritance does not extend the parent's mailbox scope. The exact subdomain must still exist as a separate verified domain record before it can be used as a login domain, sender domain, or scope. ## How scopes appear in IMAP A `mailbox` credential presents one combined `INBOX` containing incoming messages from all its scopes, plus a read-only folder for each individual scope: ```text theme={null} INBOX Scopes/*@example.com Scopes/support@another-example.com ``` `Scopes` is a folder container, not a selectable mailbox. Domain folders use the literal `*@domain` format; address folders use the complete email address. Scope folders are always read-only, including for credentials with `read_write` access. Update flags or organize messages through `INBOX` or another writable folder instead. See [IMAP behavior](/docs/mailboxes/imap-behavior) for folder, flag, and synchronization details. ## IMAP access modes `accessMode` applies only to `mailbox` credentials: * `read`: Open folders, fetch messages, search, and receive `IDLE` updates. All folders are read-only. * `read_write`: Read messages and modify writable folders using operations such as `STORE`, `APPEND`, `COPY`, `MOVE`, and `EXPUNGE`. `Scopes/*` folders remain read-only. IMAP access mode does not control whether SMTP sending is available. A read-only mailbox credential can still send through SMTP when its sender policy permits the sender. ## SMTP sender policies The sender policy applies to SMTP and to HTTP sends authorized with the credential's password. A disallowed SMTP sender is rejected with `553`; see [Send with SMTP](/docs/mailboxes/connect-smtp#allowed-senders). ### Exact identity With `sendingMode: "identity"`, the credential can send only from its configured `sendingAddress`. ```json theme={null} { "sendingMode": "identity", "sendingName": "Support", "sendingAddress": "support@example.com" } ``` The sending address must be covered by an existing domain or address scope. `sendingName` is optional and can be `null`; it is credential metadata and does not rewrite the outgoing display name, which comes from the message itself. The message's `From` address and any nonempty SMTP envelope `MAIL FROM` address must both satisfy the exact-identity policy. An empty envelope sender is permitted, but the message still needs an authorized `From` address. ### Any scoped domain With `sendingMode: "scoped_domains"`, the credential can send from any address on any exact domain represented by its scopes: ```json theme={null} { "sendingMode": "scoped_domains", "sendingName": null, "sendingAddress": null } ``` For example, a scope for `example.com` allows `support@example.com` and `billing@example.com`, but not `support@mail.example.com`. The message's `From` address and any nonempty envelope sender must both use an allowed exact domain; an empty envelope sender is permitted. An address scope restricts incoming IMAP visibility to that address, but it does not restrict SMTP to that address when the sender policy is `scoped_domains`. A credential scoped only to `support@example.com` can still send as `billing@example.com`, `admin@example.com`, or any other address on `example.com`. Use `identity` when SMTP must be limited to one exact sender. ## Login identity is separate `loginAddress` is the username for IMAP and SMTP authentication. When a credential is created or its definition is edited, the login address must use an exact domain owned and verified by your account. It does not need to match the incoming scope or the allowed sending identity. For example, the following configuration is valid when both domains are verified: ```text theme={null} Login username: imap@operations.example Incoming scope: support@customer.example SMTP policy: identity SMTP sender: support@customer.example ``` The login username does not automatically grant access to mail sent to `imap@operations.example`, and it does not automatically authorize that address as an SMTP sender. Later IMAP and SMTP login attempts recheck both the verified scope domains and the login address's own verified domain. A domain-status change does not itself disconnect an existing IMAP session; changing or disabling the credential does. See [IMAP behavior](/docs/mailboxes/imap-behavior#scope-changes-and-revoked-access). ## Permission examples | Configuration | Read incoming mail | Modify IMAP folders | Send with SMTP | | - | - | - | - | | `mailbox`, `read`, address scope, `identity` | Only the scoped address | No | Only the exact configured sender | | `mailbox`, `read_write`, domain scope, `identity` | Addresses on the exact scoped domain | Yes, except `Scopes/*` | Only the exact configured sender | | `mailbox`, `read_write`, address scope, `scoped_domains` | Only the scoped address | Yes, except `Scopes/*` | Any address on that exact scoped domain | | `smtp`, address scope, `identity` | No IMAP access | No IMAP access | Only the exact configured sender | | `smtp`, address scope, `scoped_domains` | No IMAP access | No IMAP access | Any address on that exact scoped domain | Disabled credentials cannot authenticate, regardless of their other permissions. Create credentials, update scopes, disable access, and rotate passwords. Understand folders, read-only views, flags, limits, and synchronization. # Getting Started Source: https://inbound.new/docs/quickstart/getting-started Set up Inbound and send your first email in minutes ## 1. Get Your API Key Sign up at [inbound.new](https://inbound.new) and generate an API key from your dashboard. Create and manage your account API keys ## 2. Install the SDK Install the Inbound SDK using your preferred package manager: ```bash bun theme={null} bun add inboundemail ``` ```bash npm theme={null} npm install inboundemail ``` ```bash yarn theme={null} yarn add inboundemail ``` ```bash pnpm theme={null} pnpm add inboundemail ``` ## 3. Send Your First Email ```typescript theme={null} import { Inbound } from 'inboundemail' const inbound = new Inbound(process.env.INBOUND_API_KEY!) const { data, error } = await inbound.emails.send({ from: 'Your Name ', to: 'recipient@example.com', subject: 'Hello from Inbound!', html: '

This is my first email with Inbound.

' }) if (error) { console.error('Error:', error) } else { console.log('Email sent:', data?.id) } ``` ## Next Steps Connect an email client using managed IMAP and SMTP credentials Route incoming email to your application in real time # Introduction Source: https://inbound.new/docs/quickstart/introduction Welcome to Inbound - the simple email processing API Welcome to Inbound! We make it easy to send and receive emails programmatically. ## What is Inbound? Inbound is a simple email processing API that allows you to: * **Send emails** - Transactional emails with a Resend-compatible API * **Receive emails** - Process incoming emails via webhooks or connect with IMAP * **Reply to emails** - Full threading support for conversations * **Manage domains** - Add and verify your own domains ## Why Inbound? Clean, intuitive API design that gets out of your way First-class TypeScript support with full type safety Easy migration from Resend with compatible SDK patterns Receive and process incoming emails in real-time ## Next Steps Set up your first email integration in minutes Connect an email client using managed mailbox credentials