Quick Start
This guide will help you get started with EZPI.
Installation
For CLI usage, install with pipx:
pipx install ezpi
For library usage, install with pip:
pip install ezpi
Writing Messages
EZPI manages public-inbox v2 repositories automatically, creating the directory structure and rotating epochs as needed.
From Bytes
If you have raw RFC822 message data:
import ezpi
msg_bytes = b"""From: sender@example.com
Subject: Hello World
This is the message body.
"""
ezpi.add_rfc822_v2('/path/to/inbox', msg_bytes)
From Message Objects
Using Python’s email library:
import email.message
import ezpi
msg = email.message.EmailMessage()
msg['From'] = 'sender@example.com'
msg['Subject'] = 'Hello World'
msg.set_content('This is the message body.')
ezpi.add_rfc822_v2('/path/to/inbox', msg)
From Plaintext
For plaintext content, use single-repo mode (see below):
import ezpi
repo_path = '/path/to/inbox/git/0.git'
ezpi.add_plaintext(
repo_path,
content='This is the message body.',
subject='Hello World',
authorname='Sender Name',
authoremail='sender@example.com',
)
Using the CLI
EZPI provides a command-line interface:
# Add an RFC822 message to a v2 inbox
ezpi --v2-path /path/to/inbox --rfc822 < message.eml
# Add plaintext to a v2 inbox
echo "Message body" | ezpi --v2-path /path/to/inbox \
-f "Sender <sender@example.com>" \
-s "Subject line"
See Command Line Interface for full CLI documentation and Public-Inbox v2 Format for v2 format details.
Reading Messages
Added in version 0.6.
EZPI can also read messages out of a public-inbox v2 repository that someone
else has populated (for example, a local clone of lore.kernel.org). EZPI
does not fetch or clone remotes for you – keep the repository up to
date yourself with git pull, lei up, rsync, or whatever tool
fits your workflow.
Named Cursors
Readers identify themselves with a cursor name. EZPI remembers, per
cursor, the last commit seen in each epoch so subsequent calls only yield
new messages. Cursors are plain JSON files stored next to the inbox
({inbox}/ezpi-cursor.{name}.json).
First use (default): cursor is initialized at the current HEAD of every epoch and nothing is yielded. Only messages added after that first call will be delivered to the cursor:
import ezpi
# First call -- seeds the cursor at HEAD, yields nothing.
for epoch, commit, msg_bytes in ezpi.iter_new_messages('/path/to/inbox', 'myapp'):
...
# Remote has added two messages; sync the repo first, then:
for epoch, commit, msg_bytes in ezpi.iter_new_messages(
'/path/to/inbox', 'myapp', auto_advance=True,
):
process(msg_bytes) # yielded twice
Pass start='beginning' to walk the full history on first use instead:
for epoch, commit, msg_bytes in ezpi.iter_new_messages(
'/path/to/inbox', 'myapp', start='beginning',
):
process(msg_bytes)
Manual vs. Automatic Advance
With auto_advance=True, EZPI persists cursor state after each yielded
message. If your processing can fail partway through (e.g., you deliver to
a flaky IMAP server), use manual advance so the cursor only moves once you
confirm success:
for epoch, commit, msg_bytes in ezpi.iter_new_messages('/path/to/inbox', 'myapp'):
if deliver(msg_bytes):
ezpi.save_cursor('/path/to/inbox', 'myapp', epoch, commit, msg_bytes)
New Epochs
When a public-inbox rolls over to a new epoch, EZPI notices automatically
on the next iter_new_messages call and walks the new epoch from its
first commit. No caller action is required.
Rebase Recovery
public-inbox repositories occasionally rewrite history to permanently delete a message. When EZPI detects that the cursor’s stored commit is no longer on the current history, it locates the new position by matching on Message-ID and Subject (anchored by the stored commit date) and resumes. Recovery is automatic – no special handling in caller code.
Note
Rebase recovery uses git rev-list --since-as-filter, which requires
git 2.40 or newer. Ordinary message reading works on any git.
Single-Repo Mode
For scenarios where v2 epoch management is not needed (e.g., publishing to hosts that only support single-level repositories), you can write directly to a bare git repository:
git init --bare /path/to/messages.git
Then use the non-v2 functions:
import ezpi
ezpi.add_rfc822('/path/to/messages.git', msg_bytes)
Or via CLI:
ezpi -r /path/to/messages.git --rfc822 < message.eml