[{"data":1,"prerenderedAt":1446},["ShallowReactive",2],{"search-de":3,"content-de-developer\u002Fpostbox-architecture":4,"surround-de-\u002Fdeveloper\u002Fpostbox-architecture":1437},[],{"id":5,"title":6,"body":7,"description":1429,"extension":1430,"meta":1431,"navigation":1432,"path":1433,"seo":1434,"stem":1435,"__hash__":1436},"content_de\u002F3.developer\u002F14.postbox-architecture.md","Postbox-Architektur",{"type":8,"value":9,"toc":1414},"minimark",[10,20,48,53,63,67,86,92,144,147,243,247,253,569,573,587,599,605,614,620,629,635,638,786,789,795,820,844,884,888,891,1012,1043,1052,1056,1062,1093,1101,1107,1135,1160,1163,1241,1259,1263,1360,1364],[11,12,13,14,19],"p",{},"Postbox ist Owlats Funktion für persönliche Mail: Postfächer pro Benutzer mit Webmail-Oberfläche und nativer Unterstützung für IMAP4rev1 \u002F SMTP-Submission. Diese Seite behandelt die Implementierung. Die anwenderseitige Anleitung finden Sie unter ",[15,16,18],"a",{"href":17},"\u002Fguide\u002Fpostbox","Postbox in der Produktanleitung",".",[21,22,25],"callout",{"title":23,"type":24},"Feature-Flag","info",[11,26,27,28,32,33,36,37,40,41,44,45,47],{},"Gehostetes Postbox wird durch das Flag ",[29,30,31],"code",{},"postbox"," gegatet (standardmäßig aus). Das Aktivieren schaltet das Docker-Compose-Profil ",[29,34,35],{},"personal-mail"," frei, das den Service ",[29,38,39],{},"apps\u002Fimap"," startet. Das separate Flag ",[29,42,43],{},"mail.external"," (weiter unten behandelt) ist unabhängig von ",[29,46,31],{}," — es erlaubt einem Benutzer, ein bestehendes externes Postfach zu verbinden, ohne eine Versanddomain zu registrieren.",[49,50,52],"h2",{"id":51},"komponentenübersicht","Komponentenübersicht",[54,55,60],"pre",{"className":56,"code":58,"language":59},[57],"language-text","                        ┌────────────────────────────────────┐\n   ┌──────────────────► │ apps\u002Fapi (Convex)                  │ ◄── webmail UI\n   │                    │  ├── mail\u002Fmailbox \u002F mail\u002Fimap      │     \u002Fdashboard\u002Fpostbox\u002F*\n   │                    │  ├── mail\u002Ffolders \u002F mail\u002Flabels    │\n   │                    │  ├── mail\u002Fdrafts + draftLifecycle  │\n   │                    │  ├── mail\u002Foutbound (+ lifecycle)   │\n   │                    │  ├── mail\u002Ffilters \u002F mail\u002Faliases   │\n   │                    │  ├── mail\u002FappPasswords (PBKDF2)    │\n   │                    │  ├── mail\u002FauthHttp (HMAC verify)   │\n   │                    │  └── mail\u002Fdelivery (inbound route) │\n   │                    └────────────────────────────────────┘\n   │                              ▲              ▲\n   │                              │ webhook      │ verify-cred\n   │                              │ (delivery)   │ (HMAC)\n   │                              │              │\n┌──┴───────────┐     SMTP    ┌────┴──────────────┴────┐    IMAP\u002FSMTP    ┌──────────────┐\n│ apps\u002Fweb     │ submission  │ apps\u002Fmta               │    submission   │ native       │\n│ (composer)   │────────────►│  outbound queue        │◄────────────────│ clients      │\n└──────────────┘             │  inbound routing       │                 │ (Apple Mail, │\n                             └────────┬───────────────┘                 │  Thunderbird,│\n                                      │  bind to                        │  mobile)     │\n                                      │  mailboxResolver                └──────┬───────┘\n                                      ▼                                        │\n                             ┌────────────────────────┐                        │\n                             │ apps\u002Fimap              │ ◄──────────────────────┘\n                             │  IMAP4rev1 server      │     IMAP fetch \u002F store\n                             │  port 993 implicit TLS │\n                             └────────────────────────┘\n                                      │\n                                      └─── reads\u002Fwrites via Convex client\n                                           (uses CONVEX_URL + CONVEX_ADMIN_KEY)\n","text",[29,61,58],{"__ignoreMap":62},"",[49,64,66],{"id":65},"schema","Schema",[11,68,69,70,73,74,77,78,81,82,85],{},"Die Mail-Tabellen sind in ",[29,71,72],{},"apps\u002Fapi\u002Fconvex\u002Fschema\u002Fmail.ts"," definiert (als ",[29,75,76],{},"mailTables"," nach ",[29,79,80],{},"schema.ts"," re-exportiert und dort in ",[29,83,84],{},"defineSchema()"," gespreadet). Die wichtigsten Beziehungen:",[54,87,90],{"className":88,"code":89,"language":59},[57],"mailboxes (1)──(*) mailFolders (*)──(*) mailMessages (*)──(1) mailThreads\n         ├────(*) mailAliases\n         ├────(*) mailAppPasswords\n         ├────(*) mailFilters\n         ├────(*) mailLabels\n         ├────(*) mailDrafts\n         ├────(*) mailSignatures\n         ├────(*) mailForwarding\n         ├────(*) mailVacationResponders \u002F mailVacationLog\n         ├────(*) mailContacts\n         └────(*) mailAuditLog \u002F mailAuthFailures\n\nexternalMailAccounts (1)──(1) mailboxes (kind='external')\n                     └────(*) externalMailFolderSync\npendingMailboxes        — reserved-mailbox intent attached to an invitation\n",[29,91,89],{"__ignoreMap":62},[11,93,94,95,99,100,103,104,107,108,99,110,113,114,117,118,121,122,99,124,127,128,131,132,135,136,139,140,143],{},"Es gibt ",[96,97,98],"strong",{},"keine"," Tabelle ",[29,101,102],{},"mailOutbound"," — der Zustand ausgehender Mail liegt im eingebetteten Objekt ",[29,105,106],{},"mailMessages.outbound"," (ein denormalisiertes Aggregat plus ein Array pro Empfänger). Es gibt ",[96,109,98],{},[29,111,112],{},"mailIdentities"," — Versandidentitäten sind ein Modul (",[29,115,116],{},"mail\u002Fidentities.ts","), das über ",[29,119,120],{},"mailSignatures"," und die Allowed-From-Menge des Postfachs gelegt ist. Es gibt ",[96,123,98],{},[29,125,126],{},"mailSnooze"," — das Zurückstellen steckt in den Feldern ",[29,129,130],{},"snoozedUntil"," \u002F ",[29,133,134],{},"snoozedFromFolderId"," auf ",[29,137,138],{},"mailMessages"," und wird über den Index ",[29,141,142],{},"by_snoozed_until"," von einem Cron im Minutentakt abgeräumt.",[11,145,146],{},"Hinweise zu den Indizes:",[148,149,150,193,211],"ul",{},[151,152,153,154,156,157,135,160,163,164,135,167,170,171,174,175,178,179,181,182,135,185,188,189,192],"li",{},"Die Posteingangssortierung von ",[29,155,138],{}," nutzt ",[29,158,159],{},"by_mailbox_and_received",[29,161,162],{},"[mailboxId, receivedAt]",". IMAP-UID-Bereiche nutzen ",[29,165,166],{},"by_folder_and_uid",[29,168,169],{},"[folderId, uid]","; die schnelle CONDSTORE-Resynchronisierung nutzt ",[29,172,173],{},"by_folder_and_modseq",". Thread-Lesezugriffe nutzen ",[29,176,177],{},"by_thread","; der Snooze-Cron nutzt ",[29,180,142],{},". Außerdem gibt es einen Volltextsuchindex ",[29,183,184],{},"search_messages",[29,186,187],{},"snippet",". (Einen ",[29,190,191],{},"internalDate","-Index gibt es nicht.)",[151,194,195,198,199,202,203,206,207,210],{},[29,196,197],{},"mailAppPasswords"," speichert PBKDF2-SHA256-Hashes (100k Iterationen, kodiert als ",[29,200,201],{},"\u003Csalt-hex>:\u003Chash-hex>",") — der Klartext wird dem Benutzer bei der Erstellung genau einmal angezeigt. Ein separates ",[29,204,205],{},"passwordPrefix"," (die ersten 4 Zeichen) engt die Kandidatenmenge über ",[29,208,209],{},"by_prefix"," ein, bevor der bewusst langsame Hash-Vergleich läuft.",[151,212,213,216,217,220,221,135,224,227,228,231,232,235,236,239,240,242],{},[29,214,215],{},"mailAliases"," speichert die kanonische Kleinschreibung der Adresse im Feld ",[29,218,219],{},"alias"," und ist über ",[29,222,223],{},"by_alias",[29,225,226],{},"[alias]"," indexiert (zusätzlich ",[29,229,230],{},"by_target","). Die ",[29,233,234],{},"organizationId"," steht zwar in der Zeile, ist aber ",[96,237,238],{},"nicht"," Teil des Lookup-Index — der Inbound-Router löst einen Empfänger allein über das Feld ",[29,241,219],{}," auf.",[49,244,246],{"id":245},"modulaufbau-convex","Modulaufbau (Convex)",[11,248,249,250,19],{},"Alle Mail-Module liegen unter ",[29,251,252],{},"apps\u002Fapi\u002Fconvex\u002Fmail\u002F",[254,255,256,269],"table",{},[257,258,259],"thead",{},[260,261,262,266],"tr",{},[263,264,265],"th",{},"Modul",[263,267,268],{},"Verantwortung",[270,271,272,289,299,309,328,338,351,370,380,393,403,416,426,440,450,463,473,494,504,514,528,539,549],"tbody",{},[260,273,274,286],{},[275,276,277,131,280,131,283],"td",{},[29,278,279],{},"mailbox.ts",[29,281,282],{},"mailboxActions.ts",[29,284,285],{},"mailboxQueries.ts",[275,287,288],{},"Postfach-Lebenszyklus, Lesezugriffe sowie Nachrichten-Lesezugriffe\u002F-Index",[260,290,291,296],{},[275,292,293],{},[29,294,295],{},"pendingMailbox.ts",[275,297,298],{},"Reservierungsabsicht für ein Postfach an einer BetterAuth-Einladung, beim Annehmen eingelöst",[260,300,301,306],{},[275,302,303],{},[29,304,305],{},"folders.ts",[275,307,308],{},"Initialisierung der Systemordner, Ordner-CRUD",[260,310,311,316],{},[275,312,313],{},[29,314,315],{},"messageActions.ts",[275,317,318,319,322,323,131,325],{},"Nachrichten-Mutationen (gelesen\u002Fungelesen, Label-Operationen, Verschieben) — es gibt keine ",[29,320,321],{},"messages.ts","; Lesezugriffe liegen in ",[29,324,279],{},[29,326,327],{},"imap.ts",[260,329,330,335],{},[275,331,332],{},[29,333,334],{},"labels.ts",[275,336,337],{},"Benutzerdefinierte Labels",[260,339,340,348],{},[275,341,342,131,345],{},[29,343,344],{},"drafts.ts",[29,346,347],{},"draftLifecycle.ts",[275,349,350],{},"Entwürfe beim Verfassen mit Autospeicherung; die Zustandsmaschine der Entwürfe + Sende-Kaskade",[260,352,353,367],{},[275,354,355,131,358,131,361,131,364],{},[29,356,357],{},"outbound.ts",[29,359,360],{},"outboundCron.ts",[29,362,363],{},"outboundQueries.ts",[29,365,366],{},"postboxOutboundLifecycle.ts",[275,368,369],{},"Versand-Action für ausgehende Mail, Cron für geplanten Versand, Query-Helfer und die Zustandsmaschine pro Empfänger",[260,371,372,377],{},[275,373,374],{},[29,375,376],{},"filters.ts",[275,378,379],{},"Sieve-ähnliche Regel-Engine",[260,381,382,390],{},[275,383,384,131,387],{},[29,385,386],{},"aliases.ts",[29,388,389],{},"aliasesActions.ts",[275,391,392],{},"Aliasse, die in ein Postfach routen",[260,394,395,400],{},[275,396,397],{},[29,398,399],{},"forwarding.ts",[275,401,402],{},"Regeln für ausgehende Weiterleitung",[260,404,405,413],{},[275,406,407,131,410],{},[29,408,409],{},"signatures.ts",[29,411,412],{},"identities.ts",[275,414,415],{},"Signaturen + Auflösung von Allowed-From \u002F Versandidentität",[260,417,418,423],{},[275,419,420],{},[29,421,422],{},"appPasswords.ts",[275,424,425],{},"Speicherung und Verifizierung von PBKDF2-SHA256-Credentials",[260,427,428,433],{},[275,429,430],{},[29,431,432],{},"authHttp.ts",[275,434,435,436,439],{},"HMAC-signierter ",[29,437,438],{},"verify-credential","-Endpunkt für MTA\u002FIMAP",[260,441,442,447],{},[275,443,444],{},[29,445,446],{},"authRateLimit.ts",[275,448,449],{},"Auth-Drosselung pro Adresse",[260,451,452,460],{},[275,453,454,131,457],{},[29,455,456],{},"snooze.ts",[29,458,459],{},"vacation.ts",[275,461,462],{},"Snooze-Sweep + Abwesenheits-Autoresponder nach RFC 3834",[260,464,465,470],{},[275,466,467],{},[29,468,469],{},"contacts.ts",[275,471,472],{},"Adressbuch pro Postfach",[260,474,475,483],{},[275,476,477,131,480],{},[29,478,479],{},"ai.ts",[29,481,482],{},"aiGate.ts",[275,484,485,486,489,490,493],{},"KI im Posteingang (Thread zusammenfassen + Antworten vorschlagen) an der gemeinsamen LLM-Naht; ",[29,487,488],{},"aiGate"," erzwingt vor jedem Aufruf das ",[29,491,492],{},"ai","-Flag + das Rate-Limit pro Benutzer",[260,495,496,501],{},[275,497,498],{},[29,499,500],{},"delivery.ts",[275,502,503],{},"Annahme eingehender Mail, Ordner-Routing, Anwendung der Filter",[260,505,506,511],{},[275,507,508],{},[29,509,510],{},"deliveryHooks.ts",[275,512,513],{},"Hooks nach der Zustellung (Benachrichtigungen, Trigger)",[260,515,516,521],{},[275,517,518],{},[29,519,520],{},"webhook.ts",[275,522,523,524,527],{},"Handler für den eingehenden Zustell-Webhook vom MTA (",[29,525,526],{},"handleMailWebhook",")",[260,529,530,534],{},[275,531,532],{},[29,533,327],{},[275,535,536,537],{},"Convex-seitige Helfer für den IMAP-Server (Fetch-Slices, Store\u002FCopy\u002FMove, Ordnerzustand); die Volltextsuche für die Webmail-Oberfläche liegt in ",[29,538,279],{},[260,540,541,546],{},[275,542,543],{},[29,544,545],{},"permissions.ts",[275,547,548],{},"Berechtigungsprüfungen auf Postfachebene",[260,550,551,562],{},[275,552,553,131,556,131,559],{},[29,554,555],{},"externalAccounts.ts",[29,557,558],{},"externalAccountsActions.ts",[29,560,561],{},"externalDelivery.ts",[275,563,564,565,527],{},"Verbinden\u002FTesten\u002FSynchronisieren externer Postfächer (siehe ",[15,566,568],{"href":567},"#external-mailboxes-mailexternal","Externe Postfächer",[49,570,572],{"id":571},"auth-ablauf-mit-app-passwörtern","Auth-Ablauf mit App-Passwörtern",[11,574,575,576,579,580,582,583,586],{},"Native IMAP-\u002FSMTP-Clients können die Dashboard-Sitzung nicht verwenden. Sie authentifizieren sich mit ",[96,577,578],{},"App-Passwörtern"," — auf ein Postfach beschränkte, widerrufbare Tokens, die als PBKDF2-SHA256-Hashes in ",[29,581,197],{}," liegen. Beide Verifikationspfade münden in derselben internen Convex-Action, ",[29,584,585],{},"internal.mail.appPasswords.verify",", erreichen sie aber unterschiedlich.",[11,588,589,594,595,598],{},[96,590,591,592,527],{},"IMAP (",[29,593,39],{}," — der IMAP-Server hält ",[29,596,597],{},"CONVEX_ADMIN_KEY"," und ruft die Action direkt über den Convex-Client auf:",[54,600,603],{"className":601,"code":602,"language":59},[57],"1.  user opens \u002Fdashboard\u002Fpreferences\u002Fapp-passwords\n2.  mail\u002FappPasswords.generate creates a row with PBKDF2(password); returns plaintext once\n3.  user pastes plaintext into Apple Mail; client connects to apps\u002Fimap (port 993)\n4.  commands\u002Flogin calls the Convex action `mail\u002FappPasswords:verify`\n        ({ address, password }) over the admin-key client\n5.  appPasswords.verify narrows candidates by passwordPrefix, runs PBKDF2(password),\n        and returns { ok: true, mailboxId, appPasswordId, ... } on success\n6.  apps\u002Fimap binds the IMAP session to that mailboxId\n        — every subsequent IMAP command runs against Convex as that mailbox\n",[29,604,602],{"__ignoreMap":62},[11,606,607,613],{},[96,608,609,610,527],{},"SMTP-Submission (über ",[29,611,612],{},"apps\u002Fmta"," — der MTA hält den Admin-Key nicht und postet stattdessen an den HMAC-signierten HTTP-Endpunkt:",[54,615,618],{"className":616,"code":617,"language":59},[57],"1.  desktop client submits over SMTP to apps\u002Fmta's submission port\n2.  apps\u002Fmta POSTs \u002Fwebhooks\u002Fmta-verify-credential\n        body:    { address, password, scope: 'imap' | 'smtp' }\n        headers: x-mta-signature: HMAC-SHA256(`${timestamp}.${body}`, MTA_WEBHOOK_SECRET)\n                 x-mta-timestamp: \u003Cunix-seconds>\n3.  mail\u002FauthHttp.ts verifies the HMAC + freshness, then delegates to\n        internal.mail.appPasswords.verify, returning { ok, mailboxId, appPasswordId, ... }\n",[29,619,617],{"__ignoreMap":62},[11,621,622,623,626,627,19],{},"Das HMAC-Muster bedeutet, dass der MTA für Credential-Prüfungen nie den Convex-Admin-Key benötigt — nur das gemeinsame ",[29,624,625],{},"MTA_WEBHOOK_SECRET",". Der IMAP-Server, der die vollständige Kommandoschleife gegen Convex fährt, hält hingegen ",[29,628,597],{},[49,630,632,633,527],{"id":631},"imap-server-appsimap","IMAP-Server (",[29,634,39],{},[11,636,637],{},"Die Konfiguration wird beim Start aus der Umgebung gelesen:",[254,639,640,653],{},[257,641,642],{},[260,643,644,647,650],{},[263,645,646],{},"Umgebungsvariable",[263,648,649],{},"Standard",[263,651,652],{},"Zweck",[270,654,655,670,685,704,720,735,750,763,774],{},[260,656,657,662,667],{},[275,658,659],{},[29,660,661],{},"IMAP_PORT",[275,663,664],{},[29,665,666],{},"993",[275,668,669],{},"TCP-Listen-Port",[260,671,672,677,682],{},[275,673,674],{},[29,675,676],{},"IMAP_LISTEN",[275,678,679],{},[29,680,681],{},"0.0.0.0",[275,683,684],{},"Bind-Adresse",[260,686,687,692,697],{},[275,688,689],{},[29,690,691],{},"IMAP_GREETING_HOST",[275,693,694],{},[29,695,696],{},"hostname()",[275,698,699,700,703],{},"Hostname in der ",[29,701,702],{},"* OK","-Begrüßung",[260,705,706,714,717],{},[275,707,708,131,711],{},[29,709,710],{},"IMAP_TLS_CERT",[29,712,713],{},"IMAP_TLS_CERT_FILE",[275,715,716],{},"—",[275,718,719],{},"TLS-Zertifikat (inline oder Pfad)",[260,721,722,730,732],{},[275,723,724,131,727],{},[29,725,726],{},"IMAP_TLS_KEY",[29,728,729],{},"IMAP_TLS_KEY_FILE",[275,731,716],{},[275,733,734],{},"Privater TLS-Schlüssel",[260,736,737,742,747],{},[275,738,739],{},[29,740,741],{},"TLS_CERT_DIR",[275,743,744],{},[29,745,746],{},"\u002Fopt\u002Fowlat\u002Fcerts",[275,748,749],{},"Gemeinsames Zertifikatsverzeichnis als Fallback",[260,751,752,757,760],{},[275,753,754],{},[29,755,756],{},"CONVEX_URL",[275,758,759],{},"— erforderlich",[275,761,762],{},"URL des Convex-Backends",[260,764,765,769,771],{},[275,766,767],{},[29,768,597],{},[275,770,759],{},[275,772,773],{},"Admin-Key für den IMAP→Convex-Client",[260,775,776,781,783],{},[275,777,778],{},[29,779,780],{},"REDIS_URL",[275,782,716],{},[275,784,785],{},"Optionales Backend für Rate Limiting",[11,787,788],{},"Aufbau des Quellcodes:",[54,790,793],{"className":791,"code":792,"language":59},[57],"apps\u002Fimap\u002Fsrc\u002F\n├── index.ts        # process entry, signal handling\n├── server.ts       # net.createServer + TLS upgrade\n├── connection.ts   # IMAP pump: socket lifecycle, line\u002Fliteral buffering\n├── parser.ts       # IMAP command parser\n├── mime.ts         # RFC 5322 \u002F 2045 parser\n├── convex.ts       # Convex client wrapper used by command modules\n├── rateLimit.ts    # per-IP and per-credential throttling\n├── config.ts       # env loading\n├── logger.ts\n└── commands\u002F       # one module per IMAP verb (ADR-0016)\n    ├── walker.ts   # typed dispatch registry + CAPABILITY-line assembly\n    ├── types.ts    # ImapVerb, CommandModule, session\u002Fdeps types\n    ├── helpers\u002F    # shared session helpers\n    └── \u003Cverb>\u002Findex.ts   # login, select, fetch, store, copy, move, idle, …\n",[29,794,792],{"__ignoreMap":62},[11,796,797,800,801,804,805,808,809,812,813,815,816,819],{},[29,798,799],{},"connection.ts"," ist die ",[96,802,803],{},"Pumpe"," — sie besitzt den Socket, die Zeilenpufferung und die Literal-Absorption und weiß nichts über IMAP-Verben. Die Kommandobehandlung wurde in Module pro Verb unter ",[29,806,807],{},"commands\u002F\u003Cverb>\u002Findex.ts"," ausgelagert, die über ",[29,810,811],{},"commands\u002Fwalker.ts"," dispatcht werden (siehe ADR-0016 und den Kopfkommentar in ",[29,814,799],{},"). Module mit mehreren Verben (LIST + LSUB, SELECT + EXAMINE, UNSELECT + CLOSE) registrieren sich unter jedem Verb, das sie deklarieren; der Walker setzt außerdem die ",[29,817,818],{},"CAPABILITY","-Zeile aus den deklarierten Atomen jedes Moduls zusammen.",[21,821,823],{"title":822,"type":24},"Oktettgenaues Literal-Framing",[11,824,825,827,828,831,832,835,836,839,840,843],{},[29,826,799],{}," belässt den Socket in seinem Standard-Binärmodus (kein ",[29,829,830],{},"setEncoding",") und puffert rohe Oktette in einem ",[29,833,834],{},"Buffer",", sodass die Literal-Absorption (RFC 3501 §4.3) ",[96,837,838],{},"Bytes zählt, nicht dekodierte Zeichen",". 8-Bit-\u002FBinär-MIME-Bodys und ",[29,841,842],{},"{N}","-Oktettdeklarationen werden korrekt gerahmt; Kommandotext wird erst dann als UTF-8 dekodiert, wenn eine vollständige, per CRLF terminierte Zeile abgetrennt wurde.",[11,845,846,847,850,851,854,855,858,859,862,863,866,867,870,871,870,874,870,877,870,880,883],{},"Lebenszyklus jeder Verbindung: ",[29,848,849],{},"* OK greeting"," → ",[29,852,853],{},"LOGIN","\u002F",[29,856,857],{},"AUTHENTICATE"," (→ ",[29,860,861],{},"mail\u002FauthHttp",") → ",[29,864,865],{},"SELECT inbox"," → Kommandoschleife (",[29,868,869],{},"FETCH",", ",[29,872,873],{},"STORE",[29,875,876],{},"COPY",[29,878,879],{},"MOVE",[29,881,882],{},"IDLE",", …).",[49,885,887],{"id":886},"ausgehend-verfassen-smtp","Ausgehend: Verfassen → SMTP",[11,889,890],{},"Der Mailversand aus Postbox läuft über den bestehenden MTA, nicht über den IMAP-Server. Der Ablauf:",[892,893,894,909,938,977],"ol",{},[151,895,896,897,900,901,904,905,908],{},"Der Benutzer klickt auf Senden. Die Web-App überführt den Entwurf nach ",[29,898,899],{},"pending_send"," (Zeitfenster zum Rückgängigmachen) und plant ",[29,902,903],{},"internal.mail.outbound.dispatchDraft"," ein (",[29,906,907],{},"apps\u002Fapi\u002Fconvex\u002Fmail\u002Foutbound.ts",").",[151,910,911,914,915,918,919,922,923,926,927,930,931,933,934,937],{},[29,912,913],{},"dispatchDraft"," (eine Node-Action) validiert den Entwurfszustand und das Undo-Token, prüft jeden Anhang über den ClamAV-Endpunkt des MTA (fail-open bei Ausfall), rendert die finalen HTML- und Klartext-Bodys über ",[29,916,917],{},"@owlat\u002Femail-renderer",", baut eine RFC-5322-Multipart-Nachricht und legt die rohe ",[29,920,921],{},".eml"," in ",[29,924,925],{},"ctx.storage"," ab. Anschließend übergibt sie an ",[29,928,929],{},"internal.mail.draftLifecycle.transition({ to: 'sent' })",", das atomar die ",[29,932,138],{},"-Zeile im Ordner „Gesendet“ mit ",[29,935,936],{},"outbound.state='queued'"," einfügt und den Entwurf löscht. Siehe ADR-0028.",[151,939,940,941,944,945,948,949,952,953,956,957,960,961,964,965,968,969,972,973,976],{},"Bei einem ",[96,942,943],{},"gehosteten"," Postfach POSTet sie einen MTA-",[29,946,947],{},"\u002Fsend","-Aufruf pro Empfänger und stellt der MTA-Nachrichten-ID ",[29,950,951],{},"pb-\u003CmailMessageId>-\u003Cidx>"," voran, damit der Bounce-\u002FSent-Webhook die Zeile wieder auffinden kann. Ein synchroner ",[29,954,955],{},"5xx"," überführt diesen Empfänger nach ",[29,958,959],{},"bounced","; ein Netzwerkfehler überführt ihn nach ",[29,962,963],{},"failed"," — beides über ",[29,966,967],{},"internal.mail.postboxOutboundLifecycle.transition",". (Bei einem ",[96,970,971],{},"externen"," Postfach wird der unten beschriebene Pfad ",[29,974,975],{},"dispatchViaExternalWorker"," mit einem einzigen POST genommen.)",[151,978,979,980,983,984,987,988,991,992,995,996,999,1000,1003,1004,1007,1008,1011],{},"Asynchrone MTA-Zustellereignisse treffen unter ",[29,981,982],{},"POST \u002Fwebhooks\u002Fmta"," ein. Die Webhook-Zeremonie (Rate Limiting, Signaturprüfung, Audit, Parsen, Dispatch) liegt in ",[29,985,986],{},"webhooks\u002Fpipeline.ts"," + ",[29,989,990],{},"webhooks\u002Fadapters\u002Fmta.ts","; die Route selbst wird vom gemeinsamen Handler ",[29,993,994],{},"providerFeedbackWebhook('mta')"," bedient, der nichts weiter tut, als diesen Adapter aus der Registry aufzulösen und an ",[29,997,998],{},"runInboundPipeline"," zu delegieren. Für Ereignisse, deren Provider-Nachrichten-ID das Präfix ",[29,1001,1002],{},"pb-"," trägt, ruft der Dispatcher (",[29,1005,1006],{},"webhooks\u002Fdispatcher.ts",") ",[29,1009,1010],{},"internal.mail.postboxOutboundLifecycle.transitionByMtaMessageId"," auf, das die ID parst und den Übergang für den jeweiligen Empfänger anwendet.",[11,1013,1014,1016,1017,1020,1021,1024,1025,1028,1029,1032,1033,1035,1036,1038,1039,1042],{},[29,1015,366],{}," ist der ",[96,1018,1019],{},"einzige Schreiber"," jedes ",[29,1022,1023],{},"mailMessages.outbound.recipients[].state"," und der einzige Erzeuger des abgeleiteten Aggregats ",[29,1026,1027],{},"mailMessages.outbound.state"," (siehe ADR-0012). Die Zustände pro Empfänger sind ",[29,1030,1031],{},"queued | sent | bounced | failed","; ",[29,1034,959],{}," und ",[29,1037,963],{}," sind terminal. Die Aggregatspalte ergänzt ein weiteres Literal, ",[29,1040,1041],{},"partial",", wenn die Empfänger in gemischten Zuständen sind. (Persönliche Mail verwirft die Klassifikation in Hard- und Soft-Bounces — das ist ein Belang der Kampagnenseite.)",[11,1044,1045,1046,1048,1049,1051],{},"Native SMTP-Submission von einem Desktop-Client nimmt denselben Weg: Der MTA nimmt SMTP auf seinem Submission-Port an, ruft ",[29,1047,861],{}," auf, um die Credentials gegen ",[29,1050,197],{}," zu prüfen, und reiht die Nachricht dann regulär ein.",[49,1053,1055],{"id":1054},"eingehend-mx-postfach","Eingehend: MX → Postfach",[54,1057,1060],{"className":1058,"code":1059,"language":59},[57],"inbound SMTP ──► apps\u002Fmta ──► mailboxResolver (apps\u002Fmta\u002Fsrc\u002Finbound)\n                                  │\n                                  │ POST \u002Fwebhooks\u002Fmta-mailbox\n                                  ▼\n                         mail\u002Fwebhook.ts ──► mail\u002Fdelivery.ts\n                                              │\n                                              ├── resolve alias → mailbox\n                                              ├── apply mailFilters\n                                              ├── insert mailMessages row\n                                              ├── run mail\u002FdeliveryHooks\n                                              └── notify subscribers (Convex realtime)\n",[29,1061,1059],{"__ignoreMap":62},[11,1063,1064,1065,1068,1069,1072,1073,1075,1076,1079,1080,1068,1083,850,1086,1089,1090,908],{},"Der MTA routet Zustellungen an persönliche Postfächer auf die dedizierte Route ",[29,1066,1067],{},"POST \u002Fwebhooks\u002Fmta-mailbox"," (",[29,1070,1071],{},"apps\u002Fapi\u002Fconvex\u002Fhttp.ts",", bedient von ",[29,1074,526],{}," aus ",[29,1077,1078],{},"mail\u002Fwebhook.ts","); die Routing-Entscheidung fällt in ",[29,1081,1082],{},"apps\u002Fmta\u002Fsrc\u002Fwebhooks\u002FconvexNotifier.ts",[29,1084,1085],{},"inbound.mailbox.received",[29,1087,1088],{},"\u002Fwebhooks\u002Fmta-mailbox",", alles andere → ",[29,1091,1092],{},"\u002Fwebhooks\u002Fmta",[11,1094,1095,1097,1098,1100],{},[29,1096,215],{}," ist die Nachschlagetabelle — jede angenommene Empfängeradresse muss vor der Zustellung auf eine ",[29,1099,219],{},"-Zeile passen. Spam- und ClamAV-Prüfungen laufen (sofern aktiviert) vor dem Einfügen.",[49,1102,1104,1105,527],{"id":1103},"externe-postfächer-mailexternal","Externe Postfächer (",[29,1106,43],{},[11,1108,1109,1110,1112,1113,1116,1117,1123,1124,1127,1128,1131,1132,19],{},"Getrennt vom gehosteten Postbox erlaubt das Flag ",[29,1111,43],{}," (Label ",[96,1114,1115],{},"\"Connect external mailbox\"",") jedem Benutzer, sein eigenes bestehendes Gmail-, Fastmail- oder Firmenpostfach über IMAP+SMTP zu verbinden — persönliche Mail, ohne eine Versanddomain zu registrieren. Es ist ",[96,1118,1119,1120,1122],{},"unabhängig vom gehosteten ",[29,1121,31],{},"-Flag"," (bewusst kein ",[29,1125,1126],{},"requires: ['postbox']","), aktiviert das Docker-Compose-Profil ",[29,1129,1130],{},"external-mail"," und startet den Worker ",[29,1133,1134],{},"apps\u002Fmail-sync",[21,1136,1138],{"title":1137,"type":24},"Konfiguration",[11,1139,1140,1141,987,1144,1147,1148,1151,1152,1155,1156,1159],{},"Der mail-sync-Worker wird über ",[29,1142,1143],{},"MAIL_SYNC_API_URL",[29,1145,1146],{},"MAIL_SYNC_API_KEY"," erreicht. Die Credentials des verbundenen Kontos werden verschlüsselt gespeichert (AES-256-GCM) auf ",[29,1149,1150],{},"externalMailAccounts","; lesende Queries geben den Chiffretext nie zurück. Das Convex-Backend hält ",[29,1153,1154],{},"INSTANCE_SECRET"," und entschlüsselt (in der internen Action ",[29,1157,1158],{},"getCredentialsForWorker","), um die Klartext-Credentials anschließend über den Admin-Key-Kanal an den mail-sync-Worker zu übergeben.",[11,1161,1162],{},"Worin es sich von einem gehosteten Postfach unterscheidet:",[148,1164,1165,1179,1205,1218],{},[151,1166,1167,1168,1171,1172,1175,1176,1178],{},"Ein verbundenes Konto erzeugt eine ",[29,1169,1170],{},"mailboxes","-Zeile mit ",[29,1173,1174],{},"kind='external'",", die 1:1 mit einer ",[29,1177,1150],{},"-Zeile verknüpft ist (Host\u002FPort\u002FTLS, Auth-Methode, verschlüsselter Credential-Umschlag, Verbindungsstatus).",[151,1180,1181,1182,1185,1186,1189,1190,870,1193,1196,1197,1200,1201,1204],{},"Der Worker ist der ",[96,1183,1184],{},"Client"," des entfernten IMAP-Servers. ",[29,1187,1188],{},"externalMailFolderSync"," verfolgt inkrementelle Fetch-Cursor pro (Konto, Ordner) (",[29,1191,1192],{},"remoteUidValidity",[29,1194,1195],{},"lastSeenUid",", optional CONDSTORE ",[29,1198,1199],{},"lastSeenModseq","), getrennt vom eigenen UID-Zustand der ",[29,1202,1203],{},"mailFolders"," (der Owlat-als-IMAP-Server abbildet).",[151,1206,1207,1208,1211,1212,131,1215,908],{},"Vom entfernten Server abgeholte eingehende Nachrichten werden über ",[29,1209,1210],{},"mail\u002FexternalDelivery.ts"," eingespeist (",[29,1213,1214],{},"ingestExternalRaw",[29,1216,1217],{},"ingestExternalMessage",[151,1219,1220,1221,1223,1224,1227,1228,1230,1231,1233,1234,1236,1237,1240],{},"Ausgehende Mail eines externen Postfachs umgeht den MTA-Pfad pro Empfänger: ",[29,1222,913],{}," prüft ",[29,1225,1226],{},"internal.mail.outboundTransport.resolveOutboundTransport"," und ruft bei ",[29,1229,1174],{}," ",[29,1232,975],{}," auf — ein einzelner POST an das ",[29,1235,947],{}," des Workers, der über das eigene SMTP des Benutzers sendet und die gesendete Kopie per APPEND in den entfernten Sent-Ordner legt. SMTP ist synchron, sodass das Ergebnis des Workers pro Empfänger ohne Webhook direkt auf ",[29,1238,1239],{},"postboxOutboundLifecycle"," abgebildet wird.",[11,1242,1243,1244,1246,1247,1068,1249,131,1252,1255,1256,1258],{},"Die Module für Verbinden\u002FTesten\u002FCredentials sind ",[29,1245,555],{}," (Queries + interne Mutationen), ",[29,1248,558],{},[29,1250,1251],{},"connect",[29,1253,1254],{},"testConnection"," \u002F Credential-Handhabung) und ",[29,1257,561],{}," (Ingest).",[49,1260,1262],{"id":1261},"integrationspunkte","Integrationspunkte",[254,1264,1265,1275],{},[257,1266,1267],{},[260,1268,1269,1272],{},[263,1270,1271],{},"Andere Funktion",[263,1273,1274],{},"Wie sie mit Postbox zusammenspielt",[270,1276,1277,1298,1311,1322,1333,1344],{},[260,1278,1279,1284],{},[275,1280,1281,1282,527],{},"MTA (",[29,1283,612],{},[275,1285,1286,1287,1289,1290,1068,1292,1295,1296],{},"Liefert eingehende Mail über ",[29,1288,1088],{}," ein; verifiziert Credentials der SMTP-Submission über ",[29,1291,861],{},[29,1293,1294],{},"\u002Fwebhooks\u002Fmta-verify-credential","); nimmt ausgehende Sendungen entgegen; postet Zustellereignisse an ",[29,1297,1092],{},[260,1299,1300,1305],{},[275,1301,1302,1303,527],{},"mail-sync-Worker (",[29,1304,1134],{},[275,1306,1307,1308,1310],{},"Für ",[29,1309,43],{},": verbindet sich mit entfernten IMAP-Servern, speist eingehende Mail ein und versendet ausgehende Mail über das eigene SMTP des Benutzers",[260,1312,1313,1319],{},[275,1314,1315,1316,527],{},"Inhaltsscanner (",[29,1317,1318],{},"scan.content",[275,1320,1321],{},"Läuft bei eingehender Mail vor dem Einfügen",[260,1323,1324,1330],{},[275,1325,1326,1327,527],{},"ClamAV (",[29,1328,1329],{},"scan.files",[275,1331,1332],{},"Prüft Anhänge bei der eingehenden Zustellung und beim ausgehenden Versand",[260,1334,1335,1338],{},[275,1336,1337],{},"Benachrichtigungsanbieter",[275,1339,1340,1343],{},[29,1341,1342],{},"mail\u002FdeliveryHooks"," ruft den Benachrichtigungsanbieter für Hinweise auf neue Mail auf",[260,1345,1346,1352],{},[275,1347,1348,1349,527],{},"KI-Agent (",[29,1350,1351],{},"ai.agent",[275,1353,1354,1355,1359],{},"Lauscht auf eingehende Ereignisse der ",[1356,1357,1358],"em",{},"gemeinsamen"," Support-Posteingangsfunktion, nicht auf Postbox — Postbox ist bewusst rein persönlich",[49,1361,1363],{"id":1362},"wo-sie-beim-erweitern-ansetzen","Wo Sie beim Erweitern ansetzen",[148,1365,1366,1376,1386,1396,1403],{},[151,1367,1368,1369,1372,1373],{},"Neuer Ordnertyp \u002F Systemordner → ",[29,1370,1371],{},"mail\u002Ffolders.ts"," + Dashboard-Sidebar in ",[29,1374,1375],{},"apps\u002Fweb\u002Fapp\u002Fcomponents\u002Fpostbox\u002F",[151,1377,1378,1379,1382,1383],{},"Neue Filteraktion → Regel-Executor in ",[29,1380,1381],{},"mail\u002Ffilters.ts"," + Oberfläche unter ",[29,1384,1385],{},"\u002Fdashboard\u002Fpreferences\u002Ffilters",[151,1387,1388,1389,1392,1393,1395],{},"Neues IMAP-Kommando → ein Modul ",[29,1390,1391],{},"apps\u002Fimap\u002Fsrc\u002Fcommands\u002F\u003Cverb>\u002Findex.ts"," anlegen und in ",[29,1394,811],{}," registrieren (siehe ADR-0016)",[151,1397,1398,1399,1402],{},"Neuer Zustandsübergang für ausgehende Mail → ",[29,1400,1401],{},"mail\u002FpostboxOutboundLifecycle.ts"," erweitern (der einzige Schreiber des Outbound-Zustands)",[151,1404,1405,1406,1409,1410,1413],{},"Serverseitige Durchsetzung des Feature-Flags → früh in jeder Mutation ",[29,1407,1408],{},"isFlagEnabled(stored, 'postbox')"," (bzw. ",[29,1411,1412],{},"'mail.external'",") prüfen",{"title":62,"searchDepth":1415,"depth":1415,"links":1416},2,[1417,1418,1419,1420,1421,1423,1424,1425,1427,1428],{"id":51,"depth":1415,"text":52},{"id":65,"depth":1415,"text":66},{"id":245,"depth":1415,"text":246},{"id":571,"depth":1415,"text":572},{"id":631,"depth":1415,"text":1422},"IMAP-Server (apps\u002Fimap)",{"id":886,"depth":1415,"text":887},{"id":1054,"depth":1415,"text":1055},{"id":1103,"depth":1415,"text":1426},"Externe Postfächer (mail.external)",{"id":1261,"depth":1415,"text":1262},{"id":1362,"depth":1415,"text":1363},"Wie die Postbox-Funktion für persönliche Mail verdrahtet ist — Schema, IMAP-Server, Authentifizierung per App-Passwort, ausgehendes Relay, eingehende Zustellung und externe Postfächer.","md",{},true,"\u002Fdeveloper\u002Fpostbox-architecture",{"title":6,"description":1429},"3.developer\u002F14.postbox-architecture","jPkiSjZOPX7hyF9UVaLJUOtBChvTfsDjkDfoxXu0Fmw",[1438,1442],{"title":1439,"path":1440,"stem":1441,"children":-1},"E-Mail-Sicherheit","\u002Fdeveloper\u002Femail-security","3.developer\u002F13.email-security",{"title":1443,"path":1444,"stem":1445,"children":-1},"Provider","\u002Fdeveloper\u002Fproviders","3.developer\u002F15.providers",1786915109049]