Metadata-Version: 2.4
Name: mailpeek
Version: 0.1.1
Summary: A lightweight IMAP-based email reader for Python/Django
License: MIT
License-File: LICENSE
Keywords: email,imap,inbox,django,cli,attachment,idle
Author: Anand R Nair
Author-email: anand547@outlook.com
Requires-Python: >=3.9
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Requires-Dist: imapclient (>=3.0.1)
Requires-Dist: pyzmail36 (>=1.0.5)
Project-URL: Homepage, https://github.com/anandrnair547/mailpeek
Project-URL: Repository, https://github.com/anandrnair547/mailpeek
Description-Content-Type: text/markdown

# mailpeek

A lightweight Python library for reading unread emails via IMAP.

Requires Python 3.9+.

## Installation

```bash
poetry add mailpeek
```

Or with pip:

```bash
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](CHANGELOG.md) for the details.

## Basic Usage

```python
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

```python
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:

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

### Only images:

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

To keep only the emails that actually have a match:

```python
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()`:

```python
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:

```python
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)

```python
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:

```python
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:

```bash
poetry add mailpeek
```

Run with:

```bash
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:

```bash
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:

```bash
--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

```bash
poetry install
poetry run pytest
```

## Releasing to PyPI

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

```bash
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:

```bash
# 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:

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

## Changelog

See [CHANGELOG.md](CHANGELOG.md).

## License

MIT — see [LICENSE](LICENSE).

