2025, Oct 15 17:00
How to Preserve the Date Header When Inserting Emails via Gmail API: internalDateSource Parameter Explained
Learn why Gmail API users.messages.insert ignored your Date header and how moving internalDateSource out of the body preserves chronology in email migrations.
When migrating messages into Gmail, a common requirement is to preserve the original Date header so threads and archives reflect the true chronology. If you rely on users.messages.insert and see Gmail replacing your Date with the current timestamp, the issue most likely isn’t your MIME or Base64—it’s where you put internalDateSource.
Problem overview
The goal is to insert raw RFC 5322 messages via the Gmail API and keep the Date header intact. It’s tempting to put internalDateSource into the JSON body alongside raw. Doing so leads to Gmail ignoring the intended source and stamping the message with the insertion time instead of the Date header value.
Reproducible example that causes the issue
from googleapiclient.discovery import build
import base64
# Initialize Gmail API client
gmail_svc = build('gmail', 'v1', credentials=creds_in)
# Raw email with a custom Date header
msg_src = """\
Content-Type: text/plain; charset="us-ascii"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit
to: recipient@gmail.com
from: sender@gmail.com
subject: Test Email
Date: Tue, 13 Aug 2024 14:00:00 +0000
This is a test email.
"""
# Encode the email
msg_b64 = base64.urlsafe_b64encode(msg_src.encode('utf-8')).decode('utf-8')
# Incorrect: placing internalDateSource inside body
payload = {
    'raw': msg_b64,
    'internalDateSource': 'dateHeader',
    'deleted': True
}
api_result = gmail_svc.users().messages().insert(userId='me', body=payload).execute()
print(api_result)
Despite the Date header being correctly set in the raw message and deleted being provided, the inserted message ends up with the current timestamp. Gmail uses the insertion time, not the Date header, for the message’s internal date.
Why this happens
The internalDateSource flag is a method parameter, not a part of the message body. If you include it inside body, the API silently ignores it. The method signature clarifies where this parameter belongs:
insert(userId, body=None, deleted=None, internalDateSource=None, media_body=None, media_mime_type=None, x__xgafv=None)
Placing internalDateSource in the wrong location prevents Gmail from using the Date header when setting the internal timestamp.
Fix and corrected example
Move internalDateSource to the insert call’s top-level arguments. This is enough to make Gmail honor the Date header; it also works without deleted.
from googleapiclient.discovery import build
import base64
# Initialize Gmail API client
gmail_svc = build('gmail', 'v1', credentials=creds_in)
msg_src = """\
Content-Type: text/plain; charset="us-ascii"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit
to: recipient@gmail.com
from: sender@gmail.com
subject: Test Email
Date: Tue, 13 Aug 2024 14:00:00 +0000
This is a test email.
"""
msg_b64 = base64.urlsafe_b64encode(msg_src.encode('utf-8')).decode('utf-8')
payload = {
    'raw': msg_b64,
    # 'labelIds': ['INBOX'],  # Optional: place in INBOX
}
api_result = gmail_svc.users().messages().insert(
    userId='me',
    body=payload,
    internalDateSource='dateHeader',
    # deleted=True,
).execute()
print(api_result)
With this change, Gmail uses the Date header value. In tests, messages sorted according to the specified Date, and the value appeared both in the raw headers and as Created at in the message source view. Local timezone settings can affect how that time is displayed to you.
Why this detail matters
For email migrations or archival imports, chronology is critical. Using the insertion time breaks the historical order of threads and labels, while honoring the Date header preserves the original timeline. Getting internalDateSource into the correct place ensures the API behaves as intended and avoids brittle workarounds such as additional headers.
Additional observation
The API response may sometimes include only id, while in other runs it can also include labelIds and threadId. This variation doesn’t affect whether the Date header is preserved.
Takeaways
If Gmail seems to ignore your Date header during inserts, check parameter placement first. The internalDateSource flag belongs to the insert method’s parameters, not the message body. After moving it, Gmail can rely on the Date header directly, and you don’t need deleted for the behavior to work.