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 S3 URL)
  • Email classification by type (normal, transactional, marketing, notification, automated)

Content enrichment (Maker+):

  • Markdown conversion — HTML body converted to clean Markdown, useful for LLM pipelines and content extraction
  • Classification confidence and signals (e.g. has_attachment, has_unsubscribe_link)
  • 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 and verdict
  • SPF, DKIM, and DMARC authentication results
  • Matched rules with individual weights
  • Configurable threshold for automatic filtering

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"
    },
    "attachments": []
  },
  "classification": {
    "type": "normal"
  }
}

Full payload with all options

{
  "spam": {
    "score": -0.99,
    "action": "default",
    "engine": "rspamd",
    "isSpam": false,
    "symbols": [
      { "name": "DMARC_POLICY_ALLOW", "weight": -0.5, "description": "example.com,none" },
      { "name": "R_DKIM_ALLOW", "weight": -0.2, "description": "example.com:s=selector1" }
    ],
    "threshold": 0,
    "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", "text": "View it online" }
      ]
    },
    "subject": "Invoice #2026-0042 attached",
    "recipient": {
      "name": null,
      "email": "invoices@yourdomain.com"
    },
    "attachments": [
      {
        "filename": "invoice-2026-0042.pdf",
        "contentType": "application/pdf",
        "size": 245680,
        "content": "JVBERi0xLjQK...",
        "virusScan": { "status": "clean" }
      }
    ]
  },
  "integrity": {
    "contentHash": "a3f2b8c1d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1",
    "rawEmailHash": "9f8e7d6c5b4a3f2e1d0c9b8a7f6e5d4c3b2a1f0e9d8c7b6a5f4e3d2c1b0a9f8e"
  },
  "classification": {
    "type": "normal",
    "signals": ["has_attachment", "single_recipient"],
    "confidence": "definite"
  },
  "security": {
    "virusScan": {
      "scanned": true,
      "engine": "clamav",
      "engineVersion": "1.5.1",
      "attachmentsScanned": 1,
      "attachmentsSkipped": 0,
      "threatsFound": 0
    }
  }
}

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, use the timestamp field to sort emails chronologically.

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