Skip to main content

Async email client for firstmail.ltd — powered by aioimaplib & aiosmtplib.

Project description

firstmail-py

PyPI Python License: MIT

Async email client for firstmail.ltd — powered by aioimaplib & aiosmtplib.

Full async/await rewrite of nichind/firstmail with IMAP IDLE support, OTP extraction, typed exceptions, and a clean Pythonic API.

Installation

pip install firstmail-py

Quick Start

import asyncio
from firstmail import FirstMail

async def main():
    async with FirstMail("user@firstmail.ltd", "password") as client:
        # Latest email
        last = await client.get_last_mail()
        if last:
            print(last.subject, last.sender)

        # Inbox count
        print(await client.get_message_count())

        # Fetch newest 10
        emails = await client.get_all_mail(limit=10)

        # Extract OTP code from latest email
        otp = await client.get_otp_code()
        print(f"OTP: {otp}")

asyncio.run(main())

Usage

Context Manager (recommended)

async with FirstMail("user@firstmail.ltd", "password") as client:
    count = await client.get_message_count()

Manual Resource Management

client = FirstMail("user@firstmail.ltd", "password")
try:
    count = await client.get_message_count()
finally:
    await client.close()

Credential String

client = FirstMail("user@firstmail.ltd:password")

Send Email

async with FirstMail("user@firstmail.ltd", "password") as client:
    await client.send_mail(
        to="recipient@example.com",
        subject="Hello",
        body="World!",
        cc="cc@example.com",
        bcc="bcc@example.com",
    )

Watch for New Emails

Uses IMAP IDLE for real-time push (falls back to polling automatically):

async with FirstMail("user@firstmail.ltd", "password") as client:
    async for email in client.watch_for_new_emails(check_interval=30):
        print(f"New: {email.subject} from {email.sender}")

Wait for Specific Emails

async with FirstMail("user@firstmail.ltd", "password") as client:
    # Wait for any new email (max 60s)
    msg = await client.wait_for_new_mail(timeout=60)

    # Wait for email from a specific sender (substring, case-insensitive)
    msg = await client.wait_for_sender("openai.com", timeout=60)

    # Wait for email with specific subject text
    msg = await client.wait_for_email("verification code", timeout=60)

OTP Code Extraction

async with FirstMail("user@firstmail.ltd", "password") as client:
    # Immediate — parse OTP from current inbox
    code = await client.get_otp_code()                        # "719680"
    code = await client.get_otp_code(sender="openai.com")     # filter by sender

    # Wait mode — wait for new OTP email, then extract
    code = await client.get_otp_code(sender="openai.com", timeout=60)

OTP extraction priority:

  1. HTML heading tags (<h1>123456</h1>)
  2. Subject line digits (4–8 digits)
  3. Plain-text body digits

EmailMessage

Every email is returned as an EmailMessage dataclass:

msg.subject      # str
msg.sender       # str
msg.recipient    # str
msg.body         # str  (plain text)
msg.html_body    # str | None  (HTML body if present)
msg.date         # str
msg.message_id   # str
msg.raw_message  # bytes | None

msg.to_dict()    # serialize to dict (excludes raw_message)

Exception Handling

All exceptions inherit from FirstMailError:

from firstmail import (
    FirstMailError,            # base — catch-all
    FirstMailConnectionError,  # server unreachable
    FirstMailAuthError,        # wrong credentials
    FirstMailSendError,        # send failed
    FirstMailFetchError,       # fetch failed
    FirstMailTimeoutError,     # operation timed out
    FirstMailParseError,       # email parsing failed
)

try:
    async with FirstMail("user@firstmail.ltd", "wrong") as client:
        await client.get_last_mail()
except FirstMailAuthError:
    print("Bad credentials")
except FirstMailTimeoutError:
    print("Timed out")
except FirstMailError as e:
    print(f"Error: {e}")

CLI

firstmail -e <email> -p <password> read-last
firstmail -e <email> -p <password> read-last --json
firstmail -e <email> -p <password> read-all --limit 10 --full
firstmail -e <email> -p <password> send --to user@example.com --subject "Hi" --body "Hello!"
firstmail -e <email> -p <password> watch --interval 60 --show-body
firstmail -e <email> -p <password> count

# Credential string shortcut
firstmail "user@firstmail.ltd:password" read-last

# Module invocation
python -m firstmail <command>

API Reference

Constructor

FirstMail(
    address: str,                          # email or "email:password"
    password: str | None = None,
    *,
    use_ssl: bool = True,                  # SSL/TLS (port 993/465)
    imap_host: str = "imap.firstmail.ltd",
    smtp_host: str = "imap.firstmail.ltd",
    imap_port: int | None = None,          # auto: 993 (SSL) / 143
    smtp_port: int | None = None,          # auto: 465 (SSL) / 587
    timeout: float = 30.0,                 # connection timeout (seconds)
)

Methods

Method Returns Description
await get_last_mail() EmailMessage | None Most recent email
await get_all_mail(limit=None) list[EmailMessage] All emails, newest first
await send_mail(to, subject, body, *, cc, bcc) bool Send plain-text email
await get_message_count() int Inbox message count
async for msg in watch_for_new_emails(check_interval, *, use_idle) EmailMessage Yield new emails as they arrive
await wait_for_new_mail(*, timeout, check_interval) EmailMessage Block until a new email arrives
await wait_for_sender(sender, *, timeout, check_interval) EmailMessage Block until email from sender arrives
await wait_for_email(title_contains, *, timeout, check_interval) EmailMessage Block until email with matching subject arrives
await get_otp_code(*, sender, timeout, check_interval) str | None Extract OTP code (4–8 digits) from latest or next email
await close() None Close all connections

Exceptions

Exception Parent Raised when
FirstMailError Exception Base for all errors
FirstMailConnectionError FirstMailError Cannot connect to IMAP/SMTP server
FirstMailAuthError FirstMailError Login credentials rejected
FirstMailSendError FirstMailError Email send operation failed
FirstMailFetchError FirstMailError Email fetch operation failed
FirstMailTimeoutError FirstMailError Operation exceeded timeout
FirstMailParseError FirstMailError Raw email bytes could not be parsed

Server Info

Protocol Host SSL Port Non-SSL Port
IMAP imap.firstmail.ltd 993 143
POP3 imap.firstmail.ltd 995 110
SMTP imap.firstmail.ltd 465 587

License

MIT

Project details


Download files

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

Source Distribution

firstmail_py-0.1.0.tar.gz (18.6 kB view details)

Uploaded Source

Built Distribution

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

firstmail_py-0.1.0-py3-none-any.whl (14.1 kB view details)

Uploaded Python 3

File details

Details for the file firstmail_py-0.1.0.tar.gz.

File metadata

  • Download URL: firstmail_py-0.1.0.tar.gz
  • Upload date:
  • Size: 18.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.0

File hashes

Hashes for firstmail_py-0.1.0.tar.gz
Algorithm Hash digest
SHA256 5150f4f7bee071407ac556b0c92c673d681cc13f81a1659bd37abd348128424d
MD5 5a16f9999c16421efa68482fcd5c0d27
BLAKE2b-256 8be3bbf194fa19dc453ce2947a18a26d53b7a618fd05f121fa5bab134cbd0aac

See more details on using hashes here.

File details

Details for the file firstmail_py-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: firstmail_py-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 14.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.0

File hashes

Hashes for firstmail_py-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 fd6848bfd8ece3d54c53c76b406c7dedf6b8fe322b5857b69299d1d4c0b02151
MD5 59a97c6765a8d477ad67df285b5ced6f
BLAKE2b-256 ee90c04222b2871dc54a55f145ea249b2970baeaa4957312d9a7eddc12f74b88

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page