# aiomail Async IMAP one-time-code retrieval. Reads OTP / login codes out of IMAP mailboxes **you own**, with password or OAuth2 (XOAUTH2) auth and dynamic sender / subject / code matching. This reads codes from email; it does not generate them (that is `pyotp`'s job). ## Install `requirements.txt`: ``` aiomail @ git+ssh://git@git.rethinkstudios.io/rethink-public/aiomail.git@v0.1.0 # OAuth token providers (Microsoft / Google) need the extra: aiomail[oauth] @ git+ssh://git@git.rethinkstudios.io/rethink-public/aiomail.git@v0.1.0 ``` Direct: ```bash pip install "aiomail @ git+ssh://git@git.rethinkstudios.io/rethink-public/aiomail.git@v0.1.0" pip install "aiomail[oauth] @ git+ssh://git@git.rethinkstudios.io/rethink-public/aiomail.git@v0.1.0" ``` Requires `aioimaplib` and `beautifulsoup4` (pulled transitively). The `oauth` extra adds `aiohttp` for the refresh-token providers. ## Password auth ```python from aiomail import IMAPClient, PasswordAuth, retrieve_otp client = IMAPClient(PasswordAuth(user, password), host="outlook.office365.com") async with client: code = await retrieve_otp(client, sender="no-reply@privy.io", subject="login code") ``` ## OAuth2 auth Pass a static access token, or a token provider that refreshes one on connect: ```python from aiomail import IMAPClient, OAuth2Auth, retrieve_otp from aiomail.oauth import MicrosoftTokenProvider auth = OAuth2Auth(user, token_provider=MicrosoftTokenProvider(client_id, refresh_token)) client = IMAPClient(auth, host="outlook.office365.com") async with client: code = await retrieve_otp(client, sender="no-reply@privy.io") ``` `GoogleTokenProvider(client_id, refresh_token, client_secret)` is also provided. Credentials are always supplied by you — nothing is hardcoded. ## Dynamic matching `sender` and `subject` accept a substring, a compiled regex, or a callable: ```python import re await retrieve_otp(client, sender="uber.com") # substring await retrieve_otp(client, sender=re.compile(r"no-?reply@.*\.io")) # regex await retrieve_otp(client, sender=lambda f: f.endswith("@x.com")) # callable ``` Code extraction is tunable too — `patterns` (regexes, first group wins) and `lengths` (standalone digit-run fallback): ```python from aiomail import extract_code extract_code(message, patterns=[r"PIN[:\s]+(\d{6})"], lengths=(6,)) ``` ## Scope `retrieve_otp` walks `folders` newest-first, filters by sender/subject, extracts a code, and applies `max_age` (seconds; `None` disables). Provider quirks (folder names, code lengths, freshness) are parameters, not hardcoded branches. For mailboxes / accounts you own and control. ## Verification status Pure logic (`extract_code`, `as_predicate`, `retrieve_otp`) and the IMAP entrypoints are verified against the installed `aioimaplib` API. The **live-server paths are not fully tested**: end-to-end XOAUTH2 login against real Outlook/Gmail, the Microsoft/Google refresh-token exchange (scopes may need adjusting to your app registration), and the iCloud `(BODY[])` fetch. Password IMAP and the matching logic work; **confirm OAuth end-to-end against your own mailbox before relying on it in production.**