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


23 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