Skip to content

Latest commit

Β 

History

375 Commits

Folders and files

NameName
Last commit message
Last commit date
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

email-profile β€” Email for Python, without the boilerplate. MCP included.

email-profile

PyPI Python Tests License Downloads

The simplest way to work with email in Python. No boilerplate, no low-level IMAP commands, no headaches.

Just connect, read, search, send, backup, and restore β€” with one class. Or hand the same account to Claude, Cursor or any MCP client and let the model do it.

from email_profile import Email

with Email("user@gmail.com", "app_password") as app:
    for msg in app.inbox.where().messages():
        print(f"{msg.date} | {msg.from_} | {msg.subject}")

That's it. No server configuration needed β€” email-profile auto-discovers your IMAP server from your email address.



Contents β€” Install Β· Why Β· Quick Start Β· MCP Server Β· Features Β· Providers Β· Environment


Install

pip install email-profile          # library
pip install email-profile[mcp]     # + MCP server for AI clients

Why email-profile?

Most Python email libraries make you deal with imaplib directly, parse raw bytes, manage connections manually, and write dozens of lines just to read your inbox.

email-profile gives you a clean, human API:

  • Write Email("user@gmail.com", "pw") instead of configuring IMAP servers manually
  • Write app.inbox.where(Q.unseen()).first() instead of raw IMAP search commands
  • Write app.sync() instead of building your own backup system
  • Write app.send(to="...", subject="...", body="...") instead of constructing MIME messages
  • Run email-profile-mcp and ask Claude "what needs an answer today?" instead of writing an agent

It combines IMAP + SMTP + storage + sync + MCP in a single library. No other Python package does this.

Quick Start

Connect

Three ways to connect β€” pick the one that fits:

from email_profile import Email

# Just email + password (auto-discovers the server)
with Email("user@gmail.com", "app_password") as app:
    print(app.mailboxes())

# From .env file (great for production)
with Email.from_env() as app:
    print(app.mailboxes())

# Explicit server (when you need full control)
with Email("imap.gmail.com", "user@gmail.com", "app_password") as app:
    print(app.mailboxes())

Read Emails

with Email.from_env() as app:
    # How many emails?
    print(app.inbox.where().count())

    # Read them
    for msg in app.inbox.where().messages():
        print(f"{msg.date} | {msg.from_} | {msg.subject}")

    # Just the first one
    msg = app.inbox.where().first()

    # Only headers (much faster for large mailboxes)
    for msg in app.inbox.where().messages(mode="headers"):
        print(msg.subject)

Search

Find exactly what you need with composable queries:

from email_profile import Email, Q
from datetime import date

with Email.from_env() as app:
    # Combine conditions with & (AND), | (OR), ~ (NOT)
    q = Q.subject("meeting") & Q.unseen()
    print(app.inbox.where(q).count())

    # From Alice or Bob
    q = Q.from_("alice@x.com") | Q.from_("bob@x.com")

    # Everything except seen emails
    q = ~Q.seen()

    # Emails from 2025, larger than 1MB
    q = Q.since(date(2025, 1, 1)) & Q.before(date(2025, 12, 31)) & Q.larger(1_000_000)

Or use validated kwargs if you prefer:

from email_profile import Query

query = Query(subject="report", unseen=True, since=date(2025, 1, 1))
query = Query(subject="report").exclude(subject="spam").or_(subject="urgent")

Built-in shortcuts for common searches:

app.unread().count()
app.recent(days=7).count()
app.search("invoice").count()

Send Emails

Send, reply, and forward β€” with automatic SMTP discovery:

with Email.from_env() as app:
    # Simple
    app.send(to="recipient@x.com", subject="Hello", body="Hi there!")

    # HTML + attachments + CC
    app.send(
        to=["alice@x.com", "bob@x.com"],
        subject="Report",
        body="See attached.",
        html="<h1>Report</h1>",
        attachments=["report.pdf"],
        cc="manager@x.com",
    )

    # Reply to an email (preserves threading)
    msg = app.inbox.where().first()
    app.reply(msg, body="Thanks!")

    # Forward
    app.forward(msg, to="colleague@x.com", body="FYI")

Backup & Restore

Sync your entire mailbox to a local SQLite database. Incremental β€” only downloads new emails. Parallel β€” multiple mailboxes at once. With progress bars.

with Email.from_env() as app:
    # Backup everything (compares by Message-ID, skips duplicates)
    result = app.sync()
    print(f"{result.inserted} new, {result.skipped} skipped")

    # Backup one mailbox
    result = app.sync(mailbox="INBOX")

    # Force re-download (skip duplicate check)
    result = app.sync(skip_duplicates=False)

    # Restore to server (e.g. after migrating)
    count = app.restore()

Sync demo

Mailbox Operations

with Email.from_env() as app:
    # Built-in folder shortcuts (auto-detected across languages)
    app.inbox      # INBOX
    app.sent       # Sent / Enviados / Enviadas
    app.trash      # Trash / Lixeira / Papelera
    app.drafts     # Drafts / Rascunhos
    app.spam       # Spam / Junk / Lixo EletrΓ΄nico

    # Any folder by name
    work = app.mailbox("INBOX.Work")

    # Message operations
    work.mark_seen(uid)
    work.move(uid, "INBOX.Archive")
    work.delete(uid)

Custom Storage

Storage is lazily initialized β€” email.db is only created when sync() or restore() is first called.

from email_profile import Email, StorageSQLite

# Default: saves to ./email.db on first sync
with Email.from_env() as app:
    app.sync()

# Custom path
with Email.from_env() as app:
    app.storage = StorageSQLite("./backup.db")
    app.sync()

MCP Server

The same account, as tools for an AI client. Install the extra, point Claude Code, Claude Desktop or Cursor at it, and ask:

"What came in today that needs an answer?" "Find the invoice Alice sent last month and save the PDF." "Draft a reply saying Thursday works β€” show me before you send."

pip install "email-profile[mcp]"
email-profile-mcp                 # read-only
email-profile-mcp --allow-send    # + send, reply, forward

Safety model

  • Read-only by default. send_email, reply_message and forward_message exist only with --allow-send; delete_message only with --allow-delete.
  • Irreversible tools are annotated destructive, so clients that support it ask you before calling them.
  • Credentials never pass through the model. They come from the environment or .env; no tool accepts a password.
  • Bodies are truncated (4000 chars by default) and attachment bytes never cross the wire β€” save_attachment writes only under EMAIL_MCP_ATTACHMENTS_DIR and returns the path.
  • One message per call. uid accepts a single id; IMAP ranges like 1:* are refused, and search strings are escaped before reaching the server.

Connect a client

Claude Code
claude mcp add email \
  --env EMAIL_USERNAME=you@gmail.com --env EMAIL_PASSWORD=app-password \
  -- uvx --from "email-profile[mcp]" email-profile-mcp --allow-send

Or install the repository as a plugin β€” the server plus six skills that keep the model from sending before you approve:

claude plugin marketplace add linux-profile/email-profile
claude plugin install email-profile@email-profile
Claude Desktop

~/Library/Application Support/Claude/claude_desktop_config.json:

{
  "mcpServers": {
    "email": {
      "command": "uvx",
      "args": ["--from", "email-profile[mcp]", "email-profile-mcp", "--allow-send"],
      "env": { "EMAIL_USERNAME": "you@gmail.com", "EMAIL_PASSWORD": "app-password" }
    }
  }
}
Cursor

Same shape in .cursor/mcp.json β€” copy examples/cursor.json.

From Python
from email_profile import Email
from email_profile.mcp import Settings, build

server = build(
    Settings(allow_send=True),
    email_factory=lambda: Email("imap.example.com", "user", "pw"),
)
server.run()                              # stdio
server.run(transport="streamable-http")   # or HTTP

Tools

Tool Hint What it does
list_mailboxes read-only Server-side folder names
search_messages read-only Filter one mailbox by sender, subject, text, dates, flags β€” headers only, newest first, paginated
read_message read-only Headers, body and attachment metadata for one (mailbox, uid)
list_attachments read-only Name, type and size of each attachment
save_attachment reversible Write one attachment to disk
mark_seen / mark_unseen reversible Read state
flag_message / unflag_message reversible Star
move_message reversible Move to another mailbox
send_email destructive New message over SMTP β€” --allow-send
reply_message destructive Reply keeping thread headers β€” --allow-send
forward_message destructive Forward with attachments β€” --allow-send
delete_message destructive Flag, or expunge with expunge=true β€” --allow-delete

Four prompts put the tools in the order a task needs: triage_inbox, find_message, draft_reply, summarize_thread. The plugin ships the same guidance as skills under skills/.

Options

Flag Env Default
--allow-send EMAIL_MCP_ALLOW_SEND off
--allow-delete EMAIL_MCP_ALLOW_DELETE off
--max-chars EMAIL_MCP_MAX_CHARS 4000
β€” EMAIL_MCP_LIMIT 20
β€” EMAIL_MCP_DEFAULT_MAILBOX INBOX
β€” EMAIL_MCP_ATTACHMENTS_DIR . β€” the only place save_attachment writes
--http --host --port β€” stdio

Full reference: MCP Server docs.

Features

Feature Description
Auto-discovery Detects IMAP/SMTP servers from email domain (50+ providers)
Unified API IMAP + SMTP in a single Email class
Query Builder Composable search with Q (AND, OR, NOT) and validated Query kwargs
Sync & Restore Incremental backup to SQLite, restore to any server
Parallel Multi-threaded sync and restore with configurable workers
Progress Rich progress bars with per-mailbox status
Retry Exponential backoff on transient failures
Send Send, reply, forward with HTML, attachments, CC/BCC
Storage Pluggable storage backend (SQLite default)
Flags Read/unread, flag, delete, move, copy operations
Context Manager with Email(...) as app: for automatic cleanup
MCP Server 14 tools + 4 prompts for Claude Code, Claude Desktop, Cursor β€” read-only until you opt in
Plugin Claude Code plugin with skills that gate sending behind your approval

Supported Providers

Auto-discovery works out of the box. Just use your email and password β€” no server configuration needed.

Provider IMAP Server
Gmail imap.gmail.com
Outlook / Hotmail / Live outlook.office365.com
Yahoo imap.mail.yahoo.com
iCloud imap.mail.me.com
Zoho imap.zoho.com
ProtonMail (Bridge) 127.0.0.1:1143
AOL imap.aol.com
Yandex imap.yandex.com
Mail.ru imap.mail.ru
GMX imap.gmx.com
Hostinger imap.hostinger.com
GoDaddy imap.secureserver.net
Namecheap mail.privateemail.com
Gandi mail.gandi.net
OVH ssl0.ovh.net
Ionos (1&1) imap.ionos.com
Fastmail imap.fastmail.com
Rackspace secure.emailsrvr.com
Titan imap.titan.email
Locaweb imap.locaweb.com.br
KingHost imap.kinghost.net
UOL imap.uol.com.br
Terra imap.terra.com.br

Any server with DNS SRV or MX records is also detected automatically.

Environment Variables

EMAIL_USERNAME=user@example.com
EMAIL_PASSWORD=app_password
EMAIL_SERVER=imap.example.com   # optional, auto-discovered

EMAIL_MCP_ALLOW_SEND=false      # MCP server only
EMAIL_MCP_ALLOW_DELETE=false

Gmail, Outlook and iCloud require an app password, not the account password.

Contributing

Issues and pull requests welcome β€” see CONTRIBUTING.md. Security reports: SECURITY.md.

License

MIT

About

πŸ“© The simplest way to work with email in Python β€” IMAP + SMTP + backup/restore in one library. Auto-discovers 50+ providers.

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

2 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages