Email processing

EmailConnect transforms incoming emails into structured webhook payloads. This guide explains the processing pipeline from email receipt to webhook delivery.

Processing pipeline

1. Email reception

When an email arrives at your configured domain:

  1. SMTP connection - Sender's mail server connects to mx1.emailconnect.eu
  2. Spam & virus check - Email is scanned for spam. On Business+ plans, attachments are also scanned for viruses using ClamAV
  3. Rule evaluation - Alias rules are checked (Maker+ feature)
  4. Acceptance - Email is accepted for processing

2. Parsing and extraction

EmailConnect extracts structured data from the raw email and enriches it based on your plan.

Message parsing (all plans):

  • Sender and recipient addresses, display names
  • Subject line, date, and timestamps
  • HTML and plain text body
  • Link extraction from both HTML and plain text
  • Attachments with filenames, MIME types, and sizes (Base64 inline, or a downloadUrl for offloaded files)
  • Email classification — classification.type is one of bounce, auto-reply, mailing-list, read-receipt, or normal. It is derived from RFC headers, not from content, so it is a fact about the message rather than a guess. normal means none of the automated shapes matched
  • Classification confidence (definite or likely) and signals — the exact headers behind the verdict, as { header, value } pairs, so you can audit the call instead of trusting it

Content enrichment (Maker+):

  • Markdown conversion — HTML body converted to clean Markdown, useful for LLM pipelines and content extraction. Requires the includeMarkdown alias setting, which is off by default
  • Link anchorText and source — Free plans receive url and domain only
  • Integrity hashes — SHA-256 hashes of both the processed content and the raw email for audit trails and deduplication

Spam analysis (Maker+):

  • rspamd composite score — you compare it against your own cut-off; we don't ship a verdict
  • SPF, DKIM, and DMARC authentication results
  • Matched rules with individual weights
  • Score-based alias rules to accept, block, or tag mail before delivery

Security scanning (Business+):

  • ClamAV virus scanning on all attachments
  • Infected files are excluded from delivery with excludeReason: "virus-detected"
  • Per-attachment and summary-level scan results

3. Webhook delivery

The structured payload is delivered to your configured endpoint:

  1. Payload construction - JSON payload is built
  2. Signing - Request is signed for verification
  3. Delivery - POST request sent to your webhook URL
  4. Retry handling - Failed deliveries are retried

Webhook payload structure

Looking for the full field-by-field reference? See What's in your webhook payload for every field, enrichment feature, and plan availability.

Basic payload

{
  "message": {
    "date": "2026-02-12T10:30:00.000Z",
    "sender": {
      "name": "Alice Martin",
      "email": "alice@example.com"
    },
    "content": {
      "text": "Hi there!\n\nJust wanted to follow up on our conversation.\n\nBest regards,\nAlice",
      "html": "<p>Hi there!</p><p>Just wanted to follow up on our conversation.</p><p>Best regards,<br>Alice</p>",
      "links": []
    },
    "subject": "Following up",
    "recipient": {
      "name": null,
      "email": "support@yourdomain.com",
      "tag": null
    },
    "attachments": []
  },
  "classification": {
    "type": "normal"
  }
}

Full payload with all options

{
  "spam": {
    "score": -0.99,
    "engine": "rspamd",
    "symbols": [
      { "name": "DMARC_POLICY_ALLOW", "weight": -0.5, "description": "example.com,none" },
      { "name": "R_DKIM_ALLOW", "weight": -0.2, "description": "example.com:s=selector1" }
    ],
    "authentication": {
      "spf": { "result": "pass" },
      "dkim": { "result": "pass" },
      "dmarc": { "result": "pass" }
    }
  },
  "message": {
    "date": "2026-02-12T10:30:00.000Z",
    "sender": {
      "name": "Alice Martin",
      "email": "alice@example.com"
    },
    "content": {
      "text": "Hi team,\n\nPlease find invoice #2026-0042 attached.\n\nBest,\nAlice",
      "html": "<p>Hi team,</p><p>Please find invoice #2026-0042 attached.</p><p>Best,<br>Alice</p>",
      "markdown": "Hi team,\n\nPlease find invoice #2026-0042 attached.\n\nBest,\nAlice",
      "links": [
        {
          "url": "https://billing.example.com/inv/2026-0042",
          "domain": "billing.example.com",
          "anchorText": "View it online",
          "source": "html"
        }
      ]
    },
    "subject": "Invoice #2026-0042 attached",
    "recipient": {
      "name": null,
      "email": "invoices+acme@yourdomain.com",
      "tag": "acme"
    },
    "attachments": [
      {
        "filename": "invoice-2026-0042.pdf",
        "contentType": "application/pdf",
        "size": 245680,
        "content": "JVBERi0xLjQK...",
        "virusScan": { "status": "clean" }
      }
    ]
  },
  "integrity": {
    "contentHash": "a3f2b8c1d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1",
    "rawEmailHash": "9f8e7d6c5b4a3f2e1d0c9b8a7f6e5d4c3b2a1f0e9d8c7b6a5f4e3d2c1b0a9f8e"
  },
  "classification": {
    "type": "normal",
    "confidence": "definite",
    "signals": [
      { "header": "return-path", "value": "alice@example.com" }
    ]
  },
  "security": {
    "virusScan": {
      "scanned": true,
      "engine": "clamav",
      "engineVersion": "1.5.1",
      "attachmentsScanned": 1,
      "attachmentsSkipped": 0,
      "threatsFound": 0
    }
  }
}

This example was addressed to invoices+acme@yourdomain.com, so recipient.tag is "acme" — anything after the + is handed to you pre-parsed, and the mail is still delivered to the webhook on your invoices@ alias. Untagged mail sets tag to null. See the webhook payload reference for the full rules.

A few things worth calling out in that example:

  • Links carry anchorText, not text. Each entry is { url, domain, anchorText?, source? }. url and domain are on every plan; anchorText and source are Maker+.
  • classification.signals is a list of objects, not strings — each is { header, value } naming the real RFC header that produced the verdict.
  • The envelope block is not shown above because it is off by default. Unless the alias enables "include envelope", the envelope is reduced to { messageId }. Turn it on to receive headers, allRecipients, returnPath, and processed.

See the webhook payload reference for every field and its plan.

Delivery and retries

Successful delivery

A delivery is considered successful when your endpoint returns:

  • HTTP 200-299 status code
  • Response within timeout period (30 seconds)

Retry policy

If delivery fails, EmailConnect retries with exponential backoff (±10% jitter):

Attempt Delay
1 Immediate
2 ~1 minute
3 ~5 minutes
4 ~30 minutes
5 ~2 hours
6 ~12 hours

Actual delays vary by ±10% from the listed values to prevent thundering-herd effects.

Retries are capped by your data retention policy. If the next retry would fall after your retention window expires, the email is marked as failed instead. For example, Free and Maker plans (1-hour retention) get up to 4 attempts — attempt 5 would be at +2h 36m, past the retention window. Business and Platform plans with longer retention (up to 30 or 365 days) can use all 6 attempts.

After 6 failed attempts or retention expiry (whichever comes first), the email is marked as failed and no further retries occur.

Failure reasons

Common delivery failures:

  • Endpoint not reachable (network error)
  • Endpoint returns 4xx/5xx error
  • Timeout (response takes > 30 seconds)
  • SSL/TLS certificate errors
  • Payload too large for endpoint

Webhook security

Request signing

EmailConnect signs webhook requests following the Standard Webhooks convention. Each request includes three headers:

webhook-id: msg_2KWPBgLl...
webhook-timestamp: 1716300000
webhook-signature: v1,K5oZfzN95Z9UVu1EsfQmfVNQhnkZ2pj9o9NDN/H/pI4=

The signed content is {webhook-id}.{webhook-timestamp}.{body}, signed with HMAC-SHA256 using your base64-decoded secret.

Node.js

const crypto = require('crypto');

function verifyWebhook(headers, rawBody, secret) {
  const signedContent = `${headers['webhook-id']}.${headers['webhook-timestamp']}.${rawBody}`;
  const secretBytes = Buffer.from(secret.replace(/^whsec_/, ''), 'base64');
  const expected = crypto.createHmac('sha256', secretBytes).update(signedContent).digest('base64');

  return headers['webhook-signature'].split(' ').some(sig => {
    const [v, val] = sig.split(',');
    return v === 'v1' && crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(val));
  });
}

Python

import hmac, hashlib, base64

def verify_webhook(headers, raw_body, secret):
    signed_content = f"{headers['webhook-id']}.{headers['webhook-timestamp']}.{raw_body}"
    secret_bytes = base64.b64decode(secret.removeprefix('whsec_'))
    expected = base64.b64encode(
        hmac.new(secret_bytes, signed_content.encode(), hashlib.sha256).digest()
    ).decode()

    return any(
        v == 'v1' and hmac.compare_digest(expected, val)
        for sig in headers['webhook-signature'].split(' ')
        for v, _, val in [sig.partition(',')]
    )

For full verification examples including timestamp checks, see the webhook signing guide.

Processing order

Emails are processed in order of receipt. However, webhook delivery may complete out of order due to:

  • Retry delays for failed deliveries
  • Processing time variations
  • Network latency differences

If order matters for your application, sort on a timestamp from the payload. There is no top-level timestamp field — the two you can use are:

  • message.date — when the sender's mail client says the email was sent. Present on every payload. Sender-supplied, so it can be skewed or absent.
  • envelope.processed.timestamp — when EmailConnect processed the message. This is our clock, so it is the reliable one for ordering — but it only reaches you if the alias has "include envelope" enabled, which is off by default. With the envelope off, the block is reduced to { messageId }.

For deduplication rather than ordering, use envelope.messageId — it is stable across redeliveries.

Rate limits

Plan API requests per hour Effective per minute
Free 60 1
Maker 600 10
Business 3,000 50
Platform Unlimited 10,000 cap

Emails exceeding rate limits are queued and processed when capacity is available.

Monitoring and debugging

Email logs

View processed emails in your dashboard:

  • Delivery status (success/failed/pending)
  • Response codes from your endpoint
  • Retry history
  • Full payload preview

Webhook testing

EmailConnect integrates directly with WebhookTest.eu — click "Use WebhookTest" when creating an alias and you can inspect the full payload without leaving your dashboard. No external tools or tab-switching needed.

See Testing webhooks with WebhookTest for detailed setup instructions.

Related topics