Skip to main content

mailsort

Python package codecov Documentation Status Code style: black

mailsort reduces the interruption caused by the increase of daily emails by automatically sorting your emails into folders (IMAP labels) based on the similarity of new messages to the ones you have already sorted. It uses machine learning to learn from your existing folder assignments and suggests or applies folders.

This folder based approach allows you to focus on the most important emails first, while still being able to access less important emails later. You are in contorl. When you change the folder assignments, you can retrain the mailsort machine learning model so mailsort can adapt to your changing preferences. Think of it as a personal assistant that helps you manage your emails.

mailsort is deliberately a plain Python library and command line tool rather than a hosted service: you run it yourself, on your own schedule (for example from cron), against your own local database. Your emails belong to you.

mailsort connects to any IMAP mail server, stores your emails locally in an SQLite database, and trains a machine learning model on the folders (IMAP labels) you have already assigned to your emails, and uses that model to suggest or apply folders to new messages.

To learn more about mailsort please have a look at the documentation below.

Installation

pip install mailsort

Command line interface

mailsort is organized around six subcommands - sync, train, predict, sort, status and evaluate:

mailsort sync --host imap.example.com --username user@example.com --password "..."
mailsort train
mailsort predict some_label
mailsort sort some_label
mailsort status
mailsort evaluate

The IMAP password is provided with --password, e.g. by having your shell pull it from a password manager.

  • sync downloads new and changed messages from the mail server into the local database.
  • train (re-)trains the machine learning model on the local database - it does not connect to the mail server, so it also works offline once sync has run at least once. Each folder's model is calibrated automatically where there is enough data to do so safely - see Evaluation and confidence.
  • predict FOLDER reports what the trained model would recommend for the messages currently in FOLDER, without moving, deleting or otherwise modifying anything on the server:
    MESSAGE ID                           SUBJECT                                  RECOMMENDED FOLDER    SCORE TYPE       ACCEPTED
    -----------------------------------------------------------------------------------------------------------------------------
    some_label\x1f101                     Your invoice for March                   Receipts               0.97 calibrated     True
    some_label\x1f102                     Let's catch up next week                 -                      0.00 raw           False
    
  • sort FOLDER does the same scoring as predict, but actually moves the messages whose score clears the configured threshold (--recommendation-ratio, 90% by default). That threshold is a cutoff on a model score, not a guaranteed probability of being correct - use mailsort evaluate to check what it actually achieves on your own mailbox before trusting it.
  • status reports the local database location, how many messages it knows about, and how many per-folder models have been trained - also without connecting to the mail server.
  • evaluate estimates precision, recall, F1, support and coverage/abstention rate at --recommendation-ratio on held-out data - the evidence a threshold choice should be based on. Add --sweep to compare several thresholds at once. It never connects to the mail server and never changes the models mailsort train has already stored - see Evaluation and confidence.

Run mailsort --help or mailsort <command> --help for the full list of options and examples.

Python interface

The recommended way to use mailsort from Python is MailSorter, a small facade that wraps a mailbox backend (such as Imap) and exposes the fetch-store-train-predict-move loop through the same vocabulary as the CLI:

from mailsort import Imap, MailSorter

with MailSorter(
    Imap(
        host="imap.example.com",
        port=993,
        username="user@example.com",
        password="...",
        connection_str="sqlite:///email.db",
    )
) as sorter:
    sorter.sync()
    sorter.train()
    predictions = sorter.predict("some_label")
    sorter.sort("some_label")
  • sync() downloads new and changed messages into the local database and returns a SyncResult.
  • train() (re-)trains the machine learning models on the local database and returns a TrainResult.
  • predict(folder) is read-only: it downloads and scores the messages currently in folder and returns a list[Prediction], without moving, deleting or otherwise modifying anything on the server.
  • sort(folder) scores messages the same way as predict(), but actually moves the ones whose score clears recommendation_ratio (90% by default), and returns a SortResult.

MailSorter only relies on the public AbstractMailBox interface, not on anything IMAP-specific, so it works unmodified with any current or future mailbox backend - including a Gmail backend built by gmailsorter.

Dry run / recommendation mode

predict() computes the exact same machine learning predictions sort() would act on, but only returns them - it never moves, deletes, archives or otherwise modifies anything on the server. Each message gets a Prediction, a plain, JSON-serializable dataclass - a first-class, side-effect-free representation of one classification result, independent of any mailbox change:

for prediction in sorter.predict("some_label"):
    print(
        prediction.message_id,
        prediction.source_folder,
        prediction.recommended_folder,
        prediction.score,
        prediction.threshold,
        prediction.accepted,
        prediction.score_type,
        prediction.subject,
    )

Prediction has:

  • message_id - id that uniquely identifies the message
  • source_folder - the folder the message was fetched and scored from
  • recommended_folder - the folder the model scores highest for this message, or None if no model has been trained yet
  • score - the model's score for recommended_folder
  • threshold - the recommendation_ratio this prediction was scored against
  • accepted - whether score clears threshold, i.e. whether sort() would move this message for real given the same recommendation_ratio - an abstained prediction (accepted=False) is never acted on
  • score_type - whether score is a calibrated probability (ScoreType.CALIBRATED) or a raw, uncalibrated classifier score (ScoreType.RAW) - see Evaluation and confidence for what that distinction means and why recommendation_ratio is not automatically a probability
  • subject - the message subject, if available; display metadata, not itself part of the classification

Because every field is a plain value, Prediction is equally useful to the CLI's predict table, to a caller such as gmailsorter, to a future web interface, or to an audit log - store or ship a Prediction as-is, no scikit-learn objects involved.

Evaluation

Before trusting sort()/mailsort sort to move mail automatically, check what a given recommendation_ratio actually achieves on your own data, rather than assuming it does what the number suggests:

from mailsort.api import evaluate_models

report = evaluate_models(connection_str="sqlite:///email.db", recommendation_ratio=0.9)
print(report.coverage, report.overall_precision)

This trains a separate, throwaway set of models on part of your local database and scores them against the rest - it never touches the models mailsort train has already stored. See Evaluation and confidence for the full reasoning (why a classifier score is not automatically a probability, when calibration is and is not applied, and how to read the report), and the command line equivalent, mailsort evaluate.

Low-level interface

MailSorter is a thin wrapper around methods Imap (an AbstractMailBox) already exposes directly. They remain available, both for backwards compatibility with existing scripts and for callers who want finer-grained control - MailSorter.sync()/train()/predict()/sort() above are implemented purely in terms of them, so behavior is identical either way:

from mailsort import Imap

imap = Imap(
    host="imap.example.com",
    port=993,
    username="user@example.com",
    password="...",
    connection_str="sqlite:///email.db",
)
imap.update_database(quick=False)
imap.fit_machine_learning_model_to_database()
imap.filter_messages_from_server(label="some_label", recommendation_ratio=0.9)

update_database(), fit_machine_learning_model_to_database() and filter_messages_from_server() return the same SyncResult/TrainResult/SortResult objects as their MailSorter counterparts. get_label_recommendations() is predict()'s implementation - it already returns list[Prediction], so MailSorter.predict() is a pure passthrough to it:

for prediction in imap.get_label_recommendations(
    label="some_label", recommendation_ratio=0.9
):
    print(prediction.message_id, prediction.recommended_folder)

filter_messages_from_server() scores messages through this exact same call, then moves only the accepted predictions - inference and mailbox mutation are separated in code, not just by convention, so it is not possible to move a message without it first having gone through get_label_recommendations().

Train and inspect the local database without a mail connection

train_machine_learning_models() and get_database_status() are the functions behind the train and status CLI commands. Both only need the database connection string, not mail server credentials:

from mailsort.api import get_database_status, train_machine_learning_models

train_machine_learning_models(connection_str="sqlite:///email.db")
status = get_database_status(connection_str="sqlite:///email.db")
print(status.message_count, status.trained_label_lst)

API for downstream packages

Packages built on top of mailsort, such as gmailsorter, should import the shared database and machine learning building blocks from mailsort.api rather than from mailsort's internal modules directly. This keeps mailsort.api as the single place that needs to stay consistent when mailsort's internals are refactored.

from mailsort.api import (
    AbstractMailBox,
    AbstractMessage,
    DatabaseInterface,
    DatabaseStatus,
    DatabaseTemplate,
    EvaluationReport,
    FolderMetrics,
    MachineLearningDatabase,
    MailSorter,
    Prediction,
    ScoreType,
    SortResult,
    SyncResult,
    TrainResult,
    email_date_converter,
    evaluate_models,
    get_database_status,
    get_email_database,
    get_machine_learning_database,
    strip_html_tags,
    train_machine_learning_models,
)

MailSorter, evaluate_models() and the SyncResult/TrainResult/Prediction/SortResult/ EvaluationReport/FolderMetrics result types are exported here, not just from mailsort directly, because they are generic over AbstractMailBox: a downstream package implementing its own mailbox backend (as gmailsorter does for Gmail) can wrap its own AbstractMailBox subclass in the same MailSorter facade, and evaluate its own database the same way, without reimplementing either.

Metadata

Release files for mailsort 0.0.4

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for mailsort 0.0.4
File Size Uploaded
mailsort-0.0.4.tar.gz 50.2 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for mailsort 0.0.4
File Interpreter ABI Platform
mailsort-0.0.4-py3-none-any.whl Python 3 none any Details

Total release size: 110.7 kB

Release files / mailsort-0.0.4.tar.gz

Download URL mailsort-0.0.4.tar.gz
Size 50.2 kB
Tags Source
SHA-256 checksum
How to use checksums
69b759be1792f63b21a46bf7cc241aadf5a4702e89c8402843d324fafe207fe5
BLAKE2b-256 checksum
How to use checksums
4eb3124ecdc365b5816be54a3e8a898466edef8dbdc214e662ed2b87770c9096
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 12, 2026.

Transparency log

Release files / mailsort-0.0.4-py3-none-any.whl

Download URL mailsort-0.0.4-py3-none-any.whl
Size 60.5 kB
Tags Python 3
SHA-256 checksum
How to use checksums
2e8b7578d027bb4c1526988fe7cc71b07c56bfdf3e3d9bc4611d8b0522a1073a
BLAKE2b-256 checksum
How to use checksums
008f6017d95e0aa69e50897c45803c846be8354d89172b2821d26d8f07184b69
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 12, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.0.4 This release

2 release files

0.0.3

2 release files

0.0.2

2 release files

0.0.1

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page