SDP Email SDP EmailDocs

Outgoing Webhooks

Outgoing webhooks push incoming email into your own systems. Whenever an email arrives in one of your mailboxes, SDP Email sends an HTTP POST with the full email as JSON — including download links for any attachments — to every notify URL you configure.

Typical uses: feeding a helpdesk or CRM, triggering automations, or archiving mail in your own storage.

Add a Notify URL

  1. Open Settings → Outgoing webhooks in the admin dashboard.
  2. Click Add notify URL.
  3. Enter the URL that should receive the POST. It must be reachable from the internet over HTTPS.
  4. Optionally set custom headers, a secret, and a recipient filter (explained below).
  5. Save. The webhook is active immediately.

You can add as many notify URLs as you need — each incoming email is delivered to every active URL whose filter matches.

Filtering Which Emails Notify a URL

Each notify URL has an optional filter — a list of entries the incoming email's recipient is matched against:

Entry form Example Matches
Full mailbox address [email protected] Only mail delivered to that exact mailbox
Bare domain acme.com Mail delivered to any mailbox under that domain

Rules:

  • An empty filter means the URL is notified for every incoming email in your workspace.
  • Entries are case-insensitive.
  • A URL is notified once per email when any of its entries matches.

Verifying Requests with a Secret

If you set a secret on a notify URL, SDP Email sends it with every request as a header:

X-Webhook-Secret: your-secret-value

Your endpoint should compare the header against the value you configured and reject requests that don't match. This prevents anyone who discovers your endpoint URL from feeding it forged emails.

The secret is stored encrypted and is never shown again in the dashboard after you save it — you can only replace or remove it.

Custom Headers

Add header name/value pairs to each notify URL for endpoints that require authentication or other metadata. For example:

Header name Value
Authorization Bearer your-token
X-API-Key your-api-key

Enter the complete value, including any authentication scheme such as Bearer or Basic. You can configure up to 20 headers. Names must be valid HTTP header names and unique regardless of case; values must be non-empty, single-line strings up to 8,192 characters.

Values are stored encrypted and loaded only when an administrator opens the edit form. Saved values appear in password inputs; click Show to reveal a value or Hide to mask it again. Enter a new value to replace it, leave a saved value blank to keep it, or remove its row to delete the header. Lists and request history keep values masked. Headers are sent alongside the optional X-Webhook-Secret. Transport headers, Content-Type, and X-Webhook-Secret are managed automatically and cannot be supplied as custom headers.

Use the final destination URL: redirects are not followed, so credentials are not forwarded to another endpoint.

Request Format

  • Method: POST
  • Content type: application/json
  • Headers: Your configured custom headers, plus X-Webhook-Secret (only when a secret is set)

Payload

{
    "id": 481,
    "message_id": "[email protected]",
    "thread_id": 466,
    "recipient": "[email protected]",
    "from_name": "Jane Doe",
    "from_email": "[email protected]",
    "to_recipients": [{ "name": "Support", "email": "[email protected]" }],
    "cc_recipients": [],
    "subject": "Order #1042 arrived damaged",
    "text_body": "Hi team, the package arrived damaged...",
    "html_body": "<p>Hi team, the package arrived damaged...</p>",
    "snippet": "Hi team, the package arrived damaged...",
    "has_attachments": true,
    "received_at": "2026-07-16T09:24:11+03:00",
    "attachments": [
        {
            "filename": "damage-photo.jpg",
            "content_type": "image/jpeg",
            "size": 482113,
            "url": "https://…signed-download-url…"
        }
    ]
}

Field Reference

Field Type Description
id integer SDP Email's ID for the stored email.
message_id string | null The email's Message-ID header, without angle brackets.
thread_id integer ID of the conversation thread the email belongs to.
recipient string The mailbox address the email was delivered to. This is the address the filter matched.
from_name string | null Sender display name.
from_email string | null Sender address.
to_recipients array To header addresses as { name, email } objects.
cc_recipients array Cc header addresses as { name, email } objects.
subject string | null Email subject.
text_body string | null Plain-text body.
html_body string | null HTML body.
snippet string | null Short plain-text preview of the body.
has_attachments boolean Whether the email carried attachments.
received_at string | null When the email was received, ISO-8601.
attachments array Attachment descriptors — always present, empty when the email has none.

Attachments

Each entry in attachments:

Field Type Description
filename string | null Original file name.
content_type string | null MIME type.
size integer | null Size in bytes.
url string Signed download URL, valid for 24 hours.

Attachment contents are never embedded in the payload — download them from url. Because the link expires after 24 hours, fetch and store the file promptly if you need to keep it. In the rare case an attachment's file could not be stored when the email arrived, that attachment is omitted from the array (the email itself is still delivered).

Responses, Timeouts, and Retries

  • Your endpoint must respond within 15 seconds.
  • Any 2xx status counts as delivered. Keep processing fast — acknowledge first, do heavy work asynchronously.
  • Any other status, or a timeout, counts as a failure. Delivery is retried up to 3 attempts in total, waiting 30 seconds, 2 minutes, and 5 minutes between attempts. After the final failure the notification is dropped (the email itself is always kept in the mailbox).
  • Deliveries can occasionally arrive more than once — use id or message_id to deduplicate.

Example Receiver

A minimal Laravel route that verifies the secret and reads the payload:

Route::post('/webhooks/incoming-email', function (Request $request) {
    abort_unless(
        hash_equals(config('services.email.webhook_secret'), (string) $request->header('X-Webhook-Secret')),
        401
    );

    $email = $request->json()->all();

    Log::info('Incoming email', [
        'from' => $email['from_email'],
        'subject' => $email['subject'],
        'attachments' => count($email['attachments']),
    ]);

    return response()->noContent();
});

The same in Express:

app.post('/webhooks/incoming-email', express.json(), (req, res) => {
    if (req.get('X-Webhook-Secret') !== process.env.EMAIL_WEBHOOK_SECRET) {
        return res.sendStatus(401);
    }

    const email = req.body;
    console.log(`Incoming email from ${email.from_email}: ${email.subject}`);

    res.sendStatus(204);
});

Request History and Testing

Click Details beside a notify URL to open its details popup. It shows the saved configuration and the latest 100 request attempts, including normal deliveries, retries, and manual tests. History begins when this feature is installed; older requests cannot be recovered.

Select a request to inspect its JSON payload, masked request headers, HTTP status, duration, response headers, and response body. Payloads and response bodies are encrypted at rest. Response previews are limited to 16 KB, and saved header values are redacted from history. Each retry creates a separate entry. Removing a webhook also removes its history.

Click Send test request to send a sample incoming email payload to the saved URL with its saved headers. This performs a real HTTP POST and can trigger processing at your endpoint. The payload uses sample example.test addresses, ID 0, no attachments, and a unique test message ID. It creates no email in your mailbox. Tests work for disabled webhooks and ignore recipient filters; they do not enable normal deliveries. A test makes one attempt and shows the result immediately, including HTTP errors or connection failures.

Managing Notify URLs

From Settings → Outgoing webhooks, open Details for a notify URL to:

  • Edit a URL, its filter, or its active state at any time.
  • Add, replace, or remove custom headers.
  • Replace or remove the secret (leave the field blank while editing to keep the current one).
  • Disable a URL without deleting it — deliveries stop immediately and resume when re-enabled.
  • Delete a URL — deliveries stop immediately.