Skip to content

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.sh is renamed to add-missing-columns.sh: it adds the participants column of 2.3.0 and the deleted_at column 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 is GET /api/v1/message/{id}/thread, the bulk download POST /api/v1/message/download, the bulk removal POST /api/v1/message/bulkremove, and an eDiscovery export /api/v1/ediscovery/exports/{job_id} (plural, also for cancel and download). The categories, the similar emails and the related attachments of an email are documented now.
  • add-missing-columns.sh skips 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_at column. add-missing-columns.sh adds 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, and PDF and pdf are 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_SECRET is 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_DAYS to 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_days of the piler row of piler.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.sh is upgrade-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 to YYYY/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 set for 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 From address (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_SECRET isn't set. piler-ui uses a random secret now when JWT_SECRET isn't set, and logs a warning, also about a secret shorter than 32 characters or one starting with changeme. The installers and the Docker examples set a random JWT_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.com and john@sales.example.com. Their content was never shown. The index now has a participants attribute with the sender and the recipients of each message, and a user's search only takes the messages they are on. reindex -participants fills it for the messages archived before the upgrade; until then those keep the earlier filter.
  • purge (installed setuid) ignores the caller's environment and PILER_ENV_FILE when run by another user, like pilerimport, and reads its settings from /var/piler/.env only.
  • pilerimport (installed setuid) creates the temporary files of the -mbox and -pst imports 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.
  • /metrics and /healthz took the client address from the request's own X-Forwarded-For or X-Real-IP header, so anyone reaching piler-ui, through the reverse proxy too, could read them. The header counts only from a proxy in TRUSTED_PROXIES now (only PROXY_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 the refresh_session table: 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.sh backs 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_failed and piler_backup_verification_failed, from the backup_status table, 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) and README.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-map replaces the Exchange addresses of internal mail with SMTP addresses from Active Directory, -skip-deleted skips Deleted Items. See Import PST files.
  • purge -orphans: reports the stored .m files 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 -remove it 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/quota and /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_count in 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 (default false, 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_USER and DISABLED_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_POLICIES and MIN_RETENTION_DAYS of piler-ui (both can be set per tenant in its settings_json), instead of rows of the options table, see Config options. ENABLE_TENANT_RETENTION_POLICIES=false now 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.
  • purge reads enable_purge of the tenant from piler.tenant_settings of 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.sh goes on with the other tenants after one fails. On a multi-node deployment, set enable_purge on every worker, see Change a tenant's settings.
  • The default retention of a new installation is 10 years (3653 days): the piler tenant was created with 7300 days, and the daemon's DEFAULT_RETENTION_DAYS fallback 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_PREFIX set: 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_SUBDIRS is removed. The objects were always stored and read in subdirectories; only purge honoured the setting, and with false it 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), --before downloads only the emails received before a day (e.g. the start of journaling), --dir sets 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+; install requests and msal in 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_count is 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/all show the usage collected every QUOTA_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 (besides local) is not counted: its report has no user count, and piler-ui logs a warning.
  • One billing server: the default BILLING_SERVER_URL is https://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-run said 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_settings on 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 options table 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.py lost 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. ../x was written outside the download directory.
  • Adding or saving a tenant with an invalid settings_json failed with "Failed to update tenant"; now the message says it must be a JSON object. A tenant/update API call without settings_json keeps 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 from S3_BUCKET_PREFIX + tenant: with a prefix set, the uploaded emails couldn't be read or purged. With S3_BUCKET_PREFIX or S3_PREFIX set, check the settings before starting piler-s3 after the upgrade, see S3 storage settings.
  • purge ignored PILER_STORE_DIR and PILER_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_DIR had 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_status table in the piler database, 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 the To and Cc ones, 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 From header, 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_VARIANTS and SPAM_HEADER_LINE like 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 To header lists several addresses inside one pair of angle brackets ended up in the error directory; an unusable address in To/Cc is 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 grpc and go.opentelemetry.io/otel to 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-ui Docker 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 200 instead of 503

2.2.1 - 2026-09-06#

Security#

  • Upgraded nanoid to 3.3.18 to fix CVE-2026-67213 (high severity)

Added#

  • S3_SKIP_EXISTING_OBJECTS to skip re-uploading objects already present across nodes
  • S3_PART_SIZE_MB and S3_DISABLE_MULTIPART knobs for s3uploader
  • purge --restore-id for restoring a specific prior purge run; PACKAGES.md documenting package build/signing

Changed#

  • Dockerfiles upgraded to alpine:3.24; audit log table gained stable CSS identifiers for MSP customization
  • RPM_RELEASE override for RPM packaging; fixed dpkg-sig removal 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, From header overflow, enable_purge flag 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-ui image 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; dompurify upgraded 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#

  • grpc bumped to v1.82.1 to fix security vulnerabilities
  • gofiber and golang.org/x/net upgraded (security-motivated)
  • dompurify 3.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_URL being 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=None for embedding scenarios; listens for a postMessage theme-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 in ZipAndStreamDownloadRemote (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: vue 3.5.38→3.5.39, postcss 8.5.16→8.5.23, axios 1.18.0→1.18.1, @vitejs/plugin-vue 5.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/import UI)
  • ANALYTICS_ENABLED environment variable
  • Rate limiting on authentication endpoints (security hardening)

Changed#

  • All config keys now registered with viper regardless 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
  • serverId evaluation lacking a safety check
  • Import job responses missing the id field on creation
  • Purge endpoint failing on already-deleted emails
  • Background job SQL queries
  • Extremely long Message-ID/attachment filenames not handled correctly
  • chown command incorrect in package install scripts

Dependencies#

  • gofiber/fiber 2.52.12 → 2.52.13 (main app and contrib/external-jwt-shared-secret)
  • Azure/go-ntlmssp bumped

2.1.3 - 2026-04-09#

Added#

  • SHOW_IMPORT_MENU configuration 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/sdk 1.40.0 → 1.43.0
  • russellhaering/goxmldsig 1.4.0 → 1.6.0
  • google.golang.org/grpc 1.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
  • gosec static-analysis fixes applied across reindex, imapimport, pilerimport, and s3uploader
  • 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_THREADS configuration 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/fiber bumped in contrib/external-jwt-shared-secret; go.opentelemetry.io/otel/sdk 1.36.0 → 1.40.0; gofiber/fiber 2.52.11 → 2.52.12; filippo.io/edwards25519 1.1.0 → 1.1.1

2.1.1 - 2025-12-13#

Added#

  • SIEM support
  • install.sh bare-metal installer script

Changed#

  • Microsoft Teams collector updates

Dependencies#

  • gofiber/utils bumped to 1.2.0