Metadata-Version: 2.4
Name: imap_reader
Version: 1.0.3
Summary: Simple Python library for reading verification emails via IMAP — OTP extraction, link parsing, auto IMAP detection.
Author-email: nabilunnuha <nabilunnuha@gmail.com>
License: MIT
Project-URL: Homepage, https://github.com/nabilunnuha/imap_reader
Project-URL: Bug Tracker, https://github.com/nabilunnuha/imap_reader/issues
Keywords: imap,email,otp,verification,imap-reader,login-email
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.8
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: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Topic :: Communications :: Email
Requires-Python: >=3.8
Description-Content-Type: text/markdown

# imap_reader

> Simple, clean Python library for reading verified emails via IMAP — with OTP extraction, verification link parsing, and auto IMAP detection.

**Zero external dependencies** — uses Python standard library only (`imaplib`, `email`, `re`, `threading`).

---

## Installation

```bash
pip install imap_reader
```

or

```bash
pip install git+https://github.com/nabilunnuha/imap_reader.git
```

---

## Quick Start

```python
from imap_reader import ImapClient

with ImapClient(
        email_address="user@gmail.com",
        password_email="your_app_password",
    ) as client:

    for msg in client.get_messages("UNSEEN"):
        print(msg.subject)
        print(msg.otp_codes) # ['123456']
        print(msg.verification_links) # ['https://example.com/verify?token=...']
```

---

## Features

- **IMAP auto-detect** — automatically detects the IMAP host of an email domain
- **OTP extraction** — extracts a 5–6-digit code from the email body
- **Verification link extraction** — finds and prioritizes verification/confirmation links
- **HTML email parser** — converts HTML emails to plain text for extraction
- **Login timeout** — raises an error if the connection timeout is exceeded (30–60 seconds)
- **Context manager** — use the `with` statement to automatically close the connection
- **Delete messages** — delete emails based on criteria (`ALL`, `SEEN`, `UNSEEN`)
- **Wait for message** — poll the mailbox until a matching email arrives or timeout is reached
- **Message count** — get the number of matching emails without fetching content
- **List folders** — discover all available IMAP folders on the server
- **Mark as read/unread** — mark emails without deleting them
- **Sender & subject filter** — filter emails by sender or subject keyword directly via IMAP
- **Type hints** — fully typed, IDE-friendly, and autocomplete-friendly

---

## Usage

### Basic — Read unseen emails

```python
from imap_reader import ImapClient

with ImapClient("user@gmail.com", "app_password") as client:
    for msg in client.get_messages("UNSEEN"):
        print(f"From    : {msg.sender}")
        print(f"Subject : {msg.subject}")
        print(f"OTP     : {msg.otp_codes}")
        print(f"Links   : {msg.verification_links}")
```

### Retrieve only 1 recent email

```python
with ImapClient("user@gmail.com", "app_password") as client:
    msg = client.get_latest_message("UNSEEN")
    if msg:
        print(msg.otp_codes)
```

### Custom timeout (30–60 seconds recommended)

```python
with ImapClient(
        email_address="user@gmail.com",
        password_email="app_password",
        timeout=60, # seconds
    ) as client:
    msg = client.get_latest_message()
```

### Read from specified folder

```python
with ImapClient("user@gmail.com", "app_password") as client:
    for msg in client.get_messages("ALL", folder="SPAM"):
        print(msg.subject)
```

### Filter by sender or subject

Filter emails directly at the IMAP level — faster than fetching all and filtering in Python.

```python
with ImapClient("user@gmail.com", "app_password") as client:
    # Filter by sender
    for msg in client.get_messages("UNSEEN", from_sender="noreply@github.com"):
        print(msg.otp_codes)

    # Filter by subject keyword
    for msg in client.get_messages("UNSEEN", subject_contains="verification code"):
        print(msg.subject)

    # Combine both filters
    msg = client.get_latest_message(
        "UNSEEN",
        from_sender="noreply@google.com",
        subject_contains="OTP",
    )
```

### Wait for a new email (polling)

Polls the mailbox until a matching email arrives or the timeout is reached.
Ideal for automation workflows that need to wait for an OTP after triggering a login or signup.

```python
with ImapClient("user@gmail.com", "app_password") as client:
    msg = client.wait_for_message(
        from_sender="noreply@github.com",
        subject_contains="verification",
        wait_timeout=90,   # max seconds to wait
        interval=5,        # check every 5 seconds
        on_waiting=lambda elapsed: print(f"Waiting... {elapsed}s"),
    )
    if msg:
        print("OTP:", msg.otp_codes[0])
    else:
        print("Timed out, no email received.")
```

### Count emails without fetching content

```python
with ImapClient("user@gmail.com", "app_password") as client:
    total  = client.get_message_count("ALL")
    unread = client.get_message_count("UNSEEN")
    print(f"{unread} unread of {total} total")

    # Also supports sender/subject filters
    count = client.get_message_count("UNSEEN", from_sender="noreply@github.com")
```

### List all available folders

```python
with ImapClient("user@gmail.com", "app_password") as client:
    folders = client.list_folders()
    print(folders)
    # ['INBOX', 'Sent', 'Spam', 'Trash', '[Gmail]/All Mail', ...]
```

### Mark emails as read / unread

```python
with ImapClient("user@gmail.com", "app_password") as client:
    # Mark all unread emails as read
    count = client.mark_as_read("UNSEEN")
    print(f"Marked {count} emails as read.")

    # Mark all read emails as unread
    count = client.mark_as_unread("SEEN")
    print(f"Marked {count} emails as unread.")
```

### Delete email

```python
with ImapClient("user@gmail.com", "app_password") as client:
    # Delete all read emails
    count = client.delete_messages("SEEN")
    print(f"{count} deleted emails")

    # Delete all emails in INBOX
    count = client.delete_messages("ALL", folder="INBOX")

    # Delete unread emails in SPAM
    count = client.delete_messages("UNSEEN", folder="SPAM")
```

### Add custom IMAP provider

```python
from imap_reader import ImapClient
from imap_reader.models import DEFAULT_IMAP_LIST, ImapItemModel

DEFAULT_IMAP_LIST.append(
    ImapItemModel(domain="mycompany.com", imap_host="imap.mycompany.com")
)

with ImapClient("user@mycompany.com", "password") as client:
    for msg in client.get_messages():
        print(msg)
```

### Error handling

```python
from imap_reader import ImapClient
from imap_reader.exceptions import (
    AuthenticationError,
    ConnectionTimeoutError,
    ImapNotFoundError,
)

try:
    with ImapClient("user@gmail.com", "wrong_password", use_default_imap=False, timeout=10) as client:
        msg = client.get_latest_message()
except AuthenticationError:
    print("Email or password is incorrect.")
except ConnectionTimeoutError:
    print("Connection to IMAP server timed out.")
except ImapNotFoundError:
    print("Email domain not recognized, add manually to imap_list.")
```

---

## API Reference

### `ImapClient(email_address, password_email, imap_list=None, use_default_imap=True, timeout=30)`

| Parameter          | Type   | Default             | Description                      |
| ------------------ | ------ | ------------------- | -------------------------------- |
| `email_address`    | `str`  | —                   | Complete email address           |
| `password_email`   | `str`  | —                   | IMAP password or App Password    |
| `imap_list`        | `list` | `DEFAULT_IMAP_LIST` | List of IMAP providers           |
| `use_default_imap` | `bool` | `True`              | Auto-fallback to `mail.{domain}` |
| `timeout`          | `int`  | `30`                | Login timeout in seconds         |

### Methods

| Method                                                                                                  | Returns                   | Description                              |
| ------------------------------------------------------------------------------------------------------- | ------------------------- | ---------------------------------------- |
| `get_messages(criteria, folder, limit, mark_seen, from_sender, subject_contains)`                       | `Generator[EmailMessage]` | Fetch and yield messages                 |
| `get_latest_message(criteria, folder, from_sender, subject_contains)`                                   | `EmailMessage \| None`    | Fetch 1 most recent message              |
| `wait_for_message(criteria, folder, from_sender, subject_contains, wait_timeout, interval, on_waiting)` | `EmailMessage \| None`    | Poll until email arrives or timeout      |
| `get_message_count(criteria, folder, from_sender, subject_contains)`                                    | `int`                     | Count matching messages without fetching |
| `list_folders()`                                                                                        | `list[str]`               | List all available IMAP folders          |
| `mark_as_read(criteria, folder)`                                                                        | `int`                     | Mark matching messages as read           |
| `mark_as_unread(criteria, folder)`                                                                      | `int`                     | Mark matching messages as unread         |
| `delete_messages(criteria, folder)`                                                                     | `int`                     | Delete matching messages                 |

### `EmailMessage`

| Attribute            | Type        | Description                          |
| -------------------- | ----------- | ------------------------------------ |
| `uid`                | `str`       | Unique message ID on the server      |
| `subject`            | `str`       | Decoded email subject                |
| `sender`             | `str`       | Decoded sender address               |
| `date`               | `str`       | Raw date string from email header    |
| `body_text`          | `str`       | Plain text body                      |
| `body_html`          | `str`       | HTML body                            |
| `otp_codes`          | `list[str]` | Extracted 5–6 digit OTP codes        |
| `verification_links` | `list[str]` | Extracted verification/confirm links |

---

## Gmail Setup

Gmail requires an **App Password**, not your regular password.

1. Open [Google Account](https://myaccount.google.com/) → **Security**
2. Enable **2-Step Verification**
3. Open **App Passwords** → Generate a new password
4. Use that password as `password_email`

---

## License

MIT
