Skip to content

Instantly share code, notes, and snippets.

@ernstki
Last active July 13, 2026 21:27
Show Gist options
  • Select an option

  • Save ernstki/e9cd74159f8246b23a665e2c9ab9cc0e to your computer and use it in GitHub Desktop.

Select an option

Save ernstki/e9cd74159f8246b23a665e2c9ab9cc0e to your computer and use it in GitHub Desktop.
imapfrm - Print selected headers from an IMAP inbox
#!/usr/bin/env python3
##
## imapfrm - Print selected headers from an IMAP mailbox
##
## Uses sensible defaults that should work with DavMail[1], but you must
## define IMAP_LOGIN in the environment (or hard-code it below).
##
## The name is meant to vaguely invoke `frm` from the GNU Mailutils suite,
## and earlier historical utilities, although I made no attempt to match its
## options or output format.
##
## Author: Kevin Ernst (ernstki@mail.uc.edu)
## Assisted-by: Mistral-7B-Instruct-v0.3.Q4_0.llamafile
## Date: 13 July 2026
## License: BSD Zero-Clause (effectively public domain)
##
## [1]: https://davmail.sourceforge.net
##
import os
import sys
import email
import email.utils
import imaplib
from email import policy
# you *must* set this in the environment
login = os.getenv('IMAP_LOGIN')
# defaults suitable for DavMail
passwd = os.getenv('IMAP_PASSWORD', '')
host = os.getenv('IMAP_HOST', 'localhost')
port = os.getenv('IMAP_PORT', 1143)
mbox = os.getenv('IMAP_MAILBOX', 'inbox')
wantheaders = os.getenv('IMAP_HEADERS', 'From,Subject,Date')
wantheaders = wantheaders.split(',')
if (port - 143) % 100 == 0: # if it ends in 143, assume no SSL
imap = imaplib.IMAP4(host, port=port)
else:
imap = imaplib.IMAP4_SSL(host, port=port)
imap.login(login, passwd)
imap.select(mbox)
if keywords := " ".join(sys.argv[1:]):
typ, data = imap.search(None, f'SUBJECT "{keywords}"')
else:
typ, data = imap.search(None, 'ALL')
print("\t".join(wantheaders))
for num in data[0].split():
typ, data = imap.fetch(num, '(RFC822.HEADER) FROM')
raw_email = data[0][1]
# policy.default seems to unwrap 'From' and 'Subject' automatically
# …but it's also not the default :/
msg = email.message_from_bytes(raw_email, policy=policy.default)
headers = []
for h in wantheaders:
if h == 'Date' or h == 'Received':
localdt = email.utils.parsedate_tz(msg.get(h))
localdt = email.utils.mktime_tz(localdt)
localdt = email.utils.formatdate(localdt, localtime=True)
headers.append(localdt)
else:
headers.append(msg.get(h))
print("\t".join(headers))
imap.close()
imap.logout()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment