Changelog#
All notable changes to Piler Enterprise Edition, starting from 2.1.0.
piler-smtp and the Scanner add-on are versioned and released independently, see the piler-smtp changelog and the Scanner changelog.
To follow the releases in a feed reader, subscribe to releases.xml (every release of piler-ee, piler-smtp and the Scanner) or security.xml (only the releases with security fixes, which have a Security section).
2.3.2 - 2026-11-11#
Upgrade step: piler needs Manticore 25.0.0 or newer, 29.9.0 is recommended: check the version and upgrade Manticore before piler, see Manticore 25.0.0 or newer. piler-ui doesn't start with an older Manticore.
Security#
- Some Manticore queries didn't escape their values properly: values taken from the headers of archived emails (with semantic search), from the Teams and Slack APIs, and from the addresses of the user. A crafted email could change the semantic search data of the scanner. Every Manticore query of the search, the audit log, the tags and notes, the similar emails, the semantic search and the retention of Teams and Slack messages passes its values separately from the query now, so a value can't change it. This needs Manticore 25.0.0 or newer.
- A failed search showed the error of Manticore to the user, with internal details of the query. It's in the log now, the user sees a generic error.
- A user could tag emails they have no access to. The emails stayed hidden from them, the tags are only written for their own emails now.
Changed#
- The REST API answers a request for an email the user may not see, e.g. of another tenant, with 404, like an email that doesn't exist, on a single node and through a multinode master. It was 500, or for the headers 200 with an empty body. A user whose role doesn't allow it gets 403 for viewing, downloading, the attachments, the headers, the bulk download and the restore of emails, not 500, 401 or 200.
add-participants-column.shis renamed toadd-missing-columns.sh: it adds theparticipantscolumn of 2.3.0 and thedeleted_atcolumn of the Slack table, see Slack tables: deleted_at. The old name is gone, run the new one where you ran the old one.- The Teams and Slack tables of a new tenant are columnar as intended (only one column was), which keeps them smaller. Existing tables stay as they are.
Fixed#
- A search with
%or;in it failed. - A tag or note search found the emails of the rest of the search when no email had that tag or note. The advanced search ignored the tag and note fields, and with both, only the note was used.
- The API documentation (
/api/docs) had wrong paths: the thread of an email isGET /api/v1/message/{id}/thread, the bulk downloadPOST /api/v1/message/download, the bulk removalPOST /api/v1/message/bulkremove, and an eDiscovery export/api/v1/ediscovery/exports/{job_id}(plural, also forcancelanddownload). The categories, the similar emails and the related attachments of an email are documented now. add-missing-columns.shskips a distributed table (of a multinode master) instead of failing on it.- The Slack purge failed for every message of a Slack table created by the
Manticore migrations, e.g. at an upgrade from 1.x: the table had no
deleted_atcolumn.add-missing-columns.shadds it. - The attachment statistics stopped updating when an attachment name had a
long text after its last dot, e.g.
Re. the minutes of the meeting. Such a name, like one without a dot, counts as without an extension now, andPDFandpdfare one extension.
2.3.1 - 2026-11-07#
Upgrade step: run the migrations, see Run the migrations. 033 resets the date formats left over from 1.8.x, see Date formats.
Security#
- piler-ui warns at startup if
JWT_SECRETis the example value this documentation printed for native multi-node installs until 1 November - Anyone can read that value and sign access tokens with it: replace it, see JWT secret.
- An admin could run SQL through the simulation of a retention or exclusion rule. The simulation checks its input now.
- The pending conversations showed every user the unanswered threads of the whole tenant: the sender and the subject of other users' emails. A user gets those of their own addresses now, an auditor those of the tenant, and removed emails are left out.
- The PDF export sent the HTML of the browser to Gotenberg unchecked: any user could make Gotenberg fetch an internal URL and get the answer as a PDF. piler-ui removes everything that loads a URL or navigates, and refuses the export without the PDF permission. Remote images of an email are no longer in its PDF. See GOTENBERG_ADDR to restrict Gotenberg's network too.
- A crafted email could replace the content of an attachment that other archived emails share: those emails were shown and downloaded with the crafted content, and their verification failed. piler checks an attachment against the email before storing it now.
- An attachment was stored once, under the digest of its decoded content, with the encoding of the first email that had it. A later email with the same attachment in another encoding (e.g. base64 instead of 8bit), or with other line lengths, was restored with the bytes of the first one: the attachment was broken, or the verification of the email failed. With S3 storage the last copy was kept, which could break an email archived before. Attachments are stored under the digest of their stored bytes now, so they're shared by the emails with the same attachment in the same encoding. The attachments stored before 2.3.1 keep their names, and are not checked.
- MIME bombs: some deeply nested or very fragmented emails made the piler daemon use excessive memory and CPU, so a single email could get it killed. An email over MIME_MAX_DEPTH (32 levels) or MIME_MAX_PARTS (1000 parts) goes to the error directory now, and the first 100 attachments of an email are stored apart, the others stay in the stored email. An email within the limits takes about the memory of 6-8 copies of itself.
Added#
- The default retention of a single-tenant install on the retention page
(Compliance > Retention Policy): how long the emails no retention rule
matches are kept. The admin changes it there, from
MIN_RETENTION_DAYSto 36500 days, and the change is in the audit log. It applies to the emails archived from then on. Before, it could be changed with SQL only (retention_daysof thepilerrow ofpiler.tenant_settings). On a multi-node master it's shown but not editable: each worker uses the value of its own node. The other tenants of a multi-tenant install see their default, it's set in the master's tenant editor as before.
Changed#
contrib/installer/upgrade.shisupgrade-from-1.8.4.sh: it upgrades a 1.8.4 install once, it's not the upgrade of a 2.x install, see Upgrading.
Fixed#
- After an upgrade from 1.8.x the dates were garbled for users with a 1.8.x
date format in their settings (e.g.
d.m.Y). These formats are reset toYYYY/MM/DD, and the settings accept only a format with a year, a month and a day. - A date format longer than 12 characters, e.g.
YYYY-MM-DD HH:mm:ss(the example of the settings form), couldn't be saved. An empty one is the default now, the settings were refused. - piler-ui logged
permission query: sql: no rows in result setfor every user without a custom profile. - The simulation of a rule with a domain, sender, recipient or body
criterion failed with "Simulation Failed". It matches the criteria like
the archiving does now: the domain is the domain of the
Fromaddress (it was the sender or any recipient). The body isn't counted, the result says so. See Retention and exclusion rules.
2.3.0 - 2026-11-04#
2.2.5 was not released, its changes are in 2.3.0. The 2.2.5 git tag and
the 2.2.5 Docker images of 25 October 2026 are an earlier build: don't
use them.
Upgrade step: the index needs the new participants column, otherwise
piler, piler-ui, pilerimport and reindex refuse to start. Docker
deployments get it automatically; on a native install run the script once
after the package upgrade, see
Add the participants column.
With S3 storage, also see
S3 storage settings: s3uploader reads
/var/piler/.env instead of /var/piler/.env-s3. Run the migrations too,
the Docker images don't: piler-upgrade.sh runs those of the new version
(piler-examples of 2026-11-02 or later, git pull it first), on a native
install run them by hand. Among others, 029 removes secrets from the stored
sessions and 030 adds the table of the health page's backups, see
Run the migrations.
Retention settings: the options table no longer has retention
settings. ENABLE_TENANT_RETENTION_POLICIES and MIN_RETENTION_DAYS are in
the .env of piler-ui; migration 032 lists the values you had changed. A
retention rule is active when saved, and purge doesn't purge a tenant
without its row in piler.tenant_settings on that node. See
Retention settings.
Upgrade notes: the quota user count is now the active users (email in
the last 90 days), so it drops for tenants with former employees: check
the user quotas. On a multi-node deployment upgrade the workers before the
master; with several masters set LEADER_ELECTION_ENABLED=true. A
/metrics scrape through a reverse proxy needs the proxy in
TRUSTED_PROXIES. The Docker examples keep the error directory in a
piler_error volume, create it before the upgrade. Set JWT_SECRET
(the same on every node) if it isn't set, or replace a value copied from
an example. See
JWT secret,
Quotas,
Metrics through a reverse proxy and
Docker: the error directory volume.
Security#
- Fixed the validation of access tokens when
JWT_SECRETisn't set. piler-ui uses a random secret now whenJWT_SECRETisn't set, and logs a warning, also about a secret shorter than 32 characters or one starting withchangeme. The installers and the Docker examples set a randomJWT_SECRET; set one on an existing installation, see JWT secret. - The removal of an export took a file name with
../, so a user could delete another user's export file if they knew its random name. The download and the removal of an export take a plain file name only. - The quota update and the MSP admin's branding refuse an invalid tenant id, and the audit log refuses a table name that isn't a tenant's audit table: a crafted tenant id could add entries to another tenant's audit log. The string values of the audit details are quoted, so a value can't look like another key, see Audit log details.
- A regular user's search could count, and list in the top results, the
messages of an address that looks the same in the search index, e.g.
john.sales@example.comandjohn@sales.example.com. Their content was never shown. The index now has aparticipantsattribute with the sender and the recipients of each message, and a user's search only takes the messages they are on.reindex -participantsfills it for the messages archived before the upgrade; until then those keep the earlier filter. purge(installed setuid) ignores the caller's environment andPILER_ENV_FILEwhen run by another user, like pilerimport, and reads its settings from/var/piler/.envonly.- pilerimport (installed setuid) creates the temporary files of the
-mboxand-pstimports with random names that no other user can take over. - The admin of any tenant could change the settings of other tenants or
add a tenant. Managing tenants is only for the admin of the master
tenant (
piler) now. /metricsand/healthztook the client address from the request's ownX-Forwarded-FororX-Real-IPheader, so anyone reaching piler-ui, through the reverse proxy too, could read them. The header counts only from a proxy inTRUSTED_PROXIESnow (onlyPROXY_HEADER), and a forwarded request from any other address is refused.- The query and the filters of a category rule went into the Manticore statement unescaped: a quote broke the rule, and an admin could inject a statement. The id of a category update or removal must be a number.
- The messages of the admin pages are shown as text, not HTML.
- The quota API used the tenant id of the master admin's requests
(
/api/v1/admin/quota?tenant_id=,/quota/monthly/<tenant id>) unchecked in its database queries and in the requests to the workers. An invalid tenant id is refused now. - A token login (
POST /auth/login) stored the user's secrets with the refresh session in therefresh_sessiontable: the TOTP secret and backup codes, the IMAP password, the access token and the CSRF token. The session has only what a refresh needs now. Migration 029 and piler-ui (daily, in every tenant database) remove the secrets from the sessions stored earlier, and delete the expired sessions, which were kept until the user's next login. See Refresh sessions.
Added#
- Native backups for the deb/rpm installs:
/usr/libexec/piler/piler-backup.shbacks up the databases, the search index, the stored emails and the settings into a restic repository, and restores them on a new host. Not scheduled by default, see Backup. - Backup metrics on every node:
piler_backup_last_timestamp_seconds,piler_backup_failedandpiler_backup_verification_failed, from thebackup_statustable, with alert rules for a failed, missing or too old backup. - E-invoices are found in the search:
attachment:einvoice, or the "E-invoice" checkbox of the advanced search. Piler recognizes them when it archives an email: XRechnung and other UBL or CII invoices (XML), ZUGFeRD and Factur-X (PDF with an embedded XML invoice). The emails archived before are found after a reindex, see E-invoices in the search and GoBD. - Export of the e-invoices of the search results, for the data handover in
a tax audit (GoBD Z3): the receipt button next to the export button of
the search page, or
POST /api/v1/export?format=einvoice. The ZIP has each e-invoice as received, the email it came with,index.csv(one row per e-invoice, with the SHA-256 of the file and of the email) andREADME.txt, which says whether the export is complete. See GoBD. - Configuration report for the compliance documentation, in the admin UI
under Compliance > Configuration report, or
GET /api/v1/admin/config-report: the settings, retention and exclusion rules, roles and users, and active legal holds of a tenant, as Markdown or JSON, in English or German, and printable. It has no passwords or keys. What Piler can't read stays a[placeholder]. On a multi-tenant system the admin of a tenant doesn't see the platform's servers and directory groups. See GoBD. - The audit log records every change of the configuration made through the web UI or the API, with the new values (never a password): domains, groups, LDAP, profiles, import jobs, export deletions, legal hold status, DLP and compliance rules, the DLP alerts, branding, quotas, Teams and Slack retention settings and legal holds, in addition to the users, tenants, retention rules, categories, legal holds and API tokens logged before. The GoBD require it (Rz. 111), see GoBD.
recreate-distributed-tables.sh(multinode master): recreates the distributed tables of the tenants after adding a worker, instead of the manual procedure. It never drops a table that isn't distributed.pilerimport -pst: imports PST files (Outlook, Exchange and Microsoft 365 exports, Enterprise Vault exports) directly, without a conversion step. All mail is archived, sent mail with the SMTP addresses of its sender and recipients (Bcc included), S/MIME emails as they were sent; appointments, contacts and other non-mail items are counted and skipped.-x500-mapreplaces the Exchange addresses of internal mail with SMTP addresses from Active Directory,-skip-deletedskips Deleted Items. See Import PST files.purge -orphans: reports the stored.mfiles of a tenant that no message refers to, in the local store or the S3 bucket, e.g. the files duplicates left behind before 2.2.4. With-removeit removes them, only when run as the piler user or root. Files younger than-orphan-min-age(default 24h, at least 4h) are left alone, and nothing is removed unless some of the stored files are found in the database, so a wrong database or server id can't empty the store.- Tenant metrics on
/metrics(single node and the workers): per tenant the received and purged messages, the original and stored size of the archive, and the documents, disk and RAM size of its index. For billing, capacity planning, and an alert when a tenant's email stops arriving. - Archiving pipeline metrics: the piler daemon and s3uploader report
the archived, duplicate, discarded and failed emails, the queue and the
error directory, and the S3 uploads and their backlog, served by
piler-ui's
/metrics. - Quota metrics on the master's
/metrics(a single node's): the usage and the quotas of every tenant, and their age. When a worker doesn't answer, the last usage collected from every worker is kept, so an alert doesn't resolve on usage that is too low. - Alert rules with tests, a Prometheus and a Grafana Cloud setup and a dashboard: see Hosting: monitoring and the monitoring example.
- Quota banner: the admin of the master tenant sees the tenants at or above a quota warning threshold on the admin pages, with a link to the quota dashboard.
- Tenant quota banner: per tenant, the admin of the master tenant can show the tenant's own admin a banner when the tenant is at or above the warning threshold of a quota ("Show the quota banner to the tenant's admin" in the quota editor, off by default).
- Billing API: read-only API tokens for billing scripts, created and
revoked by the admin of the master tenant (Admin > Tenant > API
tokens), optionally limited to some addresses and with an expiry.
They're accepted on
/api/v1/billing/quotaand/api/v1/billing/quota/monthly/<tenant id>only, so a script no longer needs the admin's password. See Hosting: usage reports for billing. archived_user_countin the quota API, the dashboard, the CSV export and the warning email: every address that ever received archived email, former employees included.DATAOFFICER_REVIEW_ONLY(defaultfalse, can be set per tenant): the data officer only reviews the removal requests, every other page and API is refused, see Config options.DISABLED_FEATURES_USERandDISABLED_FEATURES_AUDITOR(empty by default, can be set per tenant): turn off features for the regular users or the auditors, e.g. restore, tag, export or Smart Insights. They are hidden on the search page and their APIs are refused, see Config options.
Changed#
- Retention rules are active when saved. The grace period and the
confirmation of a new rule are gone, with the confirm API
(
/api/v1/admin/retention/rule/:id/confirm): the piler daemon applied a pending rule anyway. Migration 032 activates the pending rules. Instead, the rule dialog asks you to type the days of a rule shorter than the default retention of the tenant, when it's created or its days change. - Retention settings in the
.env:ENABLE_TENANT_RETENTION_POLICIESandMIN_RETENTION_DAYSof piler-ui (both can be set per tenant in itssettings_json), instead of rows of theoptionstable, see Config options.ENABLE_TENANT_RETENTION_POLICIES=falsenow really locks the rules: the admin of a tenant can view and simulate them, not create, change or delete them. Before, the setting didn't stop the admin of a tenant from changing the rules. MIN_RETENTION_DAYS(30 days by default) is also the minimum of an update of a rule, and of the default retention of a tenant in the tenant editor.purgereadsenable_purgeof the tenant frompiler.tenant_settingsof the node it runs on, and doesn't purge a tenant without a row there or when it can't read it (it purged then).purge.shgoes on with the other tenants after one fails. On a multi-node deployment, setenable_purgeon every worker, see Change a tenant's settings.- The default retention of a new installation is 10 years (3653 days):
the
pilertenant was created with 7300 days, and the daemon'sDEFAULT_RETENTION_DAYSfallback was 2557. An existing installation keeps its retention. - Search: the Thread menu of an email is a View Thread button. In the thread view it reads Exit Thread and goes back to the search results, where you left them, with the email you opened the thread from. Before, only a search with the empty search field left the thread view. Summarize Thread (AI features) is a button of its own.
- s3uploader refuses to start with
S3_PREFIXset: no reader looks for objects under that prefix. The files wait in the staging directory until the setting is removed. - The piler-s3 service (s3uploader) reads
/var/piler/.env, like piler and piler-ui, instead of/var/piler/.env-s3: one copy of the S3 settings, so s3uploader and piler-ui can't use different buckets. Move the settings only s3uploader uses to.env, see S3 storage settings. S3_USE_SUBDIRSis removed. The objects were always stored and read in subdirectories; onlypurgehonoured the setting, and withfalseit left the objects of the purged messages in the bucket.- Scanner 1.3.1, see the Scanner changelog.
- retrieverd (
RETRIEVER_METHOD=socket) is not recommended and will be phased out; piler-ui logs a warning when it's used. The store is read-only for piler-ui anyway. - Upgraded frontend dependencies (Vite, follow-redirects).
- pilerimport lists the reason of each failure in its summary.
download-emails-o365.py(download from Microsoft 365): throttling and network errors are retried, an interrupted download continues where it stopped (the emails already downloaded are skipped),--beforedownloads only the emails received before a day (e.g. the start of journaling),--dirsets the download directory. A summary at the end, and an exit code: 0 complete, 1 run it again, 2 complete except emails Microsoft 365 can't export. Mailboxes the app may not read are skipped. Runs on Python 3.9+; installrequestsandmsalin a virtual environment, see Import from Microsoft 365.- Built with Go 1.27.3; golang.org/x/crypto, net, sync, sys, term, text and tools updated.
- The quota
user_countis the active users: the addresses of the tenant's domains that received email in the last 90 days, the window of the billing report. Before, it was every address that ever received email. pilerimport counts the date of each email, not the time of the import, so importing old mail doesn't make former employees active. See billing. - The quota dashboard and
/api/v1/admin/quota/allshow the usage collected everyQUOTA_SNAPSHOT_INTERVAL(default 15 minutes) by the master, the leader with several masters, with the time it was collected. On a multi-node deployment the dashboard took 20-30 seconds or more with hundreds of tenants: it asked every worker for every tenant on each load. The tenants and their quotas are read at once, a new tenant is "pending" until the next collection. - The quota warning emails are sent by the master only, from the collected usage. Each quota has its own level, the subject names the worst one, and the email says when a worker didn't answer and the usage may be higher.
- A new tenant id must be 3-64 lower-case letters, digits or
_(without_with S3 storage, as it's part of the bucket names). An id with upper-case letters was accepted, but the tenant was never provisioned. - The usage report to the Piler billing server has the full count of
active users of each tenant (single-tenant: the installation), the same
count as the quota
user_count. Before, it had only the top 20 domains with 10 or more active users. Every tenant sends a report, also with no active users. A tenant without domains (besideslocal) is not counted: its report has no user count, and piler-ui logs a warning. - One billing server: the default
BILLING_SERVER_URLishttps://billing.mailpiler.com(it was billing1 and billing2). The old names reach the same server, see upgrade. - The usage report to the Piler billing server includes the license file, so the billing server can verify where the report comes from. It holds no personal data: the customer name, server id, features and expiry.
- s3uploader doesn't check the bucket before the upload, so the S3 policy
doesn't need
s3:ListBucket. Throttling and S3 server errors are retried (they went to the error directory); the log says what to fix for a missing bucket, a denied access or invalid keys.
Fixed#
- A retention rule created in the admin UI was disabled until switched on.
purge -dry-runsaid 0 files in its summary after listing them.- An email with a sender domain, a recipient or an attachment type longer than its database column, or a long non-ASCII sender cut in the middle of a character, couldn't be archived and went to the error directory. These values are cut to the column size now, at a character boundary.
- With S3 storage, an email downloaded or viewed before the s3uploader
uploaded its attachments had the attachment missing, with a 200 response.
Now the download is a 503 with
Retry-After, like an email whose own object isn't uploaded yet. - piler, pilerimport: a tenant without a row in
piler.tenant_settingson the node is archived with the default retention (DEFAULT_RETENTION_DAYS), now with a warning in the log. - A data officer or an admin logging in through EAM (a signed token posted
to
/session) landed on the search page instead of their own pages. - The attachment links in the message preview of the audit and the data officer pages pointed to a missing URL.
- Running the migrations again added the rows of the
optionstable again, and a new installation lacked a table, an index and a risk factor that the migrations add to an upgraded one. Migration 031 removes the duplicates, and the migrations are now checked to be safe to rerun. - A malformed refresh token (
POST /auth/refresh) could crash the request instead of being refused. download-emails-o365.pylost emails when Microsoft 365 throttled the download: a throttled email was skipped, and a throttled page of a folder's list ended the folder, without an error at the end. A mailbox folder named e.g.../xwas written outside the download directory.- Adding or saving a tenant with an invalid
settings_jsonfailed with "Failed to update tenant"; now the message says it must be a JSON object. Atenant/updateAPI call withoutsettings_jsonkeeps the stored settings (send{}to clear them). - The installer (
install.sh) of a multinode master created the distributed tables of the default tenant under a wrong name. - Saving a tenant in the Tenants editor undid the changes made since the editor was opened on another page (another tab): the tenant quota banner, the Teams and Slack retention, and the DLP alerts set by the tenant's own admin. Now these are kept; if the editor changed the same settings, nothing is saved and the editor asks to redo the changes.
- An email whose Date header was before 1970 or after 2106 couldn't be archived. Such a date is now handled like an unparseable one: the time of the Received header is used, else the time of archiving.
- With
KEEP_PURGED_METADATA, importing a purged message again replaced its recipients with those of the new copy, so the earlier ones were lost, including the Bcc recipients from envelope files. The recipients are now kept and the new ones added, if the copy is the same byte for byte as the purged message. - s3uploader ignored
S3_BUCKET_PREFIX, while piler-ui and the other tools read fromS3_BUCKET_PREFIX+ tenant: with a prefix set, the uploaded emails couldn't be read or purged. WithS3_BUCKET_PREFIXorS3_PREFIXset, check the settings before starting piler-s3 after the upgrade, see S3 storage settings. purgeignoredPILER_STORE_DIRandPILER_ASTORE_DIR: with another store directory it marked messages deleted but never removed their files.- piler-ui couldn't read any message when
PILER_STORE_DIRhad no trailing slash (e.g./srv/piler/store), and its disk metric always read/var/piler/store. - The quota storage usage didn't subtract the purged email, so it only grew for tenants with a retention period.
- Multi-node: the quota user count added up the workers' counts, counting a mailbox that received email on several workers more than once. The master counts each address once now.
- Multi-node: a worker that failed to answer the quota request counted as zero usage, and a tenant with no worker answering was listed as "ok". The usage now says how many workers didn't answer, and such a tenant is "unknown".
- The quota warning email said EXCEEDED on the storage line when only the user quota was exceeded, and computed "exceeded by" from the storage (a negative number of GB).
- Docker multi-node workers: the hourly sync of the master's tables failed on every worker (the master's Manticore host was missing for cron), and without it the retention, exclusion and legal hold rules weren't synced either. A quote in a category stopped the sync for the remaining tenants.
- A category update with a query Manticore couldn't parse removed the category's rule, while the response said it worked. The old rule stays now, and the response is an error.
- The admin pages didn't show the server's error message when the title of the error had a translation.
- The admin health page never showed a backup: the table it reads didn't
exist, and its query failed on every page load. Migration 030 creates the
backup_statustable in thepilerdatabase, and piler-backup.sh of the Docker examples writes it after every backup. With no backup recorded the page says unknown, not healthy. See Run the migrations.
2.2.4 - 2026-10-17#
2.2.3 was not released; its changes are included here.
Added#
- Outlook add-in (off by default,
OUTLOOK_ADDIN_ENABLED): search the archive, preview a message, read the full message and restore it to the mailbox from Outlook on the web, new Outlook for Windows, classic Outlook and Outlook for Mac. Served by piler-ui itself; the add-in asks Outlook for the lowest permission and reads nothing of the mailbox. See Outlook add-in. - Bcc recipients from the SMTP envelope: piler-smtp can write the
envelope of each email to an envelope file (
enable_envelope_sidecar), and the envelope recipients are archived like theToandCcones, so a Bcc recipient finds the email. A copy of an email delivered in another SMTP transaction adds its recipients to the archived email (ENVELOPE_MERGE_WINDOW_HOURS). See Envelope recipients. - Envelope sender: recorded for each email; auditors see it in the
message view, with a warning when it differs from the
Fromheader, and can search by it (mailfrom:, "Envelope sender" field). EXTRA_RECIPIENTS_HEADER: archive the addresses of a header added by the MTA or the journaling setup (e.g.X-Envelope-To) as recipients.ARCHIVE_MESSAGE_VARIANTS: a message with the Message-ID of an archived one but different content is archived as a message of its own instead of being dropped as a duplicate. Auditors see the other messages with the same Message-ID as related copies, also across the nodes of a multi-node setup.- pilerimport and Bcc recipients of migrated mail: an envelope file
next to an EML (
message.eml.env) adds its recipients;-add-recipients(with-dry-run) adds them to mail imported earlier, without importing it again. Every message that gets recipients has an audit log entry, also sent to the SIEM when SIEM export is enabled. POST /api/v1/message/lookup: find an archived message by its Message-ID, also on the master of a multi-node setup.
Security#
- Per-message access checks extended to exports, similar messages, related attachments, bulk removal and message categories, matching the checks of search and the message view.
- API access tokens carry the user's category and group restrictions, so API clients and multi-node workers see the same messages as the web UI.
- AI thread summaries only include, and only share between users, the messages of the thread the user may see.
- A removed message (by the user, a data officer or a GDPR erasure) stays out of the search until it's purged, also when a copy of it arrives again or pilerimport processes it.
- pilerimport installed setuid takes its configuration only from its protected configuration file, not from the caller's environment.
- API requests with a Bearer token no longer create a server-side session.
Changed#
- pilerimport honours
ARCHIVE_MESSAGE_VARIANTSandSPAM_HEADER_LINElike the piler daemon. - The "Top Attachment Types" analytics widget is labelled as a cumulative total of all archived attachments.
- More
data-*customization hooks on the Search and Audit Log pages; accessibility and HTML markup fixes. - Upgraded frontend dependencies (Vue, axios, DOMPurify).
Fixed#
- Duplicates left an orphaned encrypted message file in the store (and in the S3 bucket); new duplicates no longer do.
- A message whose search index write failed stayed archived but unsearchable; a later copy of it now restores its index entry.
- Copies of a message arriving at the same moment could both be archived.
- Ingest deadlocks between the analytics sync and concurrent archiving (e.g. pilerimport failing with "Deadlock found").
- Emails whose
Toheader lists several addresses inside one pair of angle brackets ended up in the error directory; an unusable address inTo/Ccis now skipped and logged instead of failing the email. - IMAP logins with Mailcow lost the user's local group addresses.
- Multi-node: removal requests and the data officer's approval or rejection now take effect on the worker that has the message; restore and the message view report the worker's errors instead of returning an empty or wrong result.
- The thread indicator could be missing for regular users in long threads.
- A purged message answers "the message has been purged" instead of an error.
- Upgrading from 1.x: migration 005 failed with "Illegal mix of collations" on some MariaDB versions, and the deb package left the obsolete 1.x configuration files behind, blocking the piler-smtp installation.
2.2.2 - 2026-09-22#
Security#
- Upgraded
grpcandgo.opentelemetry.io/otelto address CVE-2026-84304 and related CVEs
Added#
- Per-message delete action in the preview pane
- Stable HTML customization hooks (
data-*attributes) for MSP frontend integration - Multi-node support for user email purge
Changed#
piler-uiDocker image build simplified to a prebuilt-binary copy (matches the pattern later applied to the scanner image)- Manticore search engine upgraded to 29.9.0
- Message selection: Ctrl/Cmd+click for single-message selection, Shift+click for range selection
Fixed#
- Table content clipping in dashboard cards
- Bogus sort order in multinode search results
- Regular users seeing 0 search results in multinode mode
- Timezone validation failing on Alpine runtime images (IANA tzdata now embedded rather than relying on the host OS)
- Unmounted Vue templates briefly flashing placeholder content (
v-cloak) - Trailing boundary CRLF in attachment MIME parts incorrectly flagged as invalid
- False-positive attachment size mismatch logging for base64-encoded attachments
- S3 promotion window returning an empty
200instead of503
2.2.1 - 2026-09-06#
Security#
- Upgraded
nanoidto 3.3.18 to fix CVE-2026-67213 (high severity)
Added#
S3_SKIP_EXISTING_OBJECTSto skip re-uploading objects already present across nodesS3_PART_SIZE_MBandS3_DISABLE_MULTIPARTknobs fors3uploaderpurge --restore-idfor restoring a specific prior purge run;PACKAGES.mddocumenting package build/signing
Changed#
- Dockerfiles upgraded to
alpine:3.24; audit log table gained stable CSS identifiers for MSP customization RPM_RELEASEoverride for RPM packaging; fixeddpkg-sigremoval on package updates
Fixed#
- Fixed a tenant-configuration bug that could cause scheduled purges to be skipped
- Nested-multipart attachments misindexed in
pilerimport; mismatched download objects now caught explicitly - Five distinct archiving bugs: duplicate detection, purge tombstone
handling,
Fromheader overflow,enable_purgeflag handling, and an attachment-processing race condition - Purge command: tombstone restore, recipient preservation, dry-run loop behavior, missing-env-var warning
2.2.0 - 2026-08-16#
Changed#
- Semantic search embeddings: replaced in-process ONNX embedding
generation with calls to an external, OpenAI-compatible HTTP endpoint
(
SEMANTIC_SEARCH_ENDPOINT_URL);piler-uiimage switched to an Alpine base as a result (no longer needs ONNX Runtime/CGO)
Added#
- OX (Open-Xchange) webmail integration: theme, language, timezone, and iframe CSS synchronization
Fixed#
- Session cookie expiry handled incorrectly in some flows;
dompurifyupgraded alongside the fix - Multi-tenant provisioning; tenant GUI URL not hot-reloading after change
- OAuth2 configs being rebuilt per-request instead of cached in multi-tenant mode
- Health page's "received messages" chart rendering incorrectly
2.1.5 - 2026-07-26#
Security#
grpcbumped to v1.82.1 to fix security vulnerabilitiesgofiberandgolang.org/x/netupgraded (security-motivated)dompurify3.4.11 → 3.4.12
Added#
- Pending conversations: badge in search results plus a dedicated drawer for messages awaiting a reply
- Attachment indexing gated on
TIKA_URLbeing configured, rather than always-on - Explicit Dependabot workflow for automated dependency PRs
- Standby-node support for import jobs; multi-node import handling
- Theme and accent-color support carried over from EAM authentication
Changed#
- Build tooling switched from the previous bundler to Vite (and bumped
several times through the 6.4→8.1 series as issues were found); iframe
session cookie set to
SameSite=Nonefor embedding scenarios; listens for apostMessagetheme-change event from a parent iframe - Import job handling simplified
Fixed#
- Email direction now computed per-message from the local domain table, rather than a possibly-stale cached value
c.IP()captured before entering a goroutine inZipAndStreamDownloadRemote(was reading a request-scoped value after the request had already returned)- Multi-node export; mobile screen layout issues; vendor CSS handling under the Vite build; header parser edge case; several import/migration fixes; purge logging
Dependencies#
- Routine bumps:
vue3.5.38→3.5.39,postcss8.5.16→8.5.23,axios1.18.0→1.18.1,@vitejs/plugin-vue5.2.4→6.0.7, plus a batch of GitHub Actions version bumps (actions/checkout,actions/setup-go,actions/setup-python,actions/upload-artifact,actions/download-artifact,docker/setup-buildx-action,docker/setup-qemu-action,docker/login-action,docker/metadata-action,softprops/action-gh-release)
2.1.4 - 2026-06-09#
Added#
- Standalone importer web app (
cmd/importUI) ANALYTICS_ENABLEDenvironment variable- Rate limiting on authentication endpoints (security hardening)
Changed#
- All config keys now registered with
viperregardless of whether a default is empty, so they can still be overridden via environment variables
Fixed#
- Microsoft 365 journal processing
- LDAP authentication edge case
- Header/footer branding logo display
serverIdevaluation lacking a safety check- Import job responses missing the
idfield on creation - Purge endpoint failing on already-deleted emails
- Background job SQL queries
- Extremely long
Message-ID/attachment filenames not handled correctly chowncommand incorrect in package install scripts
Dependencies#
gofiber/fiber2.52.12 → 2.52.13 (main app andcontrib/external-jwt-shared-secret)Azure/go-ntlmsspbumped
2.1.3 - 2026-04-09#
Added#
SHOW_IMPORT_MENUconfiguration option- NIS2 compliance documentation
Changed#
- Manticore upgraded to 25.0.0 in tests and the installer
Fixed#
- Swagger/OpenAPI docs generation
Dependencies#
go.opentelemetry.io/otel/sdk1.40.0 → 1.43.0russellhaering/goxmldsig1.4.0 → 1.6.0google.golang.org/grpc1.72.1 → 1.79.3
2.1.2 - 2026-03-15#
The largest release in this window - unified archiving for chat platforms,
GDPR right-to-erasure, passkey authentication, multi-master-node HA, and a
round of gosec-driven security hardening.
Security#
- Passkey (WebAuthn) authentication support added
gosecstatic-analysis fixes applied acrossreindex,imapimport,pilerimport, ands3uploader- TLS support added for the MySQL connection
- Added additional iframe-embedding security headers
- Sensitive SIEM connection details no longer appear in logs
Added#
- Slack and Microsoft Teams collectors: initial unified-archiving support for chat platforms, including a parallelized Slack collector and dedicated UI/API components
- GDPR Right to Erasure: multi-node-aware implementation
- Find similar messages: KNN vector search over message embeddings
- Multi-master-node support (previously single-master architecture)
- IMAP import checkpoint/resume support: connection details and progress are persisted so long-running imports can resume after an interruption
- Swagger/OpenAPI documentation for the admin API
- Attachment search, with flat mode made the default
SEMANTIC_SEARCH_ONNX_THREADSconfiguration option- SIEM Sumo Logic support
Changed#
- Metadata/attachment inserts now use a database transaction
- ediscovery exports now emit an audit log entry
- Purge performance improvements
Fixed#
- Deb packaging issue; API auth edge case; cron entry handling; various migration/import fixes
Dependencies#
gofiber/fiberbumped incontrib/external-jwt-shared-secret;go.opentelemetry.io/otel/sdk1.36.0 → 1.40.0;gofiber/fiber2.52.11 → 2.52.12;filippo.io/edwards255191.1.0 → 1.1.1
2.1.1 - 2025-12-13#
Added#
- SIEM support
install.shbare-metal installer script
Changed#
- Microsoft Teams collector updates
Dependencies#
gofiber/utilsbumped to 1.2.0