mirror of
https://github.com/karatektus/pl_mail.git
synced 2026-10-25 10:04:18 +00:00
Mirror docs/ from 896f30e
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 |
|
||||
|
||||
@@ -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 |
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 |
|
||||
|
||||
@@ -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 |
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
Reference in new issue
Block a user