Skip to content

gsuite-gmail

Simple, Pythonic Gmail API client.

Installation

pip install gsuite-sdk   # gsuite_gmail ships inside the SDK

Quick Start

from gsuite_core import GoogleAuth
from gsuite_gmail import Gmail, query

# Authenticate
auth = GoogleAuth()
auth.authenticate()  # Opens browser for consent

gmail = Gmail(auth)

Reading Messages

# Get unread messages
for msg in gmail.get_unread(max_results=10):
    print(f"From: {msg.sender}")
    print(f"Subject: {msg.subject}")
    print(f"Date: {msg.date}")
    print(f"Body: {msg.body[:200]}...")

# Get starred messages
for msg in gmail.get_starred():
    print(msg.subject)

# Get all messages with pagination
messages = gmail.get_messages(max_results=50)

Searching with Query Builder

Fluent API for building Gmail search queries:

from gsuite_gmail import query

# Simple queries
messages = gmail.search(query.from_("boss@company.com"))
messages = gmail.search(query.is_unread())
messages = gmail.search(query.has_attachment())

# Combine with & (AND) and | (OR)
messages = gmail.search(
    query.newer_than(days=7) & 
    query.has_attachment() & 
    query.from_("important@company.com")
)

# Negation with ~
messages = gmail.search(~query.is_starred())

# Complex example
messages = gmail.search(
    (query.from_("team@company.com") | query.from_("boss@company.com")) &
    query.newer_than(days=30) &
    ~query.label("Processed")
)

Query Functions

Function Example Gmail Query
from_(email) query.from_("user@example.com") from:user@example.com
to(email) query.to("me@example.com") to:me@example.com
subject(text) query.subject("meeting") subject:meeting
is_unread() query.is_unread() is:unread
is_starred() query.is_starred() is:starred
has_attachment() query.has_attachment() has:attachment
newer_than(days=N) query.newer_than(days=7) newer_than:7d
older_than(days=N) query.older_than(days=30) older_than:30d
label(name) query.label("Work") label:Work
in_inbox() query.in_inbox() in:inbox
raw(query) query.raw("filename:pdf") filename:pdf

Dict-style Query (simplegmail compatible)

from gsuite_gmail.query import construct_query

# Same API as simplegmail
messages = gmail.search(construct_query(
    unread=True,
    sender="notifications@github.com",
    newer_than=(7, "day"),
    labels=["GitHub"],
))

Sending Email

# Simple send
gmail.send(
    to=["recipient@example.com"],
    subject="Hello!",
    body="This is the message body.",
)

# With CC, BCC, and HTML
gmail.send(
    to=["main@example.com"],
    cc=["copy@example.com"],
    bcc=["hidden@example.com"],
    subject="Meeting Notes",
    body="<h1>Notes</h1><p>HTML content here</p>",
    html=True,
)

# With attachments
gmail.send(
    to=["user@example.com"],
    subject="Report",
    body="Please find attached.",
    attachments=["report.pdf", ("data.csv", csv_bytes)],  # paths or (name, bytes)
)

# Reply in the same thread (In-Reply-To/References are set, so it threads
# for the recipient too). Goes to Reply-To or the sender; replying to your
# own message goes to its recipients.
original = gmail.get_messages(max_results=1)[0]
gmail.reply(
    message=original,
    body="Thanks for your email!",
)
original.reply("Thanks, all!", reply_all=True)

# Forward a message
gmail.forward(
    message=original,
    to=["colleague@example.com"],
    body="FYI - see below",
)

Message Actions

msg = gmail.get_messages(max_results=1)[0]

# Mark as read/unread
msg.mark_as_read()
msg.mark_as_unread()

# Star/unstar
msg.star()
msg.unstar()

# Archive (remove from inbox)
msg.archive()

# Move to trash
msg.trash()

# Add/remove labels
msg.add_label("Important")
msg.remove_label("Unprocessed")

# Fluent chaining
msg.mark_as_read().archive().add_label("Processed")

Labels

# List all labels
for label in gmail.get_labels():
    print(f"{label.name} ({label.type})")

# Get messages with specific label
messages = gmail.get_messages(labels=["IMPORTANT"])

# Create label
gmail.create_label("My Custom Label")

# Delete label
gmail.delete_label("My Custom Label")   # by name or ID
gmail.rename_label("Old", "New")

Bulk Changes

ids = [m.id for m in gmail.get_messages(query="from:newsletter@example.com", max_results=None)]

# One batchModify call per 1000 messages; names or IDs
gmail.batch_modify(ids, add_labels=["Newsletters"], remove_labels=["INBOX", "UNREAD"])

Listing is batched too: get_messages() fetches messages 50 per HTTP request, and iter_messages() does it lazily for large mailboxes.

Drafts

draft = gmail.create_draft(to=["ana@example.com"], subject="Proposal", body="Draft text")
for d in gmail.list_drafts():
    print(d.id, d.message.subject)
gmail.send_draft(draft.id)
gmail.delete_draft("other_draft_id")

Filters

gmail.create_filter(
    criteria={"from": "alerts@example.com"},
    action={"addLabelIds": ["Alerts"], "removeLabelIds": ["INBOX"]},  # names are resolved
)
for f in gmail.list_filters():
    print(f["id"], f["criteria"])
gmail.delete_filter("filter_id")

Attachments

msg = gmail.get_messages(query=query.has_attachment())[0]

for attachment in msg.attachments:
    print(f"Name: {attachment.filename}")
    print(f"Size: {attachment.size} bytes")
    print(f"Type: {attachment.mime_type}")

    content = attachment.download()           # bytes
    attachment.save(f"/tmp/{attachment.filename}")

Thread Operations

# Get threads instead of messages
threads = gmail.get_threads(max_results=10)

for thread in threads:
    print(f"Thread ID: {thread.id}")
    print(f"Messages: {thread.message_count}")

    # Get all messages in thread
    for msg in thread.messages:
        print(f"  - {msg.subject}")

Error Handling

from gsuite_core.exceptions import (
    GSuiteError,
    AuthenticationError,
    RateLimitError,
    NotFoundError,
)

try:
    gmail.send(to=["user@example.com"], subject="Hi", body="Hello")
except RateLimitError:
    print("Hit rate limit, waiting...")
    time.sleep(60)
except AuthenticationError:
    print("Need to re-authenticate")
except GSuiteError as e:
    print(f"Gmail error: {e}")

Configuration

Uses gsuite-core settings. See gsuite-core README for auth configuration.

License

MIT