Skip to content

First Steps

1. Connect

The simplest way to connect is with auto-discovery — just your email and password.

# Code above omitted 👆

    # Auto-discovery (recommended)
    with Email("user@gmail.com", "app_password") as app:
        print(f"Connected to {app.server}")
        print(f"Mailboxes: {app.mailboxes()}")

# Code below omitted 👇
👀 Full file preview
"""Connect to an email server."""

from email_profile import Email


def main() -> None:

    # Auto-discovery (recommended)
    with Email("user@gmail.com", "app_password") as app:
        print(f"Connected to {app.server}")
        print(f"Mailboxes: {app.mailboxes()}")

    # Explicit server
    with Email("imap.gmail.com", "user@gmail.com", "app_password") as app:
        print(f"Connected to {app.server}")

    # From environment variables (.env)
    with Email.from_env() as app:
        print(f"Connected as {app.user}")


if __name__ == "__main__":
    main()

2. Read Emails

Access your inbox and iterate over messages.

# Code above omitted 👆

    with Email.from_env() as app:
        # Count
        print(f"Total: {app.inbox.where().count()}")

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

# Code below omitted 👇
👀 Full file preview
"""Read emails from INBOX."""

from email_profile import Email


def main() -> None:
    with Email.from_env() as app:
        # Count
        print(f"Total: {app.inbox.where().count()}")

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

        # Read only headers (faster)
        for msg in app.inbox.where().messages(mode="headers"):
            print(f"{msg.subject}")

        # Read first 10
        for msg in app.inbox.where().messages(chunk_size=10):
            print(f"{msg.uid}: {msg.subject}")
            break


if __name__ == "__main__":
    main()

Use the Q builder for composable queries.

# Code above omitted 👆

def main() -> None:
    with Email.from_env() as app:
        # Shortcuts
        print(f"Unread: {app.unread().count()}")
        print(f"Recent 7 days: {app.recent(days=7).count()}")
        print(f"Search 'invoice': {app.search('invoice').count()}")

        # Q builder (composable)
        q = Q.subject("meeting") & Q.unseen()
        print(f"Unseen meetings: {app.inbox.where(q).count()}")

# Code below omitted 👇
👀 Full file preview
"""Search emails with Q builder and Query."""

from datetime import date

from email_profile import Email, Q, Query


def main() -> None:
    with Email.from_env() as app:
        # Shortcuts
        print(f"Unread: {app.unread().count()}")
        print(f"Recent 7 days: {app.recent(days=7).count()}")
        print(f"Search 'invoice': {app.search('invoice').count()}")

        # Q builder (composable)
        q = Q.subject("meeting") & Q.unseen()
        print(f"Unseen meetings: {app.inbox.where(q).count()}")

        # OR
        q = Q.from_("alice@x.com") | Q.from_("bob@x.com")
        print(f"From Alice or Bob: {app.inbox.where(q).count()}")

        # NOT
        q = ~Q.seen()
        print(f"Not seen: {app.inbox.where(q).count()}")

        # Date range
        q = Q.since(date(2025, 1, 1)) & Q.before(date(2025, 12, 31))
        print(f"Year 2025: {app.inbox.where(q).count()}")

        # Size filter
        q = Q.larger(1_000_000)
        print(f"Larger than 1MB: {app.inbox.where(q).count()}")

        # Query (Pydantic, validated)
        query = Query(subject="report", unseen=True, since=date(2025, 1, 1))
        print(f"Query: {app.inbox.where(query).count()}")

        # Check if exists
        if app.inbox.where(Q.unseen()).exists():
            print("You have unread emails!")


if __name__ == "__main__":
    main()

4. Send

Send emails via SMTP.

# Code above omitted 👆

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

# Code below omitted 👇
👀 Full file preview
"""Send emails via SMTP."""

from email_profile import Email


def main() -> None:
    with Email.from_env() as app:
        # Simple send
        app.send(
            to="recipient@example.com",
            subject="Hello",
            body="Hi there!",
        )

        # With HTML
        app.send(
            to="recipient@example.com",
            subject="Report",
            body="See attached report.",
            html="<h1>Report</h1><p>See attached.</p>",
        )

        # With attachments
        app.send(
            to=["alice@x.com", "bob@x.com"],
            subject="Files",
            body="Here are the files.",
            attachments=["report.pdf", "data.csv"],
        )

        # With CC/BCC
        app.send(
            to="recipient@example.com",
            subject="Meeting",
            body="Tomorrow at 10am.",
            cc="manager@example.com",
            bcc="hr@example.com",
        )

        # Reply to an email
        msg = list(app.inbox.where().messages(chunk_size=1))[0]
        app.reply(msg, body="Thanks for your email!")

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


if __name__ == "__main__":
    main()

5. Sync

Backup your mailbox to a local SQLite database.

# Code above omitted 👆

    with Email.from_env() as app:
        # Sync all mailboxes (compares by Message-ID)
        result = app.sync()
        print(f"Synced: {result.inserted} new, {result.skipped} skipped")

# Code below omitted 👇
👀 Full file preview
"""Sync and restore emails."""

from email_profile import Email


def main() -> None:
    with Email.from_env() as app:
        # Sync all mailboxes (compares by Message-ID)
        result = app.sync()
        print(f"Synced: {result.inserted} new, {result.skipped} skipped")

        # Sync one mailbox
        result = app.sync(mailbox="INBOX")
        print(f"INBOX: {result.inserted} new")

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

        # Restore all mailboxes to server (compares by Message-ID)
        count = app.restore()
        print(f"Restored {count} emails")

        # Restore one mailbox
        count = app.restore(mailbox="INBOX")
        print(f"Restored {count} to INBOX")

        # Force re-upload (skip duplicate check)
        count = app.restore(skip_duplicates=False)

        # Control parallelism
        result = app.sync(max_workers=5)
        count = app.restore(max_workers=5)


if __name__ == "__main__":
    main()

Full Code

"""Read your unread mail in 4 lines."""

from email_profile import Email


def main() -> None:
    with Email.from_env() as app:
        for msg in app.unread().messages():
            print(f"{msg.date}  {msg.from_:<40}  {msg.subject}")


if __name__ == "__main__":
    main()

Reference