# Paperless-Connector für Thunderbird / Betterbird Legt Anhänge und ganze E-Mails in einem Paperless-ngx-Archiv ab und fügt Dokumente aus dem Archiv als Anhang oder Freigabelink in Entwürfe ein. Schwestererweiterung zur LibreOffice-Fassung; dieselben Erkenntnisse zur Paperless-Schnittstelle, aber vollständig eigener Code — Thunderbird kennt WebExtensions, LibreOffice UNO. --- ## Funktionen | Ort | Befehl | Wirkung | |---|---|---| | Kontextmenü am Anhang | Anhang in Paperless ablegen | ausgewählte Anhänge als Dokument | | Kontextmenü in der Nachrichtenliste | E-Mail in Paperless ablegen | als PDF mit eingebettetem Original, wahlweise als `.eml` | | Beides | Ablauf | Dateiauswahl, dann je Datei Ziel und Klassifizierung | | Schaltfläche im Nachrichtenfenster | dasselbe für die angezeigte Nachricht | | | Schaltfläche im Verfassenfenster | Aus Paperless einfügen | als Anhang oder als Freigabelink | Beim Ablegen stehen dieselben zwei Ziele zur Wahl wie in der LibreOffice-Fassung: ein **neues Dokument** oder eine **neue Version eines bestehenden**. Der zweite Fall deckt die Lücke, die Dokumentenverwaltungen gern offenlassen: > Die Rechnung kommt als PDF im Anhang. Später entsteht die bearbeitbare > Fassung. Beides gehört zusammen — aber das PDF war zuerst da. --- ## Was beim Ablegen passiert **Anhänge einzeln wählbar.** Kleine Bilder und Signaturen sind vorab abgewählt — sie gehören zur Darstellung der Nachricht, nicht zum Inhalt. Wer sie alle mit archiviert, hat nach einem Jahr hunderte Logos im Bestand. Die Schwelle liegt bei 8 KB und ist einstellbar. **Die ganze Nachricht als PDF.** Betreff als Überschrift, darunter ein grau hinterlegter Kopfblock mit Konto, Absender, Empfängern, Datum und Anlagen, dann der Nachrichtentext. Zitierte Abschnitte stehen grau und eingerückt — bei langen Verläufen sieht man so auf einen Blick, was neu ist. Eingebettet werden die vollständige Originalnachricht als `.eml` sowie alle Anhänge im Original. Es geht also nichts verloren, auch wenn die Darstellung nicht originalgetreu ist. Wer lieber die Rohfassung will, stellt das in den Einstellungen um oder wählt im ersten Fenster je Datei „Original ablegen". **Titel aus dem Betreff.** Präfixe wie `AW:` oder `Fwd:` werden entfernt — sie tragen im Archiv nichts bei und stören beim Sortieren. **Zwei Schritte, dann je Datei einer.** ``` Fenster 1 Welche Dateien sollen abgelegt werden? [x] Rechnung.pdf (•) Original ( ) PDF aufbereiten [x] Anschreiben.html (•) Original ( ) PDF aufbereiten Fenster 2 Je Datei: Ziel und Klassifizierung ┌─ Ziel ──────────┐ ┌─ Klassifizierung ─┐ │ ( ) Neu │ │ Titel [.......] │ │ (•) An bestehen-│ │ Erstellt[....] │ │ des anhängen│ │ Korresp.[.......] │ │ [suche] │ │ Typ [.......] │ │ Trefferliste │ │ Tags [suche] │ └─────────────────┘ └───────────────────┘ ``` Die Trennung hat einen praktischen Grund: **Ziel und Klassifizierung sind je Datei verschieden.** Ein einzelner Dialog für alle würde die Angaben der ersten Datei auf alle übertragen — bei einer Rechnung samt Lastschriftmandat wäre das falsch. **Klassifizierung des Ziels wird übernommen.** Wählt man ein bestehendes Dokument aus der Trefferliste, stehen Titel, Datum, Korrespondent, Typ und Tags schon im rechten Kasten und lassen sich anpassen. Eine neue Version gehört inhaltlich zum selben Vorgang; die Angaben neu einzutippen wäre Arbeit für nichts, und ein abweichender Korrespondent brächte die Version vom Original weg. **Ein Aufruf je Datei.** Bewusst nicht alle in einem Durchgang: Manifest V3 beendet die Ereignisseite, sobald sie untätig scheint, und ein Aufruf über zehn Anhänge lief regelmäßig in `Receiving end does not exist`. Nebeneffekt: Der Fortschritt ist sichtbar, und scheitert eine Datei, sind die übrigen trotzdem abgelegt. ### Warum kein originalgetreues Abbild HTML-Nachrichten werden als Text gesetzt. Eine WebExtension hat keine Darstellungsmaschine, die HTML nach PDF überträgt, und **Thunderbird bietet Erweiterungen keine Druckfunktion an** — die tabs-Schnittstelle kennt in Manifest V3 kein `saveAsPDF`, anders als in Firefox. Geprüft an der API-Dokumentation für Thunderbird 156. Für die Umwandlung von HTML nach Text übernimmt `messengerUtilities.convertToPlainText()` die Arbeit. Das ist dieselbe Funktion, die beim Antworten in Nur-Text greift, und liefert deutlich bessere Ergebnisse als eine selbstgebaute Entschlackung. ### Der PDF-Erzeuger Selbst geschrieben, in `lib/pdf.js`. Der Grund ist Vorsatz: Eine Erweiterung soll nichts nachladen, und eine mitgelieferte Bibliothek wie `pdf-lib` wäre rund ein Megabyte für einen Bruchteil ihrer Fähigkeiten. Gebraucht werden hier nur Text und eingebettete Dateien. Erzeugt wird PDF 1.7 mit den eingebauten Schriften Helvetica und Helvetica-Bold. Die sind in jedem Betrachter vorhanden und müssen nicht eingebettet werden. **Ausdrücklich kein PDF/A.** Dafür wären eingebettete Schriftschnitte, ein Farbprofil und XMP-Metadaten nötig — und vor allem eine Prüfung mit veraPDF. Ein Dokument als PDF/A zu bezeichnen, ohne es geprüft zu haben, wäre eine leere Behauptung. Einstellbar sind Papierformat (A4, A5, Letter, Legal), die vier Ränder in Millimetern und die Grundschriftgröße (Vorgabe 11 Punkt). ### Das Konto im Kopfblock Bei Nachrichten an Blindkopie-Empfänger geht aus An und Kopie nicht hervor, welches Konto sie empfangen hat. Im Archiv ist das später die entscheidende Angabe. Ermittelt wird sie auf zwei Wegen: über die accounts-Schnittstelle, die allerdings die Berechtigung `accountsRead` braucht, und ersatzweise über die Zustellkopfzeilen `Delivered-To`, `X-Original-To` und Verwandte. Letztere setzt der zustellende Server, und sie sind nicht immer vorhanden — Postfix und Dovecot setzen sie zuverlässig, Microsoft-Umgebungen oft nicht. Findet sich nichts, fehlt die Zeile im PDF ganz. --- ## Einfügen in Entwürfe **Als Anhang.** Das Original wird geladen (`?original=true`), nicht die Archivfassung — bei ZUGFeRD bleibt damit die eingebettete XML erhalten. Die Version ist wählbar, mit Datum und Format. **Als Freigabelink.** Zeitlich begrenzt, ohne Anmeldung abrufbar, auf Wunsch mit einem Tag am Dokument. Eingefügt werden Name (fett), Adresse und Gültigkeit: ``` Einladung Generalversammlung https://dms.example.org/share/ZYGwdw… Link gültig bis 11.09.2026 ``` Die Adresse steht sichtbar, nicht nur als Verweisziel — bei einem Link, der ohne Anmeldung Zugriff gewährt, sollte der Empfänger sehen, wohin er führt. **Der Tag ist abschaltbar.** Ein leeres Tag-Feld bedeutet: keine Kennzeichnung setzen. Wer es dauerhaft nicht will, leert das Feld in den Einstellungen. > **Einschränkung:** Die Compose-Schnittstelle kennt kein Einfügen an der > Schreibmarke. Der Nachrichtentext muss vollständig gelesen und > zurückgeschrieben werden. Der Link landet deshalb am Ende des Textes, > nicht an der Cursorposition. Bei HTML-Nachrichten wird er vor dem > schließenden `` eingesetzt. Wann was: Der Anhang für alles, was auch in fünf Jahren noch lesbar sein muss, wenn der Link längst abgelaufen ist. Der Link für große Dateien und für Empfänger, die ohnehin Zugriff auf das Archiv haben sollen. --- ## Einrichten 1. Add-ons-Verwaltung → **Paperless-Connector** → Einstellungen 2. Basisadresse eintragen, etwa `https://dms.example.org` — ohne abschließenden Schrägstrich 3. API-Token eintragen, erzeugt im **Benutzerprofil** von Paperless 4. Speichern — dabei wird das Zugriffsrecht für genau diese Adresse erbeten und die Verbindung sofort geprüft Das Zugriffsrecht steht bewusst **nicht** pauschal im Manifest. Eine Erweiterung, die von Haus aus jede Adresse erreichen darf, ist eine unnötig große Angriffsfläche. --- ## Bauen und installieren ```bash ./build.sh ``` Zum Ausprobieren ohne Installation: ``` Extras → Entwicklerwerkzeuge → Debuggen von Add-ons → Temporäres Add-on laden → manifest.json wählen ``` Das ist beim Entwickeln der bessere Weg: Änderungen sind nach einem Klick auf „Neu laden" wirksam, und die Konsole zeigt Fehler im Klartext. Dauerhaft: ``` Add-ons-Verwaltung → Zahnrad → Add-on aus Datei installieren → paperless-connector-.xpi ``` --- ## Bekannte Einschränkung: Zugriff über IP-Adresse Der Zugang über `https://` und einen Rechnernamen funktioniert. Der direkte Weg über eine LAN-Adresse wie `http://192.168.1.5:8000` schlägt dagegen mit `NetworkError` fehl, auch wenn das Zugriffsrecht erteilt wurde. **Die Ursache ist nicht abschließend geklärt.** Eine Vermutung: Neuere Gecko-Versionen schränken Anfragen aus erweiterten Kontexten in private Adressbereiche ein. Falls das zutrifft, lässt es sich von der Erweiterung aus nicht umgehen. Praktisch ist das verschmerzbar — der Weg über den Reverse Proxy ist ohnehin der bessere, weil er verschlüsselt ist. Wer es untersuchen möchte, findet in `about:networking` und in der Netzwerkanalyse der Add-on-Debugansicht Ansatzpunkte. ## Manifest-Version Gebaut nach **Manifest V3** mit den Thunderbird-Besonderheiten: - `background.scripts` als Ereignisseite, **kein** Service Worker — Thunderbird unterstützt keine - `compose_action` und `message_display_action` neben `action` - `optional_host_permissions` statt fester Adressen Sollte die eingesetzte Version noch MV2 verlangen, sind vier Änderungen nötig: `manifest_version` auf 2, `optional_host_permissions` nach `optional_permissions`, `host_permissions` entfällt, und `background.type: module` bleibt wie es ist. --- ## Sprachen Deutsch, Englisch und Französisch über die eingebaute Verwaltung (`_locales/`). Die Sprache richtet sich nach der Oberfläche von Thunderbird. Eine weitere Sprache ergänzt man, indem man ein Verzeichnis mit `messages.json` anlegt — die Schlüssel stehen in `_locales/de/messages.json`. --- ## Sicherheit Ausführlich in [SICHERHEIT.md](SICHERHEIT.md). Die Lage ist entspannter als bei der LibreOffice-Fassung, weil hier **keine fremden Dokumente geöffnet** werden — der heikelste Punkt entfällt. Die wichtigsten Maßnahmen: - Zugriffsrecht nur für die eingetragene Adresse, zur Laufzeit erbeten - Nur die sechs tatsächlich benötigten Berechtigungen — `compose.send` war im ersten Entwurf enthalten, ohne je benutzt zu werden, und ist entfernt - Nur `http` und `https` als Basisadresse - Dateinamen aus Anhängen werden bereinigt — sie stammen vom Absender - Anhangdaten werden erst beim Hochladen geladen, nicht beim Öffnen des Dialogs - Freigabelinks und Dokumentnamen werden beim Einfügen HTML-maskiert - Aufträge zwischen Dialog und Ereignisseite werden nach 30 Minuten verworfen --- ## Aufbau ``` manifest.json background.js Menüs, Schaltflächen, Vermittlung lib/ client.js REST-Client (fetch, FormData) settings.js Einstellungen, Dateinamen, Hilfsmittel pdf.js PDF-Erzeuger ohne Fremdbibliothek mailpdf.js Aufbereitung einer Nachricht ui/ store.html/.js Schritt 1: welche Dateien schritt.html/.js Schritt 2: Ziel und Klassifizierung je Datei insert.html/.js Einfügen als Anhang oder Link options.html/.js Verbindung einrichten gemeinsam.js/.css geteilte Hilfsmittel _locales/{de,en,fr}/ icons/ ``` Die Dialoge sprechen **nicht** selbst mit Paperless, sondern lassen die Ereignisseite arbeiten. So liegen Token und Verbindungslogik an einer Stelle, und die Dialoge bleiben reine Oberfläche. Damit die Ereignisseite währenddessen nicht beendet wird, hält jeder Dialog eine offene Verbindung dorthin und sendet alle 15 Sekunden ein Lebenszeichen. Manifest V3 räumt sie sonst mitten im Vorgang ab — Wartezeiten zählen dort nicht als Ereignis. --- ### Eine Stolperstelle bei der Gestaltung Die Dialoge sind auf feste Fensterhöhe mit blätternden Listen ausgelegt. Drei Dinge mussten dafür stimmen, und jedes einzelne führte für sich zum Überlauf: | | | |---|---| | `height: 100vh` am Body | mit `min-height` wächst die Seite über das Fenster hinaus | | kein `fieldset` als Flex-Behälter | dessen innerer anonymer Kasten löst `height: 100%` nicht auf — deshalb `
` | | kein `flex-wrap` bei den Spalten | mit Umbruch bemisst sich die Zeilenhöhe nach dem Inhalt, nicht nach dem Behälter | ## Vor einer Veröffentlichung | Datei | Was | |---|---| | `manifest.json` | `browser_specific_settings.gecko.id`, `author`, `homepage_url` | | `LICENSE` | Rechteinhaber | Die Kennung sollte einer Domain folgen, die dir gehört. Aktuell steht dort `paperless@example.org`. ### Anforderungen der Prüfung auf addons.thunderbird.net Aus der Ablehnung von 1.16.0 übernommen, seit 1.16.1 umgesetzt: - **Kein async-Listener an `runtime.onMessage`.** Ein async-Listener gibt immer ein Promise zurück und beansprucht damit jede Nachricht. Der Listener prüft deshalb synchron Absender und Befehl und gibt nur für bekannte Befehle das Promise zurück, sonst `false`. Neue Befehle kommen in die Tabelle `BEFEHLE` in `background.js`. - **Keine ungenutzten Dateien im Paket.** `build.sh` packt nur die in `DATEIEN` aufgeführten Dateien und weist auf alles andere im Quellbaum hin. `make_icons.py` erzeugt weiterhin alle Symbole samt SVG — im Paket landen nur die sieben, die Manifest und Code verwenden. - **Beschreibung mit Bedienungsanleitung und Bildschirmfotos** auf der Add-on-Seite, siehe `ATN-BESCHREIBUNG.md`. - Vor dem Hochladen den Linter laufen lassen: ```bash git clone https://github.com/thunderbird/webext-linter cd webext-linter && npm install node verify.js /pfad/zu/paperless-connector-.xpi ``` --- ## Lizenz MIT, siehe [LICENSE](LICENSE). ## Haftung Die Erweiterung schreibt in ein Dokumentenarchiv. Sie wurde gegen eine Paperless-Instanz entwickelt, aber nicht formal getestet. Die MIT-Lizenz schließt jede Gewährleistung aus. Vor dem ersten produktiven Einsatz: eine Testnachricht ablegen und in Paperless nachsehen, ob Titel, Datum und Anhänge stimmen.