Skip to content

Instantly share code, notes, and snippets.

@englishm
Created March 20, 2026 04:03
Show Gist options
  • Select an option

  • Save englishm/b1e32d61e7f88fe4d048067ff824eedf to your computer and use it in GitHub Desktop.

Select an option

Save englishm/b1e32d61e7f88fe4d048067ff824eedf to your computer and use it in GitHub Desktop.
#!/usr/bin/env -S uv run --script
# /// script
# requires-python = ">=3.10"
# ///
"""
Import a .eml file into your Gmail inbox via IMAP APPEND.
Useful for replying to IETF mailing list messages from the web archive —
the original Message-ID, References, and In-Reply-To headers are preserved,
so Gmail will thread your reply correctly.
Usage:
chmod +x gmail-import-eml.py
./gmail-import-eml.py message.eml
Or without making it executable:
uv run gmail-import-eml.py message.eml
Authentication:
You need a Gmail "App Password" (NOT your regular password).
Generate one here — you'll need 2FA enabled first:
https://myaccount.google.com/apppasswords
Select app type "Mail", give it a name like "eml-import", and copy
the 16-character password it gives you.
You can either:
- Export env vars to skip the prompts:
export GMAIL_USER="you@gmail.com"
export GMAIL_APP_PASSWORD="xxxx xxxx xxxx xxxx"
- Or just run the script and it will prompt you interactively.
"""
import argparse
import email.utils
import getpass
import imaplib
import os
import sys
import time
def main():
parser = argparse.ArgumentParser(
description="Import a .eml file into your Gmail inbox via IMAP.",
epilog="Get an app password at: https://myaccount.google.com/apppasswords",
)
parser.add_argument("eml", help="Path to the .eml file to import")
parser.add_argument(
"--label",
default="INBOX",
help="Gmail label/folder to import into (default: INBOX)",
)
parser.add_argument(
"--unseen",
action="store_true",
help="Mark the message as unread so it's easy to find",
)
args = parser.parse_args()
# Read the .eml file
try:
with open(args.eml, "rb") as f:
eml_data = f.read()
except FileNotFoundError:
print(f"Error: file not found: {args.eml}", file=sys.stderr)
sys.exit(1)
# Get credentials from env or prompt
user = os.environ.get("GMAIL_USER")
if not user:
user = input("Gmail address: ")
password = os.environ.get("GMAIL_APP_PASSWORD")
if not password:
print(
"\nYou need a Gmail App Password (not your regular password)."
"\nGenerate one at: https://myaccount.google.com/apppasswords"
"\n"
)
password = getpass.getpass("App password: ")
# Parse the Date header so Gmail gets the original timestamp
internal_date = None
try:
import email
msg = email.message_from_bytes(eml_data)
date_str = msg.get("Date")
if date_str:
parsed = email.utils.parsedate_to_datetime(date_str)
internal_date = imaplib.Time2Internaldate(parsed)
except Exception:
pass # Fall back to None (current time) if parsing fails
# Connect and append
flags = "()" if args.unseen else "(\\Seen)"
try:
imap = imaplib.IMAP4_SSL("imap.gmail.com")
imap.login(user, password)
status, response = imap.append(args.label, flags, internal_date, eml_data)
imap.logout()
except imaplib.IMAP4.error as e:
print(f"IMAP error: {e}", file=sys.stderr)
print(
"\nIf you got 'AUTHENTICATIONFAILED', double-check that you're"
"\nusing an App Password, not your regular Google password."
"\nGenerate one at: https://myaccount.google.com/apppasswords",
file=sys.stderr,
)
sys.exit(1)
if status == "OK":
print(f"Imported into {args.label}: {args.eml}")
print("Open Gmail and find the message to reply with proper threading.")
else:
print(f"Failed: {response}", file=sys.stderr)
sys.exit(1)
if __name__ == "__main__":
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment