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.
inbound.veilmail.xyzMX 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.
| Type | Host | Priority | Value |
|---|---|---|---|
| MX | yourdomain.com | 10 | inbound.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/rulesconst 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.
| Pattern | Example Match | Description |
|---|---|---|
| * | anything@yourdomain.com | Catch-all, matches any address on the domain |
| support@ | support@yourdomain.com | Matches the local part prefix on the domain |
| orders@store.example.com | orders@store.example.com | Exact 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.
| Action | Description | Requires forwardTo |
|---|---|---|
webhook | Forward the parsed email to your webhook URL immediately | Yes |
store | Store the email for later retrieval via the API | No |
store_and_webhook | Store the email and forward it to your webhook | Yes |
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:
{
"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 to inbound events
const webhook = await client.webhooks.create({
url: 'https://example.com/webhooks/veilmail',
events: ['email.received'],
});List Rules
GET /v1/inbound/rulesconst { 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// 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/:idawait 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 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/:idconst 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 metadataDelete Email
DELETE /v1/inbound/emails/:idawait client.inbound.deleteEmail('inbound_xxxxx');Email Status
| Status | Description |
|---|---|
received | Email received and stored successfully |
forwarded | Email forwarded to the webhook URL successfully |
failed | Webhook 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:
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:
# 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"