Skip to main content

A Python email library designed for intuitive email operations

Project description

mailcore

A Python email library designed for intuitive email operations.

mailcore enables developers to work with email using natural, chainable methods:

# Send an email
await mailbox.send(to="user@example.com", subject="Meeting confirmed", body="See you tomorrow at 2pm.")

# Reply with attachments
await message.reply().attach("diagram.png").body("Here you are").send()

# Query and process messages
async for msg in mailbox.inbox.from_('alice@example.com').unseen():
    ...
    await msg.mark_read()

Built on async Python with IMAP/SMTP protocol abstraction, type safety, and lazy loading for performance.


Installation

pip install mailcore mailcore-imapclient mailcore-aiosmtplib

Getting Started

1. Connect to your mailbox:

from mailcore import Mailbox
from mailcore_imapclient import IMAPClientAdapter
from mailcore_aiosmtplib import AIOSMTPAdapter

# Create adapters (works with Gmail, Outlook, any IMAP/SMTP server)
imap = IMAPClientAdapter(
    host="imap.gmail.com",
    port=993,
    username="you@gmail.com",
    password="your-app-password",
    ssl=True
)

smtp = AIOSMTPAdapter(
    host="smtp.gmail.com",
    port=465,
    username="you@gmail.com",
    password="your-app-password",
    use_tls=True
)

mailbox = Mailbox(imap=imap, smtp=smtp)

2. Send emails:

# Simple send
await mailbox.send(
    to="friend@example.com",
    subject="Hello!",
    body="Just saying hi!"
)

# With attachments (use draft for fluent interface)
await mailbox.draft().to("team@example.com").subject("Report") \
    .body("See attached.") \
    .attach("/path/to/report.pdf") \
    .send()

# Mix fluent API with send() parameters (override at send time)
draft = mailbox.draft().to("user@example.com").subject("Draft subject").body("Draft body")
await draft.send(subject="Final subject", body="Overridden at send time")

3. Read emails:

# Count messages
count = await mailbox.inbox.count()

# List recent messages
messages = await mailbox.inbox.list(limit=10)
for msg in messages:
    print(f"{msg.subject} from {msg.from_.email}")

# Get first message
message = await mailbox.inbox.first()

# Retrieve message body (lazy loading)
text_body = await message.body.get_text()
html_body = await message.body.get_html()

4. Work with drafts:

# Save a draft for later
draft = mailbox.draft().to("user@example.com").subject("Draft subject").body("Work in progress")
await draft.save(folder='Drafts')

# Retrieve and edit the saved draft
draft_message = await mailbox.folders['Drafts'].first()
editable = await draft_message.edit()
await editable.body("Updated content").send()

5. Filter messages:

# Filter by sender
messages = await mailbox.inbox.from_('alice@example.com').list()

# Filter by subject
messages = await mailbox.inbox.subject('meeting').list()

# Combine multiple filters (chainable)
messages = await mailbox.inbox \
    .from_('alice@example.com') \
    .subject('urgent') \
    .unseen() \
    .list()

6. Reply and forward:

# Reply with quoted original message (quote=True is default)
message = await mailbox.inbox.first()
await message.reply().send(body="Thanks for reaching out!")

# Reply without quoting
await message.reply(quote=False).send(body="Acknowledged.")

# Forward with attachments (include_attachments=True is default)
await message.forward().send(to="colleague@example.com", body="FYI")

# Forward without attachments
await message.forward(include_attachments=False).send(to="colleague@example.com", body="FYI")

7. Work with attachments:

# Download attachments
message = await mailbox.inbox.first()
for attachment in message.attachments:
    await attachment.save('downloads/')

# Check attachment metadata (no download)
if message.has_attachments:
    print(f"Found {message.attachment_count} attachments")
    for att in message.attachments:
        print(f"- {att.filename} ({att.size} bytes)")

8. Work with folders:

# List all folders
folder_names = await mailbox.list_folders()
print(f"Available folders: {folder_names}")

# Access specific folders (folder names vary by server)
sent = mailbox.folders['Sent']  # or 'INBOX.Sent', '[Gmail]/Sent', etc.
archive = mailbox.folders['Archive']

# Search in specific folder
messages = await sent.to('client@example.com').list()

# Create, rename, and delete folders
await mailbox.create_folder('Projects/2025')
await mailbox.rename_folder('Old Archive', 'Archive 2024')
await mailbox.delete_folder('Temporary')

# Move and organize individual messages
message = await mailbox.inbox.first()
await message.move_to('Archive')

9. Bulk operations:

# Move multiple messages at once
spam_messages = await mailbox.inbox.from_('spam@example.com').list()
await mailbox.move(spam_messages, to_folder='Spam')

# Copy messages to another folder
important = await mailbox.inbox.flagged().list()
await mailbox.copy(important, to_folder='Archive')

# Bulk delete
old_messages = await mailbox.inbox.seen().list(limit=100)
await mailbox.delete(old_messages, trash_folder="Trash")  # Moves to trash
await mailbox.delete(old_messages, permanent=True)  # Permanent delete

10. Email monitoring:

import asyncio

async def watch_inbox(mailbox, handler):
    """
    Poll for new messages with 10-second latency.
    
    For real-time (sub-second latency), use mailreactor with IDLE support.
    https://github.com/mailreactor/mailreactor 
    """
    last_uid = 0
    
    while True:
        # Fetch only new messages (after last seen UID)
        new_messages = await mailbox.inbox.uid_range(last_uid + 1, "*").list()
        
        # Process messages in chronological order (oldest first)
        for message in sorted(new_messages, key=lambda m: m.uid):
            await handler(message)
            last_uid = message.uid  # Track sequentially
        
        await asyncio.sleep(10)  # Poll every 10 seconds

# Usage
async def handle_new_message(message):
    print(f"New email: {message.subject}")
    if "urgent" in message.subject.lower():
        await message.reply().send(body="Got it! Working on it.")

await watch_inbox(mailbox, handle_new_message)

Development

Setup

# Clone repository
git clone https://github.com/mailreactor/mailcore.git
cd mailcore

# Enter development environment (using Nix)
nix develop

# Create virtual environment (as prompted by shell)
uv venv --python $(which python)
source .venv/bin/activate

# Install dependencies
uv pip install -e ".[dev]"

# Verify setup
python verify-setup.sh

Running Tests

pytest tests/

Pre-commit Checks

pre-commit run --all-files --show-diff-on-failure

License

MIT License - see LICENSE for details


Credits

Built as part of the Mail Reactor project—an MCP/REST server that provides programmatic email access through webhooks and API endpoints. Mail Reactor uses mailcore as its foundational email protocol abstraction layer.

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

mailcore-0.1.0.tar.gz (117.2 kB view details)

Uploaded Source

Built Distribution

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

mailcore-0.1.0-py3-none-any.whl (46.2 kB view details)

Uploaded Python 3

File details

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

File metadata

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

File hashes

Hashes for mailcore-0.1.0.tar.gz
Algorithm Hash digest
SHA256 c0de81e30f0955a5554e2275dd0638243589de1531c0c0edbee58b2b5b543f46
MD5 f56f21433a1d91556324eb2ad5f8c94d
BLAKE2b-256 052785ea87de7b9f5eda185e31f19916b92cebf31b3f96e5a33a20ddcc15e616

See more details on using hashes here.

File details

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

File metadata

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

File hashes

Hashes for mailcore-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 6664acdb4d10e36ddc5e123372fc3041dcb70d3bc6af2c01df0efc0ee23927e0
MD5 f618c79a3d4b703fadbddf7d9f96a701
BLAKE2b-256 d31e07b7397f0bf934ff19c701c289ae48fe33dea3c2e94c35e6830bcca1f2b1

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