Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
ef87b08
Add design spec for IMAP backend support
jan-janssen Jul 25, 2026
af5e118
Add implementation plan for IMAP backend support
jan-janssen Jul 25, 2026
92c20a8
Add .gitignore for local virtualenv and build artifacts
jan-janssen Jul 25, 2026
4cd05c3
refactor: move HTML-to-text stripping into base/message.py so it can …
jan-janssen Jul 25, 2026
f34fdad
refactor: extract AbstractMailBox loop from GoogleMailBase into base/…
jan-janssen Jul 25, 2026
04feeff
feat: add IMAP message parsing (gmailsorter.imap.message)
jan-janssen Jul 25, 2026
84f1274
feat: add IMAP username/password authentication (gmailsorter.imap.aut…
jan-janssen Jul 25, 2026
d109dcb
feat: add ImapMailBase (folders-as-labels, MOVE/COPY+EXPUNGE)
jan-janssen Jul 25, 2026
ab24538
feat: add Imap convenience class and gmailsorter.Imap export
jan-janssen Jul 25, 2026
709ee1d
feat: add gmailsorter-imap CLI entry point
jan-janssen Jul 25, 2026
8b6d8af
test: add GreenMail-backed IMAP integration test and CI job
jan-janssen Jul 25, 2026
f86b463
test: retry IMAP reachability probe so CI doesn't skip on GreenMail s…
jan-janssen Jul 25, 2026
24aa002
docs: document the Imap class and CLI
jan-janssen Jul 25, 2026
d0dee28
style: apply ruff format and black to gmailsorter and tests
jan-janssen Jul 25, 2026
1717d9f
fix: use BODY.PEEK[] for IMAP fetch to avoid marking messages as read
jan-janssen Jul 25, 2026
2768c53
fix: make the IMAP LIST-response parser tolerant of legal responses
jan-janssen Jul 25, 2026
7f9cfc4
fix: exclude Trash/Junk/Sent/Drafts folders from IMAP labels
jan-janssen Jul 25, 2026
dfbbe14
fix: use UID EXPUNGE in the IMAP COPY fallback when UIDPLUS is available
jan-janssen Jul 25, 2026
9ffd65c
test: fail the IMAP integration job when no IMAP server is reachable
jan-janssen Jul 25, 2026
db2d315
feat: add IMAP connection lifecycle management and reconnect-on-drop
jan-janssen Jul 25, 2026
d506813
Delete docs/superpowers/plans/2026-07-25-imap-support.md
jan-janssen Jul 25, 2026
2092854
Delete docs/superpowers/specs/2026-07-25-imap-support-design.md
jan-janssen Jul 25, 2026
38ca76e
Merge commit 'd6d875664d8644f8458936a7a224fb57b4c14b4e' into imap-sup…
jan-janssen Jul 26, 2026
6e22011
Fix test_google_message import of removed MLStripper
jan-janssen Jul 26, 2026
7ad0da0
Extend IMAP test coverage for mail.py, message.py, and __main__.py
jan-janssen Jul 26, 2026
cdcd5b7
Merge commit '152511ca7abdfe11680f782066618b1f13907836' into imap-sup…
jan-janssen Aug 10, 2026
8b30155
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Aug 10, 2026
d0f6b67
fix datetime import
jan-janssen Aug 10, 2026
c946827
Merge commit '924c5206cd76c371e87287f48ea8819127230fbc' into imap-sup…
jan-janssen Aug 10, 2026
b342603
fix a couple of type hints
jan-janssen Aug 10, 2026
3d59520
fixes
jan-janssen Aug 10, 2026
79e9da0
Merge branch 'main' into imap-support
jan-janssen Aug 10, 2026
bf6db61
fixes
jan-janssen Aug 10, 2026
a1cb148
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Aug 10, 2026
ab44dba
black fixes
jan-janssen Aug 10, 2026
98912b6
Merge branch 'imap-support' of github.com:jan-janssen/gmailsorter int…
jan-janssen Aug 10, 2026
6f6145f
Merge commit '689ee53ae0a6c4d00f789d552dc968c41250d79f' into imap-sup…
jan-janssen Aug 23, 2026
a17ee3d
Merge commit '772e39a00c35f72e4e28a30c813de119c61e2ca9' into imap-sup…
jan-janssen Aug 23, 2026
c9f61e4
Merge commit 'a848892b7eb69bb63490ec88eb06c2fe1513f556' into imap-sup…
jan-janssen Aug 23, 2026
5c0871d
Merge changes from main
jan-janssen Aug 23, 2026
9f14b4e
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Aug 23, 2026
dd41079
fix tests
jan-janssen Aug 23, 2026
070fbcd
Merge branch 'imap-support' of github.com:jan-janssen/gmailsorter int…
jan-janssen Aug 23, 2026
415efb8
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Aug 23, 2026
5fee179
fixes
jan-janssen Aug 23, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions .github/workflows/unittest.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,40 @@ jobs:
run: |
pip install --no-deps .
coverage run --omit gmailsorter/_version.py -m unittest discover tests

imap-integration:
runs-on: ubuntu-latest

services:
greenmail:
image: greenmail/standalone:2.1.11
env:
GREENMAIL_OPTS: >-
-Dgreenmail.setup.test.smtp
-Dgreenmail.setup.test.imap
-Dgreenmail.hostname=0.0.0.0
-Dgreenmail.users=testuser:secret@example.test
ports:
- 3025:3025
- 3143:3143

env:
TEST_SMTP_HOST: localhost
TEST_SMTP_PORT: "3025"
TEST_IMAP_HOST: localhost
TEST_IMAP_PORT: "3143"
TEST_IMAP_USERNAME: testuser
TEST_EMAIL: testuser@example.test
TEST_EMAIL_PASSWORD: secret
# fail this job rather than skip it if the greenmail service never came up
IMAP_INTEGRATION_REQUIRED: "true"

steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.12'
- name: Install package
run: pip install .
- name: Run IMAP integration test
run: python -m unittest tests.test_imap_service_integration -v
9 changes: 6 additions & 3 deletions docs/source/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,12 @@ machine learning knowledge required. If you are looking for setup instructions i
Regardless of whether you use the hosted [gmailsorter.com](https://gmailsorter.com) service, the Docker container or
the plain Python package, `gmailsorter` is built from the same three building blocks:

* **Your Google Mail account** - the source of truth for your emails and labels, accessed exclusively through the
official [Gmail API](https://developers.google.com/gmail/api/guides). `gmailsorter` never reads your mailbox
through any other channel and never stores your Google password.
* **Your email account** - the source of truth for your emails and labels, accessed either through the official
[Gmail API](https://developers.google.com/gmail/api/guides) or, for any other IMAP-capable provider, through a
plain IMAP connection. `gmailsorter` never stores your Google password, and for IMAP accounts the password you
provide is used only to log in - it is not persisted anywhere. When talking to a plain IMAP server, each mailbox
folder plays the role a Gmail label plays throughout the rest of this page - "moving" an email between labels
means moving it between IMAP folders.
* **A local database** - a SQL database (SQLite by default, though any database supported by
[SQLAlchemy](https://www.sqlalchemy.org/) works) that keeps a private copy of your email metadata, your login
token and your trained models. In the Docker container and the plain Python package this database lives entirely
Expand Down
28 changes: 28 additions & 0 deletions docs/source/developer.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,34 @@ a selected label `"MyLabel"`. Then reloads the machine learning model from the l
the correct labels for these emails. The `recommendation_ratio` defines the level of certainty required to actually move
the email, with `0.9` equalling a certainty of 90%.

## IMAP accounts
`gmailsorter` also supports plain IMAP accounts (username and password, e.g. an app
password), for mail servers other than Google Mail. Import the `Imap` class instead of
`Gmail`:
```
from gmailsorter import Imap
```
```
imap = Imap(
host="imap.example.com",
port=993,
username="user@example.com",
password="app-password",
connection_str="sqlite:////absolute/path/to/email.db",
)
```
`Imap` exposes the exact same `update_database()`, `get_all_emails_in_database()` and
`filter_messages_from_server()` methods as `Gmail` - the only difference is that IMAP
folders play the role Gmail labels play elsewhere in this document: each folder is
treated as one label, and moving an email means moving it from one IMAP folder to
another. A command line interface is also available as `gmailsorter-imap`, reading the
account password from an environment variable (`IMAP_PASSWORD` by default) rather than
accepting it as a command line argument:
```
export IMAP_PASSWORD=app-password
gmailsorter-imap --host imap.example.com --username user@example.com -d sqlite:///email.db -u
```

## Future directions
The current machine learning model is limited in the precision and memory usage. So there is a great interest to replace
it with a computationally more efficient model. All suggestions and feedback are welcome. Beyond the optimization of the
Expand Down
4 changes: 2 additions & 2 deletions gmailsorter/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from gmailsorter.local import Gmail, load_client_secrets_file
from gmailsorter.local import Gmail, Imap, load_client_secrets_file

from . import _version

__version__: str = _version.__version__
__all__ = ["Gmail", "load_client_secrets_file"]
__all__ = ["Gmail", "Imap", "load_client_secrets_file"]
4 changes: 3 additions & 1 deletion gmailsorter/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,9 @@ def command_line_parser() -> None:
)
elif args.label:
gmail.filter_messages_from_server(
label=args.label, recommendation_ratio=0.9
label=args.label,
recommendation_ratio=0.9,
label_prefix="labels_Label_",
)
else:
parser.print_help()
Expand Down
Loading
Loading