Inbound Email

Receive incoming emails on your verified domains, match them against rules, and process them via webhooks or store them for later retrieval.

How It Works

Veil Mail's inbound email system allows you to receive emails on your verified domains. When an email arrives, it is matched against your inbound rules and processed according to the configured action.

1
Configure MX records - Point your domain's MX record to inbound.veilmail.xyz
2
Create inbound rules - Define which addresses to listen for and what action to take
3
Process emails - Emails are forwarded to your webhook, stored for API retrieval, or both

MX Record Setup

To receive inbound emails, add an MX record to your domain's DNS configuration. The domain must already be verified in Veil Mail for sending.

TypeHostPriorityValue
MXyourdomain.com10inbound.veilmail.xyz

Existing MX Records

If your domain already has MX records for regular email (e.g., Google Workspace, Microsoft 365), adding the Veil Mail MX record will route all email through Veil Mail. Consider using a subdomain like inbound.yourdomain.com instead.

Create an Inbound Rule

An inbound rule defines which email addresses to match and what to do when an email is received. The domain must be verified before creating rules.

POST /v1/inbound/rules
create-rule.ts
const rule = await client.inbound.createRule({
  domainId: 'domain_xxxxx',
  matchPattern: 'support@',
  forwardTo: 'https://example.com/webhook/inbound',
  action: 'store_and_webhook',
});

console.log(rule.mxRecord);
// { type: 'MX', name: 'yourdomain.com', value: 'inbound.veilmail.xyz', priority: 10 }

Match Patterns

The matchPattern field determines which incoming email addresses trigger this rule. Rules are evaluated from most specific to least specific.

PatternExample MatchDescription
*anything@yourdomain.comCatch-all, matches any address on the domain
support@support@yourdomain.comMatches the local part prefix on the domain
orders@store.example.comorders@store.example.comExact email address match

Specific patterns (prefix and exact) are evaluated before wildcard patterns. If multiple specific patterns match, the oldest rule wins.

Actions

Each rule specifies an action that determines how matched emails are processed.

ActionDescriptionRequires forwardTo
webhookForward the parsed email to your webhook URL immediatelyYes
storeStore the email for later retrieval via the APINo
store_and_webhookStore the email and forward it to your webhookYes

Webhook Payload Format

When an inbound rule with a webhook or store_and_webhook action matches, the parsed email is POSTed to your forwardTo URL as JSON:

webhook-payload.json
{
  "id": "clxxxx...",
  "ruleId": "clxxxx...",
  "from": "sender@example.com",
  "to": "support@yourdomain.com",
  "subject": "Help with my order",
  "text": "Hi, I need help with order #12345...",
  "html": "<p>Hi, I need help with order #12345...</p>",
  "headers": {
    "message-id": "<abc@example.com>",
    "date": "Fri, 31 Jan 2026 10:00:00 +0000"
  },
  "attachments": [
    {
      "filename": "screenshot.png",
      "contentType": "image/png",
      "size": 45230
    }
  ],
  "spamScore": 0.1,
  "receivedAt": "2026-01-31T10:00:05Z"
}

Your endpoint should return a 2xx status code to acknowledge receipt. If the webhook delivery fails, the email status is set to failed (when stored).

Webhook Events

In addition to forwarding the email to your rule's forwardTo URL, Veil Mail also fires an email.received webhook event through the standard webhook system. Subscribe to this event in your webhook configuration.

subscribe-event.ts
// Subscribe to inbound events
const webhook = await client.webhooks.create({
  url: 'https://example.com/webhooks/veilmail',
  events: ['email.received'],
});

List Rules

GET /v1/inbound/rules
list-rules.ts
const { data } = await client.inbound.listRules();

for (const rule of data) {
  console.log(`${rule.matchPattern} on ${rule.domain} -> ${rule.action}`);
}

Update Rule

PATCH /v1/inbound/rules/:id
update-rule.ts
// Disable a rule
const rule = await client.inbound.updateRule('rule_xxxxx', {
  enabled: false,
});

// Change to catch-all and store only
const updated = await client.inbound.updateRule('rule_xxxxx', {
  matchPattern: '*',
  action: 'store',
});

Delete Rule

DELETE /v1/inbound/rules/:id
delete-rule.ts
await client.inbound.deleteRule('rule_xxxxx');

Retrieve Stored Emails

When a rule's action is store or store_and_webhook, received emails are stored and can be retrieved via the API. The list endpoint returns email summaries; use the get endpoint to fetch full body and headers.

List Emails

GET /v1/inbound/emails
list-emails.ts
// List all received emails
const { data, hasMore, nextCursor } = await client.inbound.listEmails({
  limit: 50,
});

// Filter by rule, sender, or status
const filtered = await client.inbound.listEmails({
  ruleId: 'rule_xxxxx',
  from: 'sender@example.com',
  status: 'received',
});

Get Email

GET /v1/inbound/emails/:id
get-email.ts
const email = await client.inbound.getEmail('inbound_xxxxx');

console.log(email.from);      // "sender@example.com"
console.log(email.subject);   // "Help with my order"
console.log(email.textBody);  // Plain text body
console.log(email.htmlBody);  // HTML body
console.log(email.headers);   // Full email headers
console.log(email.attachments); // Attachment metadata

Delete Email

DELETE /v1/inbound/emails/:id
delete-email.ts
await client.inbound.deleteEmail('inbound_xxxxx');

Email Status

StatusDescription
receivedEmail received and stored successfully
forwardedEmail forwarded to the webhook URL successfully
failedWebhook delivery failed (email is still stored if action includes store)

Complete Example

Here is a complete example of setting up inbound email processing with a catch-all rule and a specific address rule:

complete-example.ts
import { VeilMail } from '@veilmail/sdk';

const client = new VeilMail('veil_live_xxxxx');

// Step 1: Create a catch-all rule to store all inbound emails
const catchAll = await client.inbound.createRule({
  domainId: 'domain_xxxxx',
  matchPattern: '*',
  action: 'store',
});

// Step 2: Create a specific rule for support emails
const supportRule = await client.inbound.createRule({
  domainId: 'domain_xxxxx',
  matchPattern: 'support@',
  forwardTo: 'https://api.example.com/hooks/support-ticket',
  action: 'store_and_webhook',
});

// Step 3: Add the MX record shown in the response to your DNS
console.log('Add this MX record:');
console.log(supportRule.mxRecord);

// Step 4: Later, retrieve stored emails
const { data: emails } = await client.inbound.listEmails({
  ruleId: supportRule.id,
  limit: 10,
});

for (const email of emails) {
  const full = await client.inbound.getEmail(email.id);
  console.log(`From: ${full.from}`);
  console.log(`Subject: ${full.subject}`);
  console.log(`Body: ${full.textBody}`);
}

REST API Example

You can also use the REST API directly with any HTTP client:

rest-api.sh
# Create an inbound rule
curl -X POST https://api.veilmail.xyz/v1/inbound/rules \
  -H "Authorization: Bearer veil_live_xxxxx" \
  -H "Content-Type: application/json" \
  -d '{
    "domainId": "domain_xxxxx",
    "matchPattern": "*",
    "forwardTo": "https://example.com/webhook/inbound",
    "action": "store_and_webhook"
  }'

# List received emails
curl https://api.veilmail.xyz/v1/inbound/emails?limit=20 \
  -H "Authorization: Bearer veil_live_xxxxx"

# Get a specific email
curl https://api.veilmail.xyz/v1/inbound/emails/clxxxx \
  -H "Authorization: Bearer veil_live_xxxxx"