How to Process Email Headers

Every email header — standard and custom — is available in the JsonHook JSON payload. Extract routing metadata, authentication results, and custom application headers with a simple key lookup.

Table of Contents
  1. Overview
  2. Prerequisites
  3. Step-by-Step Instructions
  4. Code Example
  5. Common Pitfalls

Overview

Email headers carry rich metadata beyond the visible From, To, and Subject fields. They include:

  • Authentication results: SPF pass/fail, DKIM signature status, DMARC policy outcomes
  • Routing metadata: Received chain showing the path the message took across mail servers
  • Custom application headers: X-Order-Id, X-Customer-Id, X-Priority, X-Mailer — set by automated sending systems
  • Content metadata: Content-Type, MIME-Version, Content-Transfer-Encoding
  • List management headers: List-Unsubscribe, List-Id used by mailing lists

JsonHook exposes all headers in the email.headers object as a lowercase-key map, alongside the promoted top-level fields (from, to, subject, date). This makes header inspection a simple property lookup rather than a raw string parse.

Prerequisites

No special prerequisites. All headers are included in every JsonHook delivery payload on all plans. To explore the headers available for your specific email sources:

  • Send a few representative emails to a test JsonHook address pointed at webhook.site or your own logging endpoint
  • Inspect the email.headers object in the delivered payload
  • Identify the custom headers your sending systems include (e.g., x-order-id, x-shopify-event)

Access Every Email Header as JSON

All headers available in every payload. Start free — no credit card needed.

Get Free API Key

Step-by-Step Instructions

Extract and use email headers in your webhook handler:

  1. Access the headers object:
    const headers = payload.email.headers;
    // All keys are lowercase: "x-order-id", "authentication-results", etc.
  2. Read a specific header:
    const orderId = headers["x-order-id"];
    const authResults = headers["authentication-results"];
    const priority = headers["x-priority"];
  3. Parse authentication results to verify SPF and DKIM:
    const auth = headers["authentication-results"] ?? "";
    const spfPass = auth.includes("spf=pass");
    const dkimPass = auth.includes("dkim=pass");
    if (!spfPass || !dkimPass) {
      console.warn("Email failed authentication — sender may be spoofed");
    }
  4. Use custom headers for reliable routing — they are more stable than subject-line parsing:
    const eventType = headers["x-shopify-event-type"];
    if (eventType === "order.created") { await handleNewOrder(payload); }
  5. Handle missing headers gracefully with optional chaining and defaults:
    const mailer = headers["x-mailer"] ?? "unknown";

Code Example

A handler that uses custom headers for routing and authentication results for security validation:

interface EmailHeaders {
  [key: string]: string;
}

function validateSenderAuthentication(headers: EmailHeaders): boolean {
  const authResults = headers["authentication-results"] ?? "";
  // Require both SPF and DKIM to pass for automated senders
  return (
    authResults.includes("spf=pass") &&
    authResults.includes("dkim=pass")
  );
}

function extractCustomData(headers: EmailHeaders) {
  return {
    orderId:    headers["x-order-id"]       ?? null,
    customerId: headers["x-customer-id"]    ?? null,
    eventType:  headers["x-event-type"]     ?? null,
    priority:   parseInt(headers["x-priority"] ?? "3", 10),
    listId:     headers["list-id"]          ?? null,
  };
}

app.post("/webhooks/email", (req, res) => {
  // ... verify HMAC signature ...
  const { email } = JSON.parse(req.body.toString());
  const headers = email.headers as EmailHeaders;

  if (!validateSenderAuthentication(headers)) {
    // Log but still acknowledge — don't retry unauthenticated emails
    console.warn("Unauthenticated sender:", email.from);
    return res.sendStatus(200);
  }

  const data = extractCustomData(headers);
  console.log("Custom header data:", data);

  res.sendStatus(200);
});

Common Pitfalls

Header processing pitfalls:

  • Case-sensitive header lookups. JsonHook lowercases all header names. Use lowercase keys in your lookups: headers["x-order-id"] not headers["X-Order-Id"].
  • Multi-value headers returned as a single string. Some headers (like Received) can appear multiple times. In the JsonHook payload, multiple values for the same header are joined with a newline. Split on to get individual values.
  • Treating authentication-results as a boolean. The Authentication-Results header contains a text description from the receiving server — it is not a boolean pass/fail. Parse its text content carefully and handle different server formats.
  • Relying on headers that senders can forge. Headers like X-Priority and custom X- headers are set by the sender and can be spoofed. Only use them for routing decisions when you control the sending system or when a failed authentication check is acceptable.
  • Not accounting for header folding in raw values. Long headers may be folded across multiple lines in the raw MIME. JsonHook unfolds headers before including them in the JSON — the values you receive are already unfolded.

Frequently Asked Questions

Are all email headers included in the payload?

Yes. JsonHook includes all headers from the original email in the email.headers object with lowercase keys. This includes standard headers (From, To, Subject, Date, Message-ID), routing headers (Received chain), authentication headers (Authentication-Results, DKIM-Signature), and any custom X-headers the sender included.

How do I check if an email passed DKIM verification?

Look at the authentication-results header. It is set by the receiving mail server (JsonHook's SMTP infrastructure) and contains the DKIM verification result. A passing result looks like: dkim=pass header.d=example.com. JsonHook performs DKIM verification before delivering to your webhook, so you can trust this header value.

Can I access the Message-ID header?

Yes. The Message-ID is available both as email.messageId (a promoted top-level field for convenience) and as email.headers["message-id"]. Use email.messageId as an idempotency key to detect duplicate deliveries of the same email message.

How do I read the Received chain to trace email routing?

The Received headers form a chain showing each hop the email made from sender to JsonHook's servers. They are available as email.headers.received. If there are multiple Received headers, they are joined with newlines in the payload — split on \n to get each hop. The first entry is the most recent hop (JsonHook's server); the last entry is the originating server.