When examining raw email headers, you'll encounter multiple addressing fields that serve different purposes in the SMTP delivery chain. The key fields in question are:
Received: from mailserver1.example.com (mailserver1.example.com [192.0.2.1])
by mailserver2.work.com with ESMTP id 123456789;
Wed, 12 Jul 2023 14:25:36 +0000
Delivered-To: mike.smith@work.com
To: "msmith@work.com"
From: sender@example.com
Return-Path:
These discrepancies typically occur due to:
- Email Aliasing: When an organization maintains multiple email addresses that resolve to the same mailbox
- Mailing List Expansion: Where the original recipient is a list address that gets expanded
- Forwarding Rules: Either server-side or client-side forwarding configurations
- SMTP Envelope vs Header Differences: The fundamental technical distinction in email routing
The critical distinction lies in the difference between SMTP envelope addresses and header addresses:
# SMTP envelope (not visible in received email)
MAIL FROM:
RCPT TO:
# Email headers (visible to end user)
To: "msmith@work.com"
Delivered-To: mike.smith@work.com
The Delivered-To
field typically reflects the final RCPT TO value in the SMTP transaction, while To
comes from the message headers.
Here's a Python script to parse and compare addressing fields:
import email
from email.header import decode_header
def analyze_email_headers(raw_email):
msg = email.message_from_string(raw_email)
print("Header Analysis Results:")
print(f"To: {msg['to']}")
print(f"Delivered-To: {msg['delivered-to']}")
print(f"Envelope Recipient: {get_envelope_recipient(msg)}")
def get_envelope_recipient(msg):
received_headers = msg.get_all('received', [])
if received_headers:
last_received = received_headers[-1]
if 'for ' in last_received.lower():
return last_received.split('for ')[-1].split(';')[0]
return None
In corporate environments, several factors can create these discrepancies:
- Shared mailbox configurations
- Email security gateways rewriting headers
- Mail transport agents performing address rewriting
- Internal routing within Exchange or other mail servers
When building email processing systems, remember:
// Bad practice - relying only on the To: header
const recipient = message.headers.get('to');
// Better approach - check multiple fields
function getActualRecipient(message) {
return message.headers.get('delivered-to') ||
message.headers.get('x-original-to') ||
message.headers.get('to');
}
Always verify email delivery using the most authoritative header available for your specific use case.
html
When examining raw email headers, programmers often encounter discrepancies between the Delivered-To
and To
fields. Let's break down how this occurs at the protocol level:
Received: by mail.work.com (Postfix, from userid 1000)
Delivered-To: mike.smith@work.com
To: "msmith@work.com" <msmith@work.com>
X-Original-To: msmith@work.com
Here are three technical situations where these fields diverge:
- Alias Expansion:
# Postfix alias_maps configuration msmith: mike.smith
- Mailing List Processing:
# Procmail rule example :0 * ^To:.*dev-team@ { :0 c | formail -A "X-Forwarded-To: dev-team@company.com" }
- Internal Rewriting Rules:
# Exim4 rewrite rule *@work.com ${lookup{$local_part}lsearch{/etc/email/rewrites}}
Use these methods to trace the email path:
# Python email header parser
import email
msg = email.message_from_file(open('email.eml'))
print("Final recipient:", msg['Delivered-To'])
print("Original recipient:", msg['X-Original-To'] or msg['To'])
For server-side investigation with Postfix:
postmap -q msmith@work.com alias_maps postconf -n | grep virtual_alias
The typical delivery process looks like:
1. MAIL FROM:<sender@domain.com> 2. RCPT TO:<msmith@work.com> # Original recipient 3. DATA 4. Received: ... (server adds Delivered-To after processing)
Here's how different MTAs handle this:
Postfix virtual aliases:
/etc/postfix/virtual: msmith@work.com mike.smith@work.com
Sendmail mailertable:
msmith@work.com local:mike.smith
Always validate both headers when writing email processing scripts:
# Ruby email validation snippet
def validate_recipient(email)
delivered_to = email.header['Delivered-To']
original_to = email.header['To']
raise "Spoofing detected" unless valid_domains.include?(delivered_to.domain)
end