Thunderbird Add-on for Integrating with Paperless-NGX
I was missing an integration between Thunderbird and Paperless-NGX. While Paperless-NGX does offer a feature for linking email addresses, it wasn’t interactive enough for me, since I obviously don’t want to import massive amounts of emails. I want to do this selectively. I also wanted the ability to retrieve attachments directly from the archive into an email—either as a file attachment or as a share link. This makes day-to-day work much easier. To that end, with the help of AI (Anthropic Claude), I created an add-on that provides the desired functionality.
Situation
Invoices arrive via email. Until now, this meant: save the attachment, move it to the “Consume” folder, and reclassify it in Paperless—three steps for a single task. After developing the LibreOffice extension for Paperless, creating a Thunderbird extension was a natural next step—the same insights regarding the interface, but entirely original code. Thunderbird uses WebExtensions, while LibreOffice uses UNO; the only thing transferable was the knowledge.
What it can do
Right-click on an attachment → Save to Paperless. If there are multiple attachments, a selection menu appears. Small images and signatures are deselected by default—they are part of the message’s presentation, not its content. If you archive everything, you’ll end up with hundreds of logos in your collection after a year.
Right-click in the message list → Save email.
The complete message as an .eml file; attachments remain included. The title is derived from the subject line, without prefixes like AW: or Fwd:—these add no value to the archive and interfere with sorting.
In the compose window → Insert from Paperless. As an attachment or as a shareable link.
Two Options for Saving
As in the LibreOffice version: either a new document or a new version of an existing one. The second case is one I’ve encountered from time to time, and for which none of the programs I use has—or had—a direct solution. The invoice arrives as a PDF, and later an editable version is created—or a corrected version of the same invoice arrives. The two belong together. The classification dialog appears immediately after saving. Otherwise, a new document only has the title, and experience shows that people rarely add it later.
Attachment or Link
When inserting into a draft, both options are available.
As an attachment for anything that still needs to be readable in five years, when the link has long since expired. The original is uploaded, not the archived version.
As a share link for large files and for recipients who should have access to the archive anyway. The name, address, and validity period are included:
**Invitation to Sample Event**
https://dms.example.org/share/ZYGwdw…
Link valid until 09/11/2026
The address is displayed visibly, not just as the link destination. For a link that grants access without requiring a login, the recipient should be able to see where it leads.
If desired, the document can be tagged with a label; the default is Sharelink. This makes it possible to verify later what was shared externally. An empty field means: no tag.
Access Permission for a Single Address
The extension does not require a blanket access permission in the manifest. Instead of host_permissions, it uses optional_host_permissions, and when the settings are saved, permission is requested for exactly the specified
address.
The difference is significant: An extension with <all_urls> can access any address and read any content. Here, it’s exactly one origin, and you can see which one when granting permission.
A total of five permissions—storage, menus, messagesRead, compose, notifications. The first draft also included compose.send, which was never actually used. A permission that isn’t needed doesn’t belong in the manifest.
Known Limitations
The share link appears at the end of the text, not at the cursor position. The Compose interface doesn’t support inserting text at the cursor position—the message text must be read in its entirety and rewritten.
*Access via a LAN address does not work. * Everything works via a computer name with https; a direct address like http://192.168.1.5:8000 fails with a NetworkError, even if access permission has been granted. The cause has not been conclusively determined; presumably, newer Gecko versions restrict requests from extended contexts in
private address spaces.
This is practically tolerable—using a reverse proxy is the better option anyway, since it’s encrypted.
Installation
Paperless-ngx 3.0 or newer is required.
To test it out:
Tools → Developer Tools → Debug Add-ons
→ Load Temporary Add-on → Select manifest.json
Permanently via the Add-ons Manager, “Install Add-on from File.” Then enter the base address and an API token in the settings. When saving, access permission is requested and the connection is verified immediately. If internal access runs on a different port, there is an additional field for the address used to generate sharing links — otherwise, they would include the internal port and would be useless from outside the network. The interface is available in German, English, and French.
Source Code
All under the MIT License:
Git Repository: Thunderbird Connector Add-on
Built using Manifest V3 with Thunderbird-specific features: event page instead of a service worker, compose_action and message_display_action in addition to action.
Tested on a single instance; not formally tested. Feedback is welcome.