Skip to main content

mailpeek

A lightweight Python library for reading unread emails via IMAP.

Requires Python 3.9+.

Installation

poetry add mailpeek

Or with pip:

pip install mailpeek

Upgrading from 0.1.0? 0.1.1 repairs the attachment pipeline, which could not succeed in 0.1.0. It is a drop-in upgrade for almost everyone, but attachments[].part_id is now an int and limit=0 now means zero rather than unlimited. See the CHANGELOG for the details.

Basic Usage

from mailpeek.reader import EmailReader

reader = EmailReader(
    host="imap.gmail.com",
    email="your-email@gmail.com",
    password="your-app-password"
)

emails = reader.fetch_unread()
for mail in emails:
    print(mail["subject"], mail["from"])

Fetch All Emails with Limit

emails = reader.fetch_emails(unread_only=False, limit=10)

Filter Attachments

These filters narrow the attachments list within each email. They do not filter which emails are returned — an email with no matching attachment still comes back, with an empty attachments list.

Only PDFs:

emails = reader.fetch_unread(attachment_filename_contains=".pdf")

Only images:

emails = reader.fetch_unread(attachment_mime_startswith="image/")

To keep only the emails that actually have a match:

emails = [m for m in reader.fetch_unread(attachment_mime_startswith="image/") if m["attachments"]]

Fetch Attachments On-Demand

part_id is an integer index into the message's parts. Pass it straight back to get_attachment_stream():

for mail in emails:
    for att in mail["attachments"]:
        stream = reader.get_attachment_stream(mail["uid"], att["part_id"])
        with open(att["filename"], "wb") as f:
            f.write(stream.read())

Each call opens its own IMAP connection. To download several attachments over a single connection, use the reader as a context manager:

with reader:
    for mail in reader.fetch_unread():
        for att in mail["attachments"]:
            stream = reader.get_attachment_stream(mail["uid"], att["part_id"])
            with open(att["filename"], "wb") as f:
                f.write(stream.read())

Use with IMAP IDLE (Real-Time Mail Listener)

from mailpeek.imap_idle_listener import IMAPIdleListener

def on_new_mail(msg):
    print("\n📥 New email:", msg.get_subject())

def on_disconnect(error):
    print(f"🔌 Disconnected: {error}")

listener = IMAPIdleListener(
    host="imap.gmail.com",
    email="your-email@gmail.com",
    password="your-app-password",
    callback=on_new_mail,
    on_disconnect=on_disconnect,
)

listener.start()

If the connection drops, the listener calls on_disconnect(error), waits reconnect_delay seconds (default 10), and rebuilds the connection. Each message is handed to the callback exactly once. An exception raised inside your callback is logged and skipped — it won't kill the listener.

To stop listening:

listener.stop()

stop() blocks until the background thread has exited. Because idle_check() can be mid-wait, this may take up to idle_timeout seconds (default 300); lower idle_timeout if you need faster shutdown.

Django Integration

  • Create a management/commands/read_emails.py command that calls fetch_unread()
  • Use get_attachment_stream() to save files into FileField
  • Run via cron or Celery

CLI Usage

Install with:

poetry add mailpeek

Run with:

poetry run mailpeek --email your-email@gmail.com

You'll be prompted for the password. To avoid the prompt in scripts, use the env var:

export MAILPEEK_PASSWORD='your-app-password'
poetry run mailpeek --email your-email@gmail.com

--password still works, but avoid it: command-line arguments are visible to other users on the machine via ps, and land in your shell history.

Optional:

--all              # Fetch read + unread
--limit 20         # Only get 20 emails
--filename .pdf    # Only attachments with .pdf in name
--mime image/      # Only attachments starting with MIME image/
--timeout 30       # Socket timeout in seconds

Development

poetry install
poetry run pytest

Releasing to PyPI

Authenticate once. Mint a token at pypi.org/manage/account/token — scope it to the mailpeek project rather than the whole account — then store it:

poetry config pypi-token.pypi pypi-AgEIcHlwaS5vcmc...

Poetry keeps this in ~/.config/pypoetry/auth.toml (or your OS keyring), so you never pass the token on the command line, where it would land in shell history. If you'd rather not persist it, export POETRY_PYPI_TOKEN_PYPI instead.

To cut a release:

# 1. bump the version in pyproject.toml and src/mailpeek/__init__.py
# 2. add the release notes to CHANGELOG.md
poetry run pytest          # must be green
poetry build               # writes dist/*.whl and dist/*.tar.gz
poetry publish

A version can never be reused on PyPI once uploaded. To rehearse the upload against a throwaway index first:

poetry config repositories.testpypi https://test.pypi.org/legacy/
poetry config pypi-token.testpypi pypi-...
poetry publish --repository testpypi

Changelog

See CHANGELOG.md.

License

MIT — see LICENSE.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

mailpeek-0.1.1.tar.gz (13.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

mailpeek-0.1.1-py3-none-any.whl (9.2 kB view details)

Uploaded Python 3

File details

Details for the file mailpeek-0.1.1.tar.gz.

File metadata

  • Download URL: mailpeek-0.1.1.tar.gz
  • Upload date:
  • Size: 13.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: poetry/2.4.1 CPython/3.14.4 Darwin/25.5.0

File hashes

Hashes for mailpeek-0.1.1.tar.gz
Algorithm Hash digest
SHA256 6d4a5e98aae7509a646b78b712b3985cf41263dbcedde47663ad20978e1e29bf
MD5 db9fbb965655517757c484a6e1660031
BLAKE2b-256 1bd04ae845e7fbaf7be14234e972ad9a208ae4c7cb4d436b858f443d2999602b

See more details on using hashes here.

File details

Details for the file mailpeek-0.1.1-py3-none-any.whl.

File metadata

  • Download URL: mailpeek-0.1.1-py3-none-any.whl
  • Upload date:
  • Size: 9.2 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: poetry/2.4.1 CPython/3.14.4 Darwin/25.5.0

File hashes

Hashes for mailpeek-0.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 64c32b77784f8e52441ec82c4b31fa225011facc566e3f4c52b058ace9e5744c
MD5 76ae1fc16b25bff875be9fd7042f4833
BLAKE2b-256 6d397dd83ed679476f3f3a107323c10fd8e6013bd15a9ab67df0181bad59fce3

See more details on using hashes here.

Release history Release notifications | RSS feed

0.2.0

2 files

This release

0.1.1 This release

2 files

0.1.0

2 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