Email Headers Explained for Developers
A technical deep-dive into email headers. Learn what each header does, how to read raw email data, and how headers affect deliverability.
When an email arrives, you see the subject, sender, and body. But underneath there's a whole other layer: headers. These metadata fields control how email is routed, authenticated, and displayed.
Understanding headers helps you debug deliverability issues, trace email paths, and build better email systems.
Essential headers:
From/To/Reply-To— addressingSubject/Date/Message-ID— identificationMIME-Version/Content-Type— body formatReceived— routing traceAuthentication-Results— SPF/DKIM/DMARC status
To view headers: Gmail → three dots → "Show original"
Viewing Raw Headers
Every email client has a way to see the raw message. Here's how:
| Client | How to view |
|---|---|
| Gmail | Three dots → "Show original" |
| Outlook | File → Properties → "Internet headers" |
| Apple Mail | View → Message → "Raw Source" |
| Thunderbird | View → "Message Source" |
What you'll see looks something like this:
Delivered-To: recipient@gmail.com
Received: by 2002:a17:90a:f2d1:0:0:0:0 with SMTP id ca17csp891279pjb;
Sun, 29 Dec 2024 08:15:23 -0800 (PST)
Authentication-Results: mx.google.com;
dkim=pass header.d=yourapp.com;
spf=pass (google.com: domain of bounce@mail.yourapp.com designates 198.51.100.1 as permitted sender);
dmarc=pass (p=REJECT sp=REJECT dis=NONE) header.from=yourapp.com
From: YourApp <notifications@mail.yourapp.com>
To: recipient@gmail.com
Subject: Your order has shipped
Date: Sun, 29 Dec 2024 16:15:20 +0000
Message-ID: <abc123@mail.yourapp.com>
MIME-Version: 1.0
Content-Type: multipart/alternative; boundary="----=_Part_123"
Let's break this down.
Addressing Headers
From
The sender's address. This is what recipients see.
From: YourApp <notifications@mail.yourapp.com>
The format is Display Name <email@address.com>. The display name is optional but recommended.
The From header is what DMARC checks for alignment. If this doesn't match your authenticated domain, you'll fail DMARC.
To, Cc, Bcc
Recipients. Pretty straightforward.
To: alice@example.com, bob@example.com
Cc: manager@example.com
Bcc recipients don't appear in headers (that's the point).
Reply-To
Where replies go. Use this when you want replies sent somewhere other than the From address.
From: notifications@mail.yourapp.com
Reply-To: support@yourapp.com
Now when someone hits "reply," it goes to support, not the no-reply sending address.
Sender and Return-Path
These are related but different:
| Header | Purpose |
|---|---|
Sender | The actual sender when different from From (like a secretary sending on behalf of a boss) |
Return-Path | Where bounce notifications go (set by the receiving server, not you) |
Most of the time, you don't set these directly. Your email provider handles Return-Path for bounce processing.
Identification Headers
Message-ID
A unique identifier for the email. Every email should have one.
Message-ID: <1735488920.abc123@mail.yourapp.com>
Format: <unique-id@sending-domain>
This is critical for:
- Threading (replies use
In-Reply-ToandReferencesto link back) - Deduplication
- Tracking
If you don't set a Message-ID, your email provider generates one. But for better tracking and threading, consider generating your own.
Subject
The email subject line. No surprises here.
Subject: Your order #12345 has shipped
Keep subjects under 50-60 characters to avoid truncation.
Date
When the email was composed (not necessarily when it was sent or received).
Date: Sun, 29 Dec 2024 16:15:20 +0000
Format is RFC 2822. Always include the timezone.
Content Headers
MIME-Version
Always 1.0. This indicates the email uses MIME (Multipurpose Internet Mail Extensions) encoding.
MIME-Version: 1.0
Content-Type
Describes the body format.
Plain text:
Content-Type: text/plain; charset="UTF-8"
HTML:
Content-Type: text/html; charset="UTF-8"
Mixed content (attachments):
Content-Type: multipart/mixed; boundary="----=_Part_123"
HTML with plain text fallback:
Content-Type: multipart/alternative; boundary="----=_Part_456"
The boundary parameter separates different parts of the email.
Content-Transfer-Encoding
How the body is encoded.
| Value | Use case |
|---|---|
7bit | ASCII only |
quoted-printable | Mostly ASCII with some special chars |
base64 | Binary data (images, attachments) |
Content-Transfer-Encoding: quoted-printable
Routing Headers
Received
The email's travel history. Each server that handles the email adds a Received header.
Received: from mail.yourapp.com (mail.yourapp.com [198.51.100.1])
by mx.google.com with ESMTPS id abc123
for <recipient@gmail.com>
Sun, 29 Dec 2024 08:15:23 -0800 (PST)
Received: from app-server-01 (internal [10.0.0.5])
by mail.yourapp.com with SMTP
Sun, 29 Dec 2024 16:15:21 +0000
Read these bottom-to-top. The oldest (first hop) is at the bottom.
This is useful for:
- Debugging delivery delays
- Tracing the email path
- Identifying which server rejected the email
X-Originating-IP
The original sender's IP address. Added by some mail servers.
X-Originating-IP: [198.51.100.1]
Authentication Headers
Authentication-Results
Added by the receiving server. Shows SPF, DKIM, and DMARC results.
Authentication-Results: mx.google.com;
dkim=pass header.d=yourapp.com header.s=selector1;
spf=pass (google.com: domain of bounce@mail.yourapp.com designates 198.51.100.1 as permitted sender);
dmarc=pass (p=REJECT sp=REJECT dis=NONE) header.from=yourapp.comThis is what you check when debugging deliverability. Look for pass on all three. You can also use our free deliverability checker to verify these without sending a test email.
DKIM-Signature
The DKIM signature added by the sending server.
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;
d=yourapp.com; s=selector1;
h=from:to:subject:date:message-id:content-type:mime-version;
bh=2jUSOH9NhtVGCQWNr9BrIAPreKQjO6Sn7XIkfJVOzv8=;
b=AuUoFEfDxTDkHlLXSZEpZj79LICEps6eda7W3deTVFOk4yAUoqOB...
| Field | Meaning |
|---|---|
d= | Signing domain |
s= | Selector (identifies which key) |
h= | Headers that were signed |
bh= | Body hash |
b= | The actual signature |
If someone tampers with any signed header or the body, the signature won't verify.
ARC Headers
ARC (Authenticated Received Chain) preserves authentication results across forwarding.
ARC-Authentication-Results: i=1; mx.google.com;
dkim=pass ...
ARC-Message-Signature: ...
ARC-Seal: ...
This solves the problem where forwarding breaks DKIM (because the forwarder modifies the message).
List and Subscription Headers
List-Unsubscribe
Tells email clients how to unsubscribe. Gmail and other clients show an unsubscribe button when this is present.
List-Unsubscribe: <mailto:unsubscribe@yourapp.com>, <https://yourapp.com/unsubscribe?id=abc123>
List-Unsubscribe-Post: List-Unsubscribe=One-Click
The List-Unsubscribe-Post header enables one-click unsubscribe (required by Gmail for bulk senders).
Even for transactional email, including List-Unsubscribe can improve deliverability. Gmail looks for it.
Precedence
Indicates the email type.
Precedence: bulk
Values: bulk, list, junk. Used by some filters to categorize email.
Custom Headers (X-Headers)
Any header starting with X- is a custom extension.
X-Mailer: YourApp/1.0
X-Priority: 1
X-Entity-Ref-ID: order-12345
Common uses:
- Tracking IDs
- Application metadata
- Priority hints (though most clients ignore
X-Priority)
You can add custom headers for your own tracking, but recipients can see them.
Header Order and Limits
Order matters (sort of)
Headers can technically appear in any order, but some parsers expect certain headers first. Best practice: put addressing headers (From, To, Subject) near the top.
Size limits
| Limit | Value |
|---|---|
| Single header line | 998 characters (78 recommended) |
| Total header size | No spec limit, but 64KB is safe |
Long headers should be folded (split across lines with whitespace):
Received: from very-long-server-name.example.com
(very-long-server-name.example.com [198.51.100.1])
by mx.google.com
Common Issues
Missing Message-ID
Some email libraries don't generate Message-IDs. This can cause:
- Threading issues
- Duplicate delivery
- Tracking problems
Wrong Date format
Date: 29 Dec 2024 16:15:20 # Wrong - missing day name and timezone
Date: Sun, 29 Dec 2024 16:15:20 +0000 # Correct
Content-Type mismatch
Saying the content is text/plain when it's actually HTML, or vice versa, confuses email clients.
Too many Received headers
If an email has 20+ Received headers, it's been forwarded many times. Some spam filters flag this as suspicious.
Debugging with Headers
When something goes wrong, headers tell you what happened.
Email delayed?
Check Received headers for timestamps at each hop.
Landing in spam?
Check Authentication-Results for failures.
Replies going wrong?
Check Reply-To and From.
Display issues?
Check Content-Type and Content-Transfer-Encoding.
Next Steps
- Fix authentication issues: DKIM, SPF, and DMARC Explained
- Improve deliverability: Email Deliverability Checklist
- Troubleshoot spam: Why Are My Emails Going to Spam?