Skip to main content

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

Metadata

Release files for email-profile 1.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for email-profile 1.1.0
File Size Uploaded
email_profile-1.1.0.tar.gz 48.1 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for email-profile 1.1.0
File Interpreter ABI Platform
email_profile-1.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 110.1 kB

Release files / email_profile-1.1.0.tar.gz

Download URL email_profile-1.1.0.tar.gz
Size 48.1 kB
Tags Source
SHA-256 checksum
How to use checksums
fde194000abd4e3e67afcd2304ed193736faa7492bf3d3353166b526bbd21cab
BLAKE2b-256 checksum
How to use checksums
b7bf841aa7e924f196fc83b9c06797577ee32639966208c4957c8938c5d491d9
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 11, 2026.

Transparency log

Release files / email_profile-1.1.0-py3-none-any.whl

Download URL email_profile-1.1.0-py3-none-any.whl
Size 62.0 kB
Tags Python 3
SHA-256 checksum
How to use checksums
702cf308f2303b9bd6f80095eabdd7feede9f73f99e40fc73998c2ab72065f17
BLAKE2b-256 checksum
How to use checksums
d54a10a67bc3c0e62d3fb731acc86d02bcd412840ec7532fa7e18146bbdbfe8f
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 11, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

1.1.0 This release

2 release files

1.0.1

2 release files

1.0.0

2 release files

0.4.0

2 release files

0.3.0

2 release files

0.2.0

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page