Mirror docs/ from 896f30e

github-actions[bot] committed 2026-10-10 21:43:18 +00:00
1 parent 7707e97111
commit e940a73286
12 files changed
+166 -22

No files matched your search

+12 -3
@@ -1,4 +1,4 @@
<!-- translated-from: features/accounts.md sha1:dd31f87ab1777ab4166251dc0b7cb0ce6aef417f -->
<!-- translated-from: features/accounts.md sha1:0a9e8d4f75f2ed1058799d55ad90b09e851c653b -->
# Konten und Aliase
@@ -30,7 +30,16 @@ daran, aber ein Konto, das sauber abgelegt wird und sich nicht anmelden kann, is
Fehler, den man lieber jetzt als bei der ersten Synchronisierung findet — deshalb läuft die
Prüfung beim Speichern noch einmal, und ihr Ergebnis bleibt am Konto vermerkt.
Die erste Synchronisierung startet sofort. Danach hält plMail eine IMAP-IDLE-Verbindung zu
Die erste Synchronisierung startet sofort, und die vorhandene Mail des Kontos wird danach
hereingeholt, die neueste zuerst: das Jüngste aus dem Posteingang binnen etwa einer Minute, dann
der Rest des Posteingangs, dann Gesendet, Entwürfe und deine eigenen Ordner, das Archiv und
zuletzt Spam und Papierkorb. Mail, die währenddessen ankommt, wird davon nicht aufgehalten. Neben
dem Suchfeld steht **Import läuft**, solange ein Konto importiert — ein Klick darauf zeigt den
Stand je Konto und den Ordner, an dem es gerade ist; bewegt sich eine Weile nichts, steht dort
**Wartet darauf, weiterzumachen**, und ein Konto, das nicht erreichbar ist, erscheint unter
**Zustand der Konten**.
Danach hält plMail eine IMAP-IDLE-Verbindung zu
jedem Postfach und synchronisiert in dem Moment, in dem sich etwas ändert, mit einem geplanten
Durchlauf alle fünfzehn Minuten dahinter.
@@ -54,7 +63,7 @@ Google abgelehnt werden und beim nächsten Abgleich wieder verschwinden. Ein Mic
dieselben Rechte per Richtlinie zurückhalten.
plMail vergleicht jetzt, was es bekommen hat, mit dem, wonach es gefragt hat, und zeigt bei einem
Unterschied eine Karte unter **Kontozustand** — mit einem Knopf, um das Konto neu zu verbinden und
Unterschied eine Karte unter **Zustand der Konten** — mit einem Knopf, um das Konto neu zu verbinden und
den Rest zu erteilen. Wenn du ein Konto verbunden hast, bevor es das gab, erscheint die Karte beim
ersten Erneuern der Anmeldung oder sobald eine Änderung zum ersten Mal abgelehnt wird.
+3 -2
@@ -1,4 +1,4 @@
<!-- translated-from: install/docker.md sha1:6a1b5ac4bb8a1c96bf13c943ef14a18ab8ce0cbf -->
<!-- translated-from: install/docker.md sha1:77ef18f80d56bea9e0c01c3223e7a745282137ee -->
# Installation mit Docker Compose
Der unterstützte Weg, von Anfang bis Ende: was du brauchst, was `docker compose up` tatsächlich tut,
@@ -156,7 +156,8 @@ bis zu dreißig Sekunden, wenn er abgestürzt ist.
| `mercure` | Der Mercure-Hub, 1.x | Live-Aktualisierungen — die Mailliste, die sich von selbst auffrischt |
| `imap-supervisor` | `app:imap:supervise` | Startet und überwacht je einen `app:imap:idle`-Prozess pro IDLE-fähigem Postfach, damit gewöhnliche IMAP-Mail in dem Moment ankommt, in dem sie eintrifft |
| `worker-export` | `messenger:consume export` | Alles, was plMail verlässt, und die einzige Warteschlange, auf die jemand wartet. In einem eigenen Prozess, damit ein Versand nie hinter einem Abgleich steht |
| `worker-ingest` | `messenger:consume ingest` | Mail abholen, und nur das |
| `worker-ingest` | `messenger:consume ingest` | Mail abholen, die gerade ankommt, und nur das |
| `worker-backlog` | `messenger:consume ingest_backlog` | Die Vorgeschichte eines Postfachs beim ersten Import, Seite für Seite. In einem eigenen Prozess, damit gerade ankommende Mail nie dahinter steht |
| `worker-live` | `messenger:consume enrich_live` | Was auf Mail folgt, die gerade eben angekommen ist — Einsortieren, Terminerkennung, Insight-Karten, Lesebestätigungen. Eigener Prozess, damit neue Mail nie hinter einem Import steht |
| `worker-release` | `messenger:consume release` | Zeigt Mail an, die für den Assistenten zurückgehalten wurde, sobald ihre Wartezeit vorbei ist. Fast immer untätig, und in einem eigenen Prozess, damit nie etwas davor steht |
| `worker-enrich` | `messenger:consume enrich enrich_backlog` | Dieselbe Arbeit für die aktuelle Mail eines Imports, danach — nur wenn diese Warteschlange leer ist — alte Mail, die der Assistent einsortiert |
+3 -2
@@ -1,4 +1,4 @@
<!-- translated-from: install/troubleshooting.md sha1:6776a1d8e46bcf99b043bdac37a1154c27c04abb -->
<!-- translated-from: install/troubleshooting.md sha1:ad52dfe87905d09cdd1f0545fabb06f413d402fa -->
# Fehlersuche
Was `/healthz` bedeutet, wie du eine hängende von einer leeren Warteschlange unterscheidest, wo die
@@ -60,7 +60,8 @@ Worker-Container:
| Warteschlange | Prozess | Was darauf liegt |
|---|---|---|
| `export` | `worker-export` | Alles, was plMail verlässt — Versand, Flag-Pushes, Gmail-Label-Änderungen, Mail aus dem Notifier. Die einzige Warteschlange, auf die jemand wartet |
| `ingest` | `worker-ingest` | Mail abholen und sonst nichts: Konto- und Postfachabgleiche, Gmail- und Graph-Nachrichten-Batches, Kalenderabgleiche |
| `ingest` | `worker-ingest` | Mail abholen, die gerade ankommt, und sonst nichts: Konto- und Postfachabgleiche, Gmail- und Graph-Nachrichten-Batches, Kalenderabgleiche |
| `ingest_backlog` | `worker-backlog` | Ein erster Import: Seiten aus der Vorgeschichte eines Postfachs, die neuesten zuerst. Lang, solange ein großes Konto hereingeholt wird, und so ist es gedacht — nichts, was gerade ankommt, wartet darauf |
| `enrich_live` | `worker-live` | Was auf Mail folgt, die gerade eben in einem Konto angekommen ist, dessen erster Import abgeschlossen ist: Einsortieren durch den Assistenten, Terminerkennung, Insight-Karten, Lesebestätigungen. Eigener Prozess, damit neue Mail nie hinter einem Import steht |
| `release` | `worker-release` | Beendet das Warten für Mail, die für den Assistenten zurückgehalten wird. Fast immer leer; eigener Prozess, damit nie etwas davor steht |
| `enrich` | `worker-enrich` | Dieselbe Arbeit für aktuelle Mail aus einem Import, dazu die Suchindizierung |
+2 -2
@@ -1,4 +1,4 @@
<!-- translated-from: internals/architecture.md sha1:6dbabd36bda20eb3426c8d4237e129b868d776f6 -->
<!-- translated-from: internals/architecture.md sha1:9a71407f7557ff10c7e390efae4605835733b595 -->
# Architektur
Die Schichten, was wo liegt, und die Regeln, die das so halten. Diese Seite beschreibt die
@@ -177,7 +177,7 @@ Workers, damit ein verzögerter Retry nie auf einen Neustart warten muss.
Getrennte Transports allein genügen nicht. Ein Worker, der bereits in einem langen Handler
steckt, kann eine Sendung nicht aufnehmen, wie hoch sie auch priorisiert ist — also hat jeder
Transport seinen eigenen Prozess: `worker-export`, `worker-ingest`, `worker-live`, `worker-release`, `worker-enrich`,
Transport seinen eigenen Prozess: `worker-export`, `worker-ingest`, `worker-backlog`, `worker-live`, `worker-release`, `worker-enrich`,
`worker-maintenance` und `worker-bulk`, die `app:work` nebeneinander im Worker-Container betreibt.
`worker-enrich` ist der eine, der zwei Transports abarbeitet, `enrich` und `enrich_backlog`, in
dieser Reihenfolge — siehe [Mail-Ingest](De-Internals-Mail-Ingest#was-auf-eine-nachricht-folgt-und-wo-es-läuft). Ein fünfter Transport, `async`, wird ohne Routing
+65 -5
@@ -1,4 +1,4 @@
<!-- translated-from: internals/mail-ingest.md sha1:38b6beb3ba071c7ee0ef05200adc575b44fdf9c7 -->
<!-- translated-from: internals/mail-ingest.md sha1:dcb2b781f03c1702974cc382857086d18024e21c -->
# Mail-Ingest
Vom Anbieter in die Datenbank: wie aus Bytes eine Zeile `Message` wird, was mit ihr geschieht,
@@ -43,8 +43,10 @@ geholt hatte.
**Graph hat keinen Cursor auf Kontoebene.** Ein Gegenstück zur einen `historyId` gibt es nicht,
also liegt der Delta-Zustand pro Ordner vor, gespeichert als Abbildung `folderId => deltaLink`
auf dem `Account`. Eine Delta-Abfrage ohne gespeicherten Link zählt den Ordner auf und gibt
einen zurück; derselbe Aufruf mit Link liefert nur noch Änderungen, sodass ein Codepfad den
Erst- und den Folgefall abdeckt.
einen zurück; derselbe Aufruf mit Link liefert nur noch Änderungen. Für Graph ist das ein
Vorgang. Für plMail sind es zwei: einem Ordner mit Link zu folgen ist Sache des Abgleichs, und
die Aufzählung, die den Link erst einbringt, ist Sache des Imports — siehe „Ein Erstimport ist
kein Abgleich" weiter unten.
Die scharfe Kante bei Graph sind die Ordnerbewegungen. Eine Nachricht aus einem Ordner
herauszuschieben erscheint im Delta des Quellordners als `@removed` und im Delta des Zielordners
@@ -61,6 +63,63 @@ veraltetem Stand. Was tatsächlich hält, wenn Stapel sich über Läufe oder Ret
überlappen, ist `uniq_message_gmail_id_account` und sein Graph-Gegenstück auf `message`, mit der
Anbieter-Id vorn in der Spaltenliste, damit der Index auch die reinen Id-Nachschläge bedient.
### Ein erster Import ist kein Abgleich
Ein Abgleich holte früher neben der neuen Mail auch die Vorgeschichte eines Kontos: ein einziger
Job, der jeden IMAP-Ordner von der ältesten Nachricht bis zur neuesten las oder jede
Gmail-Nachrichten-Id auflistete, bevor er eine abrief. Bei einem Konto mit zwanzigtausend
Nachrichten hielt dieser Job die Warteschlange `ingest` so lange besetzt, wie er eben brauchte,
zeigte die meiste Zeit einen leeren Posteingang und stellte die neue Mail aller anderen Konten
dahinter an.
Beides ist jetzt getrennt. Ein Abgleich sorgt dafür, dass neue Mail gesehen wird, und hört auf;
die Vorgeschichte gehört `App\Service\Mail\MailImporter`, auf der Warteschlange
`ingest_backlog` mit eigenem Worker.
- **Eine Nachricht ist eine Seite.** `ImportMailMessage` trägt eine Konto-Id und sonst nichts;
ihr Handler holt eine Seite und stößt die nächste an. Nirgends darin steckt ein langer Job,
also wechseln sich die Seiten mehrerer Konten in der Warteschlange ab, und jedes hat einen
brauchbaren Posteingang, bevor irgendeines sein Archiv hat.
- **IMAP liest jeden Ordner von oben nach unten.** Beim ersten Kontakt setzt `ImapImportPlan`
`lastSeenUid` an das obere Ende des Ordners und `importFloorUid` direkt darüber. Neue Mail
ist alles oberhalb des ersten; Vorgeschichte ist alles unterhalb des zweiten, und
`ImapMailboxImporter` arbeitet sich fünfzig Nachrichten pro Aufruf nach unten. Eine Seite
entsteht, indem der Server gefragt wird, welche UIDs unterhalb der Marke existieren, und die
schon gespeicherten herausgenommen werden — eine Nachricht, die da ist, wird nie zweimal
abgerufen.
- **Ordner kommen in der Reihenfolge, die jemand sich wünschen würde:** Posteingang, Gesendet,
Entwürfe, eigene Ordner, das Archiv und zuletzt Spam und Papierkorb.
`MailboxSpecialUse::importRank()` ist die Regel.
- **Gmail fragt zuerst namentlich nach dem Posteingang,** dann blättert es die Auflistung des
ganzen Postfachs Anfrage für Anfrage durch; das Seiten-Token liegt zwischen den Jobs auf dem
Konto.
- **Microsoft liest die erste Delta-Runde jedes Ordners seitenweise.** Ein Ordner ohne
Delta-Link wird auf dem Konto eingeplant (`Account::$graphImport`), statt vom Abgleich
aufgezählt zu werden, und `GraphApiSyncer::importPage()` geht diese Runde in Schritten von
fünfzig Nachrichten durch, die neuesten zuerst, in derselben Ordnerreihenfolge nach
`LabelRole::importRank()`. Die Seite, die die Runde beendet, bringt den Delta-Link des
Ordners mit, und ab da folgt ihm der Abgleich. Bis dahin fragt der Abgleich einen solchen
Ordner nur nach dem, was seit der Einplanung eingegangen ist. Ein abgelaufener Delta-Link
gibt seinen Ordner an den Import zurück, statt im Abgleich neu aufgezählt zu werden.
- **Eine Nachricht, die sich nicht speichern lässt, kostet nur sich selbst.** Die Marke bleibt,
wo sie ist, und die Seite wird erneut angefragt; nach fünf Versuchen rückt die Marke an ihr
vorbei, und der Ordner geht weiter.
Jeder gewöhnliche Abgleich endet in `MailImporter::ensureRunning()`, das einen Import startet,
der Arbeit hat und seit fünf Minuten still ist. Das startet den Import eines neuen Kontos, und
es startet einen neu, dessen Kette von Nachrichten gerissen ist.
„Importiert dieses Konto noch" ist `InitialImportState`, und für IMAP wird es an
`importFloorUid` der Ordner abgelesen: `syncedAt` eines Ordners sagt das nicht mehr, weil sein
erster Abgleich jetzt sofort endet und die Vorgeschichte noch aussteht. Ein Microsoft-Konto
importiert, solange `Account::$graphImport` einen Ordner aufführt.
Eines ändert ein Microsoft-Import am Abgleich: Eine Nachricht, die aus einem Ordner entfernt
wurde, ist nur dann gelöscht, wenn sie in keinem anderen angekommen ist, und ein Ordner, der
noch gelesen wird, kann das nicht sagen. Solche Entfernungen bleiben auf dem Konto
(`Account::$graphRemovals`) und werden vom ersten Abgleich beurteilt, bei dem jeder Ordner
geantwortet hat.
## Die Post-Ingest-Pipeline
Sobald ein Stapel `Message`-Zeilen existiert und geflusht ist, läuft
@@ -156,8 +215,9 @@ Regeln der Nutzerin, ihre Kontakte — und keinen Job. Die Backfill-Aufgaben (`a
insights`, `event-extraction`, …) sind der Weg, Vergangenes zu lesen, wenn jemand es will.
**Live.** `App\Service\Mail\InitialImportState` sagt, ob ein Konto sein Postfach noch zum ersten
Mal hereinholt: Gmail über `needsBackfill()`, Graph darüber, ob es überhaupt einen Delta-Link
gibt, IMAP darüber, ob jeder zum Abgleich aktivierte Ordner ein `syncedAt` hat. Die Pipeline
Mal hereinholt: Gmail über `needsBackfill()`, Graph darüber, ob es einen Delta-Link gibt und kein
Ordner mehr zu lesen ist, IMAP darüber, ob kein zum Abgleich aktivierter Ordner noch
Vorgeschichte offen hat. Die Pipeline
fragt einmal pro Stapel und trägt die Antwort in `PostIngestResult::$live`; `EnrichmentRouter`
macht daraus einen `TransportNamesStamp`. Mail eines fertigen Kontos geht auf `enrich_live`,
dessen Worker nie Import-Arbeit vor sich hat. Im Zweifel heißt es „importiert noch" — ein
+7 -1
@@ -1,4 +1,4 @@
<!-- translated-from: providers/imap-smtp.md sha1:fd8e43c65cd158f4e298cdf3ea0384299bba5d68 -->
<!-- translated-from: providers/imap-smtp.md sha1:90f0e331560ca2cd562d56cfc01609f1a52cb275 -->
# IMAP und SMTP
@@ -32,6 +32,12 @@ Anbieter tatsächlich uneins sind.
5. **Ausgehend — SMTP** nimmt dieselben drei, und alles davon ist optional. Ein Postfach ohne
SMTP-Einstellungen ist ein Postfach, das du lesen und aus dem du nicht senden kannst, und das ist
für ein Archiv ein legitimer Wunsch.
Das eine Passwort gilt für beide Richtungen. Wo der Server zum Senden ein anderes verlangt —
Zoho vergibt je Protokoll ein eigenes App-Passwort, und manche Anbieter stellen ein Relay mit
eigenen Zugangsdaten vor ihr SMTP —, blendet **Zum Senden ein anderes Passwort verwenden** unter
den SMTP-Feldern ein zweites Feld ein. Der Benutzername bleibt derselbe. **Zum Senden dasselbe
Passwort verwenden** nimmt es wieder weg.
6. **Verbindung testen** prüft IMAP und SMTP getrennt und meldet beides einzeln, ein funktionierendes
Postfach mit falschem SMTP-Port sagt das also, statt beim ersten Senden zu scheitern.
+9 -1
@@ -24,7 +24,15 @@ which is what nearly every provider wants.
but an account that stores cleanly and cannot log in is the failure worth catching now rather than
at the first sync, so the probe runs again on save and its outcome is remembered on the account.
The first sync starts immediately. After that, plMail holds an IMAP IDLE connection to each
The first sync starts immediately, and the account's existing mail is then brought in newest
first: the most recent of the Inbox within a minute or so, the rest of the Inbox after it, then
Sent, Drafts and your own folders, the archive, and Spam and the bin last. Mail that arrives
while this runs is not held up by it. A chip beside the search box says **Importing** for as long
as an account is, and opens a list with how much of each is in and which folder it is on; if nothing
has moved for a while it says **Waiting to continue**, and an account that cannot be reached is
listed under **Account health**.
After that, plMail holds an IMAP IDLE connection to each
mailbox and syncs the moment something changes, with a scheduled sweep every fifteen minutes
behind it.
+2 -1
@@ -141,7 +141,8 @@ crashed.
| `mercure` | The Mercure hub, 1.x | Live updates — the mail list refreshing by itself |
| `imap-supervisor` | `app:imap:supervise` | Spawns and watches one `app:imap:idle` process per IDLE-enabled mailbox, so standard IMAP mail arrives the moment it lands |
| `worker-export` | `messenger:consume export` | Anything leaving plMail, and the only queue somebody is watching. On its own process so a send is never behind a sync |
| `worker-ingest` | `messenger:consume ingest` | Fetching mail, and only that |
| `worker-ingest` | `messenger:consume ingest` | Fetching mail that is arriving now, and only that |
| `worker-backlog` | `messenger:consume ingest_backlog` | A mailbox's history on first import, a page at a time. On its own process so mail arriving now is never behind it |
| `worker-live` | `messenger:consume enrich_live` | What follows mail that has just arrived — sorting, event extraction, insight cards, receipts. On its own process so new mail is never behind an import |
| `worker-release` | `messenger:consume release` | Shows mail that was held for the assistant once its wait is over. Idle nearly always, and on its own process so that nothing is ever in front of it |
| `worker-enrich` | `messenger:consume enrich enrich_backlog` | The same work for an import's recent mail, then — only when that queue is empty — old mail being sorted by the assistant |
+2 -1
@@ -54,7 +54,8 @@ Eight transports on the Doctrine transport, consumed by seven processes in the w
| Queue | Process | What is on it |
|---|---|---|
| `export` | `worker-export` | Anything leaving plMail — sends, flag pushes, Gmail label changes, mail from the notifier. The only queue somebody is waiting on |
| `ingest` | `worker-ingest` | Fetching mail and nothing else: account and mailbox syncs, Gmail and Graph message batches, calendar syncs |
| `ingest` | `worker-ingest` | Fetching mail that is arriving now and nothing else: account and mailbox syncs, Gmail and Graph message batches, calendar syncs |
| `ingest_backlog` | `worker-backlog` | A first import: pages of a mailbox's history, newest first. Long while a large account is being brought in, and that is the design — nothing arriving now waits on it |
| `enrich_live` | `worker-live` | What follows mail that has just arrived on an account whose first import is over: sorting by the assistant, event extraction, insight cards, read receipts. Its own process, so new mail is never behind an import |
| `release` | `worker-release` | Ends the wait for mail held for the assistant. Nearly always empty; its own process so nothing is ever in front of it |
| `enrich` | `worker-enrich` | The same work for recent mail brought in by an import, and search indexing |
+1 -1
@@ -163,7 +163,7 @@ delayed retry is never left waiting on a restart.
Separate transports are not enough on their own. A worker already inside a long handler
cannot pick up a send however it is prioritised, so each transport has its own process:
`worker-export`, `worker-ingest`, `worker-live`, `worker-release`, `worker-enrich`, `worker-maintenance` and
`worker-export`, `worker-ingest`, `worker-backlog`, `worker-live`, `worker-release`, `worker-enrich`, `worker-maintenance` and
`worker-bulk`, which `app:work` runs side by side in the worker container. `worker-enrich` is the one
that takes two transports, `enrich` and `enrich_backlog`, in that order — see
[Mail ingest](Internals-Mail-Ingest#what-follows-a-message-and-where-it-runs). A
+55 -3
@@ -38,7 +38,9 @@ to strand an account on whatever the first run happened to fetch.
**Graph has no account-level cursor.** There is no equivalent of a single `historyId`, so
delta state is per folder, stored as a `folderId => deltaLink` map on the `Account`. A delta
query with no stored link enumerates the folder and hands back one; the same call with a link
returns only changes, so one code path covers both the initial and the incremental case.
returns only changes. To Graph that is one operation. To plMail it is two: following a folder
that has a link is the sync's, and the enumeration that earns the link is the import's — see
"A first import is not a sync" below.
Graph's folder moves are the sharp edge. Moving a message out of a folder appears as
`@removed` on the source folder's delta and as an addition on the destination's, and the two
@@ -54,6 +56,56 @@ The guard that actually holds when batches overlap across runs or retries is
`uniq_message_gmail_id_account` and its Graph counterpart on `message`, with the provider id
leading the column list so the index also serves the id-only lookups.
### A first import is not a sync
A sync used to bring in an account's history as well as its new mail: one job that read every
IMAP folder from its oldest message to its newest, or listed every Gmail message id before
fetching one. On an account of twenty thousand messages that job held the `ingest` queue for
as long as it took, showed an empty Inbox for most of it, and put every other account's new
mail behind it.
The two are separate now. A sync arranges for new mail to be seen and stops; the history is
`App\Service\Mail\MailImporter`'s, on the `ingest_backlog` queue with a worker of its own.
- **One message is one page.** `ImportMailMessage` carries an account id and nothing else; its
handler brings in one page and dispatches the next. There is no long job anywhere in it, so
several accounts' pages interleave on the queue and each gets a usable Inbox before any gets
its archive.
- **IMAP reads each folder from the top down.** On first contact `ImapImportPlan` puts
`lastSeenUid` at the top of the folder and `importFloorUid` just above it. New mail is
everything above the first; history is everything below the second, and
`ImapMailboxImporter` walks that down fifty messages at a call. A page is found by asking
the server which UIDs exist below the floor and taking out the ones already stored, so a
message that is here is never fetched twice.
- **Folders are read in the order somebody would ask for:** Inbox, Sent, Drafts, a person's own
folders, the archive, and Spam and the bin last. `MailboxSpecialUse::importRank()` is the
rule.
- **Gmail asks for the Inbox by name first,** then pages the whole-mailbox listing one request
at a time, with the page token kept on the account between jobs.
- **Microsoft reads each folder's first delta round a page at a time.** A folder without a
delta link is planned on the account (`Account::$graphImport`) instead of being enumerated
by the sync, and `GraphApiSyncer::importPage()` walks that round fifty messages at a call,
newest first, in the same folder order by `LabelRole::importRank()`. The page that ends the
round carries the folder's delta link, and from then on the sync follows it. Until then the
sync asks such a folder only for what it received since it was planned. A delta link that
expires hands its folder back to the import rather than being re-enumerated in the sync.
- **A message that will not store costs itself.** The floor stays where it is and the page is
asked for again; after five tries the floor moves past it and the folder goes on.
Every ordinary sync ends in `MailImporter::ensureRunning()`, which starts an import that has
work and has gone quiet for five minutes. That is what starts a new account's import and what
restarts one whose chain of messages broke.
"Is this account still importing" is `InitialImportState`, and for IMAP it is read off the
folders' `importFloorUid`: a folder's `syncedAt` no longer says so, because its first sync now
ends at once with the history still to come. A Microsoft account is importing for as long as
`Account::$graphImport` lists a folder.
One thing a Microsoft import changes about the sync: a message removed from one folder is a
deletion only if it arrived in no other, and a folder still being read cannot say. Such
removals are kept on the account (`Account::$graphRemovals`) and judged by the first sync
whose every folder answered.
## The post-ingest pipeline
Once a batch of `Message` rows exists and has been flushed, `App\Service\Mail\PostIngestPipeline`
@@ -137,8 +189,8 @@ tasks (`app:backfill insights`, `event-extraction`, …) are how history is read
wants it.
**Live.** `App\Service\Mail\InitialImportState` says whether an account is still bringing in its
mailbox for the first time: Gmail by `needsBackfill()`, Graph by having a delta link at all,
IMAP by every sync-enabled folder having a `syncedAt`. The pipeline asks once per batch and
mailbox for the first time: Gmail by `needsBackfill()`, Graph by having a delta link and no folder
left to read, IMAP by no sync-enabled folder having history still to fetch. The pipeline asks once per batch and
carries the answer on `PostIngestResult::$live`; `EnrichmentRouter` turns it into a
`TransportNamesStamp`. Mail of a finished account goes to `enrich_live`, whose worker never has
import work in front of it. It errs towards "still importing" — a Gmail backfill settles up to
+5
@@ -28,6 +28,11 @@ connection to come up, and about the handful of settings providers genuinely dis
5. **Outgoing — SMTP** takes the same three, and all of it is optional. A mailbox with no SMTP
settings is a mailbox you can read and not send from, which is a legitimate thing to want for an
archive.
The one password is used for both directions. Where the server wants a different one to send —
Zoho issues an app password per protocol, and some hosts put a relay with its own credentials in
front of SMTP — **Use a different password for sending** under the SMTP fields reveals a second
field. The username stays shared. **Use the same password for sending** takes it away again.
6. **Test connection** probes IMAP and SMTP separately and reports each one, so a working mailbox
with a wrong SMTP port says so instead of failing at the first send.