Skip to main content

Overview

Endpoints are the unified way to handle incoming emails in Inbound. Instead of managing separate webhooks, email forwards, and distribution lists, endpoints provide a single interface for all email routing. An endpoint can be a webhook that receives HTTP POST requests, an email forward that relays messages, or an email group that distributes to multiple recipients.
Endpoints replace the legacy webhook system and provide:
  • Webhooks - Send email data to your HTTP endpoint
  • Email forwards - Forward emails to another address
  • Email groups - Distribute emails to multiple recipients

Schema Definition

From the database schema (lib/db/schema.ts:386-397):
export const endpoints = pgTable('endpoints', {
  id: varchar('id', { length: 255 }).primaryKey(),
  name: varchar('name', { length: 255 }).notNull(),
  type: varchar('type', { length: 50 }).notNull(), // 'webhook', 'email', 'email_group'
  webhookFormat: varchar('webhook_format', { length: 50 }).default('inbound'),
  config: text('config').notNull(), // JSON configuration based on type
  isActive: boolean('is_active').default(true),
  description: text('description'),
  userId: varchar('user_id', { length: 255 }).notNull(),
  createdAt: timestamp('created_at').defaultNow(),
  updatedAt: timestamp('updated_at').defaultNow(),
});

Endpoint Types

Webhook Endpoints

Webhook endpoints receive HTTP POST requests with email data:
import { Inbound } from 'inboundemail'

const inbound = new Inbound(process.env.INBOUND_API_KEY!)

// Create webhook endpoint
const endpoint = await inbound.endpoints.create({
  name: 'Support Webhook',
  type: 'webhook',
  config: {
    url: 'https://yourapp.com/api/webhook/email',
    timeout: 30,        // seconds (default: 30, max: 120)
    retryAttempts: 3,   // number of retries (default: 3, max: 10)
    headers: {          // optional custom headers
      'X-Custom-Header': 'value',
      'X-Team': 'support'
    }
  },
  description: 'Handles support emails via webhook'
})

console.log(endpoint)
// {
//   id: 'endp_xyz789',
//   name: 'Support Webhook',
//   type: 'webhook',
//   config: {
//     url: 'https://yourapp.com/api/webhook/email',
//     timeout: 30,
//     retryAttempts: 3,
//     headers: { 'X-Custom-Header': 'value' },
//     verificationToken: 'tok_abc123def456...' // auto-generated
//   },
//   isActive: true,
//   createdAt: '2024-01-15T10:30:00Z'
// }

Email Forward Endpoints

Email forward endpoints relay emails to another address:
// Create email forward endpoint
const forwardEndpoint = await inbound.endpoints.create({
  name: 'Forward to Gmail',
  type: 'email',
  config: {
    forwardTo: '[email protected]',
    preserveHeaders: true // keeps original From, To, etc.
  },
  description: 'Forwards to personal Gmail account'
})

// Emails will be forwarded with:
// - Original subject line
// - Original body (HTML + text)
// - Original attachments
// - Original headers (if preserveHeaders: true)

Email Group Endpoints

Email group endpoints distribute emails to multiple recipients:
// Create email group endpoint
const groupEndpoint = await inbound.endpoints.create({
  name: 'Team Distribution',
  type: 'email_group',
  config: {
    emails: [
      '[email protected]',
      '[email protected]',
      '[email protected]'
    ],
    preserveHeaders: true
  },
  description: 'Distributes to entire support team'
})

// Each recipient gets a copy of the email
// Maximum 50 recipients per group

Creating Endpoints

From the implementation (app/api/e2/endpoints/create.ts:94-212):

Using the SDK

import { Inbound } from 'inboundemail'

const inbound = new Inbound(process.env.INBOUND_API_KEY!)

// Webhook endpoint
const webhook = await inbound.endpoints.create({
  name: 'Production Webhook',
  type: 'webhook',
  config: {
    url: 'https://api.yourapp.com/inbound/emails',
    timeout: 45,
    retryAttempts: 5,
    headers: {
      'X-Environment': 'production',
      'X-Version': '2.0'
    }
  }
})

// Email forward endpoint
const forward = await inbound.endpoints.create({
  name: 'Fallback Forward',
  type: 'email',
  config: {
    forwardTo: '[email protected]',
    preserveHeaders: false // sends as new email from your domain
  }
})

// Email group endpoint
const group = await inbound.endpoints.create({
  name: 'Executive Team',
  type: 'email_group',
  config: {
    emails: [
      '[email protected]',
      '[email protected]',
      '[email protected]'
    ]
  }
})

Direct API Request

curl -X POST https://inbound.new/api/e2/endpoints \
  -H "Authorization: Bearer ${INBOUND_API_KEY}" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Production Webhook",
    "type": "webhook",
    "config": {
      "url": "https://api.yourapp.com/inbound/emails",
      "timeout": 30,
      "retryAttempts": 3
    },
    "description": "Main production webhook for email processing"
  }'
Webhook endpoints automatically receive a verificationToken in their config. This token is used to verify webhook requests are coming from Inbound. Store it securely!

Configuration Validation

From the implementation (app/api/e2/endpoints/validation.ts):
// Webhook config validation
{
  url: string,              // Must be https:// (or http:// for localhost)
  timeout?: number,         // 1-300 seconds
  retryAttempts?: number,   // 0-10
  headers?: Record<string, string> // Optional custom headers
}

// Email forward config validation
{
  forwardTo: string,        // Valid email address
  preserveHeaders?: boolean // Default: false
}

// Email group config validation
{
  emails: string[],         // Array of 1-50 valid email addresses
  preserveHeaders?: boolean // Default: false
}
Validation errors:
// Invalid URL
{
  error: 'Invalid configuration',
  details: 'Webhook URL must be a valid HTTPS URL'
}

// Too many recipients
{
  error: 'Invalid configuration',
  details: 'Email group can have maximum 50 recipients'
}

// Invalid email format
{
  error: 'Invalid configuration',
  details: 'Invalid email address in group: not-an-email'
}

Testing Endpoints

From the implementation (app/api/e2/endpoints/test.ts:261-645):
// Test webhook endpoint with realistic payload
const testResult = await inbound.endpoints.test('endp_xyz789', {
  webhookFormat: 'inbound', // 'inbound', 'discord', or 'slack'
  overrideUrl: 'https://webhook.site/xyz' // optional: test different URL
})

console.log(testResult)
// {
//   success: true,
//   message: 'Webhook responded successfully (200)',
//   responseTime: 245,      // milliseconds
//   statusCode: 200,
//   responseBody: 'OK',
//   webhookFormat: 'inbound',
//   urlTested: 'https://yourapp.com/api/webhook/email',
//   testPayload: {
//     event: 'email.received',
//     timestamp: '2024-01-15T10:30:00Z',
//     email: {
//       id: 'inbnd_test_abc123',
//       messageId: '<[email protected]>',
//       from: {
//         text: 'Inbound Test <[email protected]>',
//         addresses: [{ name: 'Inbound Test', address: '[email protected]' }]
//       },
//       subject: 'Test Email - Inbound Email Service',
//       // ... complete test payload
//     }
//   }
// }

Test Payload Structure

From the implementation (app/api/e2/endpoints/test.ts:66-205), test payloads are production-realistic:
// The test endpoint sends a real InboundWebhookPayload
{
  event: 'email.received',
  timestamp: '2024-01-15T10:30:00Z',
  email: {
    id: 'inbnd_test_abc123',
    messageId: '<[email protected]>',
    from: {
      text: 'Inbound Test <[email protected]>',
      addresses: [{ name: 'Inbound Test', address: '[email protected]' }]
    },
    to: {
      text: '[email protected]',
      addresses: [{ name: null, address: '[email protected]' }]
    },
    recipient: '[email protected]',
    subject: 'Test Email - Inbound Email Service',
    receivedAt: '2024-01-15T10:30:00Z',
    threadId: null,
    threadPosition: null,
    parsedData: {
      messageId: '<[email protected]>',
      date: new Date('2024-01-15T10:30:00Z'),
      subject: 'Test Email - Inbound Email Service',
      from: { /* ... */ },
      to: { /* ... */ },
      cc: null,
      bcc: null,
      replyTo: null,
      textBody: 'This is a test email.\nRendered for webhook testing.',
      htmlBody: '<div><p>This is a test email.</p><p><strong>Rendered for webhook testing.</strong></p></div>',
      raw: 'From: Inbound Test <[email protected]>\r\n...',
      attachments: [],
      headers: { /* complete header object */ },
      priority: undefined
    },
    cleanedContent: {
      html: '<div><p>This is a test email.</p><p><strong>Rendered for webhook testing.</strong></p></div>',
      text: 'This is a test email.\nRendered for webhook testing.',
      hasHtml: true,
      hasText: true,
      attachments: [],
      headers: { /* ... */ }
    }
  },
  endpoint: {
    id: 'endp_xyz789',
    name: 'Production Webhook',
    type: 'webhook'
  }
}
Test endpoint features:
  • Sends production-like webhook payloads
  • Includes verification token in headers
  • Measures response time
  • Shows HTTP status code and response body
  • Supports testing with override URL (for webhook.site, ngrok, etc.)
  • Works with webhook, email, and email_group types

Webhook vs Email vs Email Group

FeatureWebhookEmail ForwardEmail Group
PurposeProcess emails programmaticallyForward to single addressDistribute to multiple addresses
ConfigurationURL + timeout + headersEmail addressArray of email addresses
Use CasesAPI integrations, automationPersonal email, backupTeam aliases, distribution lists
ProcessingHTTP POST with JSONEmail relayEmail relay to multiple
Max RecipientsN/A150
Custom LogicYes (in your code)NoNo
Response TimeConfigurable (1-120s)N/AN/A
RetriesYes (0-10 attempts)NoNo
HeadersCustomizablePreservablePreservable
VerificationToken-basedN/AN/A

Listing Endpoints

// List all endpoints
const endpoints = await inbound.endpoints.list({
  limit: 50,
  offset: 0
})

// Filter by type
const webhooks = await inbound.endpoints.list({
  type: 'webhook'
})

// Filter by active status
const activeEndpoints = await inbound.endpoints.list({
  isActive: true
})

console.log(endpoints)
// {
//   data: [
//     {
//       id: 'endp_xyz789',
//       name: 'Production Webhook',
//       type: 'webhook',
//       config: { url: '...', timeout: 30 },
//       isActive: true,
//       deliveryStats: {
//         total: 142,
//         successful: 138,
//         failed: 4,
//         lastDelivery: '2024-01-15T10:30:00Z'
//       },
//       createdAt: '2024-01-01T00:00:00Z'
//     }
//   ],
//   pagination: { limit: 50, offset: 0, total: 12, hasMore: false }
// }

Updating Endpoints

// Update webhook URL
const updated = await inbound.endpoints.update('endp_xyz789', {
  config: {
    url: 'https://new-api.yourapp.com/webhooks/email',
    timeout: 45,
    retryAttempts: 5
  }
})

// Disable endpoint temporarily
const disabled = await inbound.endpoints.update('endp_xyz789', {
  isActive: false
})

// Update email group members
const updatedGroup = await inbound.endpoints.update('endp_abc123', {
  config: {
    emails: [
      '[email protected]',
      '[email protected]',
      '[email protected]' // added
    ]
  }
})
Updating an endpoint’s config will affect ALL email addresses that route to it. If you need different behavior, create a new endpoint instead.

Monitoring Endpoint Deliveries

// List deliveries for an endpoint
const deliveries = await inbound.endpoints.listDeliveries('endp_xyz789', {
  limit: 20,
  status: 'failed' // 'pending', 'success', 'failed'
})

for (const delivery of deliveries.data) {
  console.log(`📧 Email ${delivery.emailId}:`, {
    status: delivery.status,
    attempts: delivery.attempts,
    lastAttempt: delivery.lastAttemptAt,
    responseData: delivery.responseData
  })
}

// Example failed delivery
{
  id: 'del_def456',
  emailId: 'inbnd_abc123',
  endpointId: 'endp_xyz789',
  deliveryType: 'webhook',
  status: 'failed',
  attempts: 3,
  lastAttemptAt: '2024-01-15T10:32:00Z',
  responseData: {
    error: 'Request timeout after 30s',
    statusCode: null
  },
  createdAt: '2024-01-15T10:30:00Z'
}

Common Patterns

Multi-Environment Setup

// Development webhook
const devWebhook = await inbound.endpoints.create({
  name: 'Development Webhook',
  type: 'webhook',
  config: {
    url: 'https://dev-api.yourapp.com/webhooks/email',
    timeout: 60, // longer timeout for debugging
    headers: { 'X-Environment': 'development' }
  }
})

// Production webhook
const prodWebhook = await inbound.endpoints.create({
  name: 'Production Webhook',
  type: 'webhook',
  config: {
    url: 'https://api.yourapp.com/webhooks/email',
    timeout: 30,
    retryAttempts: 5,
    headers: { 'X-Environment': 'production' }
  }
})

// Create separate email addresses for each
await inbound.emailAddresses.create({
  address: '[email protected]',
  domainId: domain.id,
  endpointId: devWebhook.id
})

await inbound.emailAddresses.create({
  address: '[email protected]',
  domainId: domain.id,
  endpointId: prodWebhook.id
})

Hybrid Webhook + Forward

// Create webhook for processing
const webhook = await inbound.endpoints.create({
  name: 'Processing Webhook',
  type: 'webhook',
  config: { url: 'https://api.yourapp.com/emails' }
})

// Create forward for backup
const forward = await inbound.endpoints.create({
  name: 'Backup Forward',
  type: 'email',
  config: { forwardTo: '[email protected]' }
})

// Route primary address to webhook
await inbound.emailAddresses.create({
  address: '[email protected]',
  domainId: domain.id,
  endpointId: webhook.id
})

// Route backup address to forward
await inbound.emailAddresses.create({
  address: '[email protected]',
  domainId: domain.id,
  endpointId: forward.id
})

Testing with Override URL

// Test webhook with webhook.site
const test1 = await inbound.endpoints.test('endp_xyz789', {
  overrideUrl: 'https://webhook.site/abc123'
})

// Test with ngrok (local development)
const test2 = await inbound.endpoints.test('endp_xyz789', {
  overrideUrl: 'https://abc123.ngrok.io/api/webhook'
})

// Test with different webhook format
const test3 = await inbound.endpoints.test('endp_xyz789', {
  webhookFormat: 'slack' // Sends Slack-compatible payload
})

Best Practices

Endpoint Organization
  • Use descriptive names that indicate purpose (“Production Webhook”, “Team Distribution”)
  • Create separate endpoints for different environments (dev, staging, prod)
  • Use endpoint descriptions to document integration details
  • Monitor delivery stats to detect issues early
  • Test endpoints before routing production traffic
Webhook Configuration
  • Always use HTTPS URLs (never HTTP in production)
  • Set appropriate timeouts (30s default, up to 120s for heavy processing)
  • Configure retry attempts based on your reliability needs
  • Use custom headers for environment/version tracking
  • Store verification tokens securely (environment variables)
Performance Tips
  • Return 200 OK quickly from webhooks (< 1s ideal)
  • Use background jobs for heavy processing
  • Configure longer timeouts only when necessary
  • Monitor endpoint deliveries for patterns
  • Use the test feature to verify changes before deploying

Next Steps

Build docs developers (and LLMs) love