Inbound Email
Receive and process inbound emails via webhook forwarding.
Sendry can receive inbound emails and forward them to a webhook endpoint in your application. This lets you build reply-handling, support ticket creation, or any workflow that processes incoming email.
Every organization gets a shared inbound address automatically — no MX record or DNS changes required. Pro and higher plans can also receive inbound email on a custom verified domain.
How it works
- Emails sent to your inbound address arrive at Sendry's mail servers
- Sendry parses the email and POSTs a JSON payload to your configured webhook URL
- Retrieve received emails via the API for up to 30 days
For shared-domain inbound, your address is {slug}@recv.sendry.online — visible in your inbound config. For custom-domain inbound, you configure an MX record on your own domain (see Custom domain setup).
Inbound webhook payload
When an email arrives, Sendry sends a POST request to your configured URL with this JSON body:
{
"id": "inb_abc123",
"from": "sender@example.com",
"to": ["support@acme.com"],
"cc": [],
"subject": "Question about my order",
"text": "Hi, I have a question...",
"html": "<p>Hi, I have a question...</p>",
"headers": {
"message-id": "<abc123@example.com>",
"reply-to": "sender@example.com"
},
"attachments": [
{
"filename": "screenshot.png",
"contentType": "image/png",
"size": 45312,
"contentId": null
}
],
"webhook_delivered": true,
"created_at": "2025-03-12T09:00:00Z"
}
The payload is signed with the same HMAC-SHA256 mechanism as outbound webhooks. See Webhook Verification for verification examples.
Get inbound config
GET /v1/inbound/config
Requires read_only scope. Returns the current inbound webhook configuration and your inbound address.
Response
{
"inbound_address": "acme@recv.sendry.online",
"url": "https://api.acme.com/webhooks/inbound",
"secret": "whsec_abc123xyz..."
}
| Field | Description |
|---|---|
inbound_address | Your shared inbound email address. Emails sent here are forwarded to your webhook URL. |
url | Webhook URL for inbound forwarding. null if not configured. |
secret | HMAC signing secret for verifying payloads. |
Update inbound config
PUT /v1/inbound/config
Requires full_access scope. Set or update the webhook URL for inbound email forwarding.
Request body
| Field | Type | Required | Description |
|---|---|---|---|
url | string | null | Yes | Webhook URL to forward inbound emails to. Set to null to disable forwarding. |
secret | string | null | No | HMAC signing secret. Set to null to auto-generate a new secret. |
Example request
curl -X PUT https://api.sendry.online/v1/inbound/config \
-H "Authorization: Bearer $SENDRY_API_KEY" \
-H "Content-Type: application/json" \
-d '{"url": "https://api.acme.com/webhooks/inbound", "secret": null}'
Response
{
"inbound_address": "acme@recv.sendry.online",
"url": "https://api.acme.com/webhooks/inbound",
"secret": "whsec_newly_generated_secret..."
}
Save the secret. You will need it to verify inbound webhook signatures. If lost, update the config with "secret": null to regenerate.
List inbound emails
GET /v1/inbound
Requires read_only scope. Lists received emails in reverse chronological order.
Query parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
limit | number | 50 | Results per page (1–100). |
cursor | string | Pagination cursor from previous response's next_cursor. |
Response
{
"data": [
{
"id": "inb_abc123",
"from": "sender@example.com",
"to": ["support@acme.com"],
"cc": [],
"subject": "Question about my order",
"text": "Hi, I have a question...",
"html": "<p>Hi, I have a question...</p>",
"headers": {"message-id": "<abc123@example.com>"},
"attachments": [],
"webhook_delivered": true,
"created_at": "2025-03-12T09:00:00Z"
}
],
"has_more": false,
"next_cursor": null
}
Get inbound email
GET /v1/inbound/:id
Requires read_only scope. Returns a single inbound email by ID.
Example
curl https://api.sendry.online/v1/inbound/inb_abc123 \
-H "Authorization: Bearer $SENDRY_API_KEY"
Custom domain inbound (Pro+)
Pro and higher plans can receive inbound email directly on a verified domain (e.g., support@acme.com) instead of the shared recv.sendry.online address. This requires adding an MX record to your domain.
Add an MX record pointing to Sendry's inbound servers. Contact support@sendry.online or check the dashboard under Domains → Inbound for the MX hostname and priority specific to your account.
Example:
| Type | Host | Value | Priority |
|---|---|---|---|
| MX | acme.com | inbound.sendry.online | 10 |
DNS propagation can take up to 48 hours.
Once the MX record is verified, emails sent to any address on that domain will be routed to your inbound webhook.
Handling inbound webhooks
import express from "express";
import { createHmac, timingSafeEqual } from "node:crypto";
const app = express();
app.post(
"/webhooks/inbound",
express.raw({ type: "application/json" }),
(req, res) => {
const rawBody = req.body.toString("utf-8");
const signature = req.headers["sendry-signature"] as string;
const secret = process.env.SENDRY_INBOUND_SECRET!;
// Verify signature
const hmac = createHmac("sha256", secret);
hmac.update(rawBody);
const expected = `sha256=${hmac.digest("hex")}`;
const a = Buffer.from(expected);
const b = Buffer.from(signature);
if (a.length !== b.length || !timingSafeEqual(a, b)) {
return res.status(403).json({ error: "Invalid signature" });
}
const email = JSON.parse(rawBody);
console.log(`Received email from ${email.from}: ${email.subject}`);
console.log(`Attachments: ${email.attachments.length}`);
// Process the email — create support ticket, trigger workflow, etc.
// Do heavy processing asynchronously; return 200 quickly.
res.status(200).json({ ok: true });
}
);
Limitations
- Inbound emails are stored for 30 days and then automatically deleted
- Maximum inbound message size: 10 MB
- Attachments are included as metadata (filename, content type, size) but attachment content is not included in the webhook payload. If you need attachment content, retrieve the full email via
GET /v1/inbound/:id - Shared-domain inbound (
slug@recv.sendry.online) is available on all plans - Custom-domain inbound (your own MX record) requires a Pro plan or higher