Files
thunderbird-paperless-conne…/README.md

14 KiB

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 </body> 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

./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-<version>.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. 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 <section class="kasten">
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:
    git clone https://github.com/thunderbird/webext-linter
    cd webext-linter && npm install
    node verify.js /pfad/zu/paperless-connector-<version>.xpi
    

Lizenz

MIT, siehe 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.