2025, Oct 15 17:16
Как сохранить заголовок Date в Gmail API: правильный internalDateSource
Как через Gmail API users.messages.insert сохранить заголовок Date при миграции: укажите internalDateSource=dateHeader в параметрах метода, а не внутри body.
При миграции сообщений в Gmail важное требование — сохранить исходный заголовок Date, чтобы цепочки и архивы отображали реальную хронологию. Если вы используете users.messages.insert и замечаете, что Gmail подменяет ваш Date текущим временем, проблема, скорее всего, не в MIME или Base64 — дело в том, куда вы помещаете internalDateSource.
Обзор проблемы
Цель — вставлять «сырые» сообщения формата RFC 5322 через Gmail API и сохранить заголовок Date без изменений. Возникает соблазн добавить internalDateSource в тело JSON рядом с raw. Но так Gmail игнорирует указанный источник и проставляет время вставки вместо значения из заголовка Date.
Воспроизводимый пример, который вызывает проблему
from googleapiclient.discovery import build
import base64
# Инициализируем клиент Gmail API
gmail_svc = build('gmail', 'v1', credentials=creds_in)
# «Сырое» письмо с пользовательским заголовком Date
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')
# Неправильно: internalDateSource помещён внутрь body
payload = {
    'raw': msg_b64,
    'internalDateSource': 'dateHeader',
    'deleted': True
}
api_result = gmail_svc.users().messages().insert(userId='me', body=payload).execute()
print(api_result)
Хотя заголовок Date в исходном сообщении задан корректно и передан параметр deleted, вставленное письмо получает текущую метку времени. Для внутренней даты сообщения Gmail использует момент вставки, а не значение Date.
Почему так происходит
Флаг internalDateSource — это параметр метода, а не часть тела сообщения. Если поместить его в body, API тихо его проигнорирует. Сигнатура метода показывает, где ему место:
insert(userId, body=None, deleted=None, internalDateSource=None, media_body=None, media_mime_type=None, x__xgafv=None)
Неправильное расположение internalDateSource мешает Gmail использовать заголовок Date при установке внутренней временной метки.
Решение и исправленный пример
Перенесите internalDateSource в список верхнеуровневых аргументов вызова insert. Этого достаточно, чтобы Gmail учитывал заголовок Date; параметр работает и без deleted.
from googleapiclient.discovery import build
import base64
# Инициализируем клиент Gmail API
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'],  # Необязательно: поместить во входящие
}
api_result = gmail_svc.users().messages().insert(
    userId='me',
    body=payload,
    internalDateSource='dateHeader',
    # deleted=True,
).execute()
print(api_result)
После этого изменения Gmail берёт значение из заголовка Date. В тестах письма сортировались по указанной дате, а сама величина отображалась и в «сырых» заголовках, и как Created at в просмотре исходника сообщения. Учтите, что на отображение влияет локальный часовой пояс.
Почему это важно
При миграции почты или импорте архивов хронология критична. Если брать время вставки, нарушается исторический порядок переписок и меток; использование заголовка Date сохраняет исходную временную линию. Правильное размещение internalDateSource гарантирует ожидаемое поведение API и избавляет от хрупких обходных путей вроде дополнительных заголовков.
Дополнительное наблюдение
Иногда в ответе API приходит только id, а в других случаях дополнительно появляются labelIds и threadId. Это не влияет на сохранение заголовка Date.
Выводы
Если при вставке Gmail будто бы игнорирует ваш заголовок Date, сначала проверьте расположение параметров. Флаг internalDateSource — это параметр метода insert, а не тела сообщения. Переместив его, вы позволите Gmail опираться напрямую на Date, причём для этого не требуется deleted.