A simple Gmail / Google Workspace email client built on the official Gmail API, using the fastai/fastcore coding style.
pip install solvemailOr for development:
pip install -e .For detailed instructions on setting up Google Cloud credentials, see ezgmail's excellent documentation.
In brief:
- Create an OAuth Client ID (Desktop app) in Google Cloud Console and enable the Gmail API
- Download the client secrets JSON as
credentials.json - Put
credentials.jsonnext to your script (or pass its path)
On first run, solvemail will open a browser to authorize and will save token.json.
import solvemail
solvemail.init() # reads credentials.json + token.json in cwd
# For multiple accounts, use separate token files:
# solvemail.init(token_path='work.json') # first run opens browser to auth
# solvemail.init(token_path='personal.json') # switch account without re-auth
# Check which account you're using
solvemail.profile().email
# solvemail exports the key API functionality through wildcard import
from solvemail import *
# Search for threads
threads = search_threads('is:unread newer_than:7d', max_results=10)
# Get thread details
t = threads[0]
for m in t.msgs():
print(m.frm, '|', m.subj)
# Read a message
m = t.msgs()[0]
m.subj, m.frm, m.snip, m.text()
# Send an email
send(to='[email protected]', subj='Hello', body='Hi there!')
# Create and send a reply
draft = t.reply_draft(body='Thanks!')
draft.send()# Search threads (conversations)
search_threads('from:[email protected]', max_results=20)
# Search individual messages
search_msgs('has:attachment filename:pdf', max_results=100)m = msg(id) # Fetch by id
m.subj, m.frm, m.to # Headers
m.text(), m.html() # Body content
m.mark_read(), m.mark_unread() # Read status
m.star(), m.unstar() # Starred
m.archive() # Remove from inbox
m.trash(), m.untrash() # Trash
m.add_labels('MyLabel') # Add labels
m.rm_labels('INBOX') # Remove labelst = thread(id) # Fetch by id
t.msgs() # List messages
t[0], t[-1] # Index into messages
t.reply_draft(body='...') # Create reply draft
t.reply(body='...') # Send reply directly
# Batch fetch multiple threads efficiently (one HTTP call)
threads = search_threads('in:inbox', max_results=50)
threads = get_threads(threads)Messages render nicely in Jupyter notebooks (quotes and signatures stripped automatically).
m = t[-1]
m.body() # Cleaned text (no quotes/signatures)
m.html() # HTML body (falls back to text wrapped in <pre>)
# View message with headers (as dict or plain text)
view_msg(m.id) # Returns dict with headers + body
view_msg(m.id, as_json=False) # Returns formatted text
# View full thread
view_thread(t.id) # Dict of msgid -> msg dict
view_thread(t.id, as_json=False) # Concatenated text with separatorsview_inbox(max_msgs=20) # Batch fetch inbox messages
view_inbox_threads(max_threads=20) # Batch fetch inbox threads
view_inbox(unread=True) # Only unreadlabels() # List all labels
label('INBOX') # Get by name or id
find_labels('project') # Search labels
create_label('My Label') # Create new labeldrafts() # List drafts
create_draft(to='...', subj='...', body='...')
reply_to_thread(thread_id, body='...')# Batch modify labels (auto-chunks, no 1000 message limit)
ids = [m.id for m in search_msgs('in:inbox')]
batch_label(ids, add=['SPAM'], rm=['INBOX'])
# Trash multiple messages
trash_msgs(ids)
# Permanently delete (requires full mail scope)
batch_delete(ids)Set these env vars to run e2e tests against a throwaway Gmail/Workspace account:
GMAILX_CREDS— path tocredentials.jsonGMAILX_TOKEN— path totoken.json(will be created if missing)GMAILX_E2E— set to1to enable e2e tests
pytest -qInspired by ezgmail by Al Sweigart — thanks Al for the great work! The ezgmail repo also has excellent documentation on setting up Gmail API credentials.