Understanding Email Header Discrepancies: Why Delivered-To and To Fields Differ in SMTP Routing


2 views

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:

  1. Alias Expansion:
    # Postfix alias_maps configuration
    msmith: mike.smith
  2. Mailing List Processing:
    # Procmail rule example
    :0
    * ^To:.*dev-team@
    {
        :0 c
        | formail -A "X-Forwarded-To: dev-team@company.com"
    }
  3. 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