Add message filters and capture markers for human mailboxes (#04, #22)#6
Merged
Merged
Conversation
A `filters` map — global or per route — declares allow/deny rules on from, subject, header and has_attachment. Patterns are regular expressions when delimited and case-insensitive wildcards otherwise. Deny wins over allow; an empty allow list allows everything. A filtered message is still marked seen so it is not picked up again, but never reaches the journal, archive or endpoint. Malformed rules (broken regex, unknown field) are rejected at matcher construction instead of silently skipping mail.
A `mark` option — global or per route — chooses how mailspoon marks messages it has already viewed. The default `seen` keeps using the \Seen flag, right for robot mailboxes. For mailboxes read by humans the read state stays untouched: `keyword:<name>` marks with a custom IMAP keyword (selection switches to UNKEYWORD; invisible in mail clients, cursor lives on the server), and `none` leaves the message alone entirely, tracking position in a new relay_cursors table by UID, restarted when UIDVALIDITY changes (cron-poll only — IDLE does not advance the cursor). The marker applies to every viewed message, including filtered ones, so nothing is re-read on the next pull; only messages passing the #4 filter are journaled and relayed. mailspoon:doctor validates the mark and filter rules per mailbox and reminds about PERMANENTFLAGS for keyword mailboxes.
EgorGruzdev
added a commit
that referenced
this pull request
Jun 12, 2026
A configurable `after` map (global and per route) decides what happens to a message in its mailbox once its relay outcome is known: none (default), seen, keyword:<name>, move:<folder> or delete. The vocabulary is shared with capture markers instead of the originally sketched mark_seen/flag:. Outcomes land in different store-and-forward phases. `filtered` is known at capture, so its action runs right in the listener while the connection is open (marker first, so a failing action cannot cause a re-pull). `delivered` and `failed` are reached in mailspoon:deliver without an IMAP connection — they are applied by the new scheduled mailspoon:tidy command, which reconnects and finds each message by the UID stored at capture. Because UIDs are only stable within one UIDVALIDITY epoch, tidy verifies the found message against the journal record (Message-Id, or raw fingerprint when the header is absent) before acting. `failed` means the final outcome: a message is only acted on once its delivery attempts are exhausted. mailspoon:replay resets the tidy state along with the attempt counter, so the new outcome gets its own action. Move targets are created automatically; delete only flags \Deleted. mailspoon:doctor validates and reports the actions per mailbox.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Реализует #04 (фильтрация) и #22 (ящики-люди) — вместе они закрывают сценарий «ящик операторов: искать символ в теме, обрабатывать, не помечать прочитанным».
#4 — фильтры захвата
Карта
filters(глобально или на маршруте):allow/denyпоfrom/subject/header/has_attachment, регэкспы или wildcard'ы.denyпобеждаетallow, пустойallowпропускает всё. Отфильтрованное письмо помечается маркером ящика, но не попадает в журнал/архив/доставку. Битые правила отвергаются при конструировании матчера и ловятся доктором.#22 — маркер просмотренного
Опция
mark(глобально или на маршруте):seen(дефолт) — как раньше;keyword:<имя>— кастомный IMAP-кейворд: выборкаUNKEYWORD, прочитанность не трогается, курсор на сервере;none— письмо не трогается вовсе; UID-курсор в новой таблицеrelay_cursorsсо сбросом при сменеUIDVALIDITY. Курсор продвигает pull; в IDLE новые письма захватываются дедупом, перечитанный после рестарта хвост отбрасывается без повторной доставки.mailspoon:doctorвалидируетmarkи фильтры по каждому ящику и напоминает проPERMANENTFLAGS \*для keyword-ящиков.Сценарий целиком
Тесты
71 passed (249 assertions), Pint чистый. Новые: матчер (10 кейсов), маркер (6), listener с фильтром/кейвордом/none, выборки pull по маркеру, resume/restart UID-курсора, доктор с битым фильтром и репортом маркера.