https://github.com/answerdotai/solvemail
Gmail / Google Workspace email client built on the Gmail API
https://github.com/answerdotai/solvemail
Last synced: 23 days ago
JSON representation
Gmail / Google Workspace email client built on the Gmail API
- Host: GitHub
- URL: https://github.com/answerdotai/solvemail
- Owner: AnswerDotAI
- Created: 2026-01-13T09:23:55.000Z (about 1 month ago)
- Default Branch: main
- Last Pushed: 2026-01-17T20:03:45.000Z (26 days ago)
- Last Synced: 2026-01-19T13:53:40.848Z (24 days ago)
- Language: Python
- Homepage:
- Size: 176 KB
- Stars: 7
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
README
# solvemail
A simple Gmail / Google Workspace email client built on the official Gmail API, using the fastai/fastcore coding style.
## Install
```bash
pip install solvemail
```
Or for development:
```bash
pip install -e .
```
## OAuth setup
For detailed instructions on setting up Google Cloud credentials, see [ezgmail's excellent documentation](https://github.com/asweigart/ezgmail#enable-the-gmail-api).
In brief:
1. Create an OAuth Client ID (Desktop app) in [Google Cloud Console](https://console.cloud.google.com) and enable the Gmail API
2. Download the client secrets JSON as `credentials.json`
3. Put `credentials.json` next to your script (or pass its path)
On first run, `solvemail` will open a browser to authorize and will save `token.json`.
## Quick start
```python
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='someone@example.com', subj='Hello', body='Hi there!')
# Create and send a reply
draft = t.reply_draft(body='Thanks!')
draft.send()
```
## Features
### Searching
```python
# Search threads (conversations)
search_threads('from:boss@company.com', max_results=20)
# Search individual messages
search_msgs('has:attachment filename:pdf', max_results=100)
```
### Messages
```python
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 labels
```
### Threads
```python
t = 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)
```
### Message display
Messages render nicely in Jupyter notebooks (quotes and signatures stripped automatically).
```python
m = t[-1]
m.body() # Cleaned text (no quotes/signatures)
m.html() # HTML body (falls back to text wrapped in
)
# 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 separators
```
### Inbox helpers
```python
view_inbox(max_msgs=20) # Batch fetch inbox messages
view_inbox_threads(max_threads=20) # Batch fetch inbox threads
view_inbox(unread=True) # Only unread
```
### Labels
```python
labels() # List all labels
label('INBOX') # Get by name or id
find_labels('project') # Search labels
create_label('My Label') # Create new label
```
### Drafts
```python
drafts() # List drafts
create_draft(to='...', subj='...', body='...')
reply_to_thread(thread_id, body='...')
```
### Bulk operations
```python
# 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)
```
## Testing
Set these env vars to run e2e tests against a throwaway Gmail/Workspace account:
- `GMAILX_CREDS` — path to `credentials.json`
- `GMAILX_TOKEN` — path to `token.json` (will be created if missing)
- `GMAILX_E2E` — set to `1` to enable e2e tests
```bash
pytest -q
```
## Credits
Inspired by [ezgmail](https://github.com/asweigart/ezgmail) by [Al Sweigart](https://inventwithpython.com/) — thanks Al for the great work! The ezgmail repo also has excellent documentation on setting up Gmail API credentials.