# Changelog

What changed in each release of ProjectSend, written for the people running it.

Versions follow [SemVer](https://semver.org/): the middle number moves when there are new features,
the last one when there are only fixes, and the first one when an upgrade needs more from you than
dropping in the new files and running the migrations.

Anything under **⚠️ Important — do these yourself** is something you have to do, not something
we did. It sits at the top of a release for that reason. Older entries call the same section
**Upgrade notes**.

## 2.8.0 — 28 October 2026

Connect an AI assistant to your account, require staff to sign in through your organisation's
provider, and a large batch of security fixes.

### ⚠️ Important — do these yourself

Everything else in this release happens on its own, and nothing here stops the upgrade.

- **Re-create API tokens that set file details.** Making a file public, its expiry date, its
  download limit, its categories and uploading into a public folder are now abilities you tick on
  the token. Older tokens do not have them, so those fields are quietly left as they were until the
  token is re-created with them.
- **Check API tokens that act on other people's files or manage staff.** A token now does only what
  it was given, even when its owner could do more. "Edit others' files" no longer covers the owner's
  own files, removing somebody else's comment needs "Moderate comments" on the token, and a token
  that manages staff accounts needs the abilities those accounts' roles carry. Only a token holding
  every ability can create or manage an administrator.
- **Check custom email templates that used Markdown.** Lines in a notification email are now shown
  as plain text, so links and formatting typed into a custom template appear as written.
- **If you installed from the zip and upload very large files,** add the upload-completion block
  from the nginx example in `INSTALL.md`. Without it, finishing a multi-gigabyte upload on slow
  storage can time out after a minute.

**New**

- **Connect an AI assistant.** Staff can connect Claude, ChatGPT or another assistant to their
  account. It can only do what that person can do, and everything it does appears in the activity
  log. It is on by default; Settings → AI assistants turns it off for everybody at once.
- **Require staff to sign in through one provider**, such as your Microsoft or Google organisation,
  from Settings → Social login. Switching somebody off there then keeps them out here too. Clients
  keep their passwords.
- **Skip two-factor for a provider** that already asks for a second factor of its own.
- **Select a whole page or the whole listing** on the files screen, and jump straight to a page.
- **The Branded email theme shows your logo.**
- *(Self-hosted)* **Register your own service providers** in `bootstrap/providers.local.php`, which
  updates leave alone.

**Closed holes in who can see what**

- An API token can no longer do more than it was given, on the API or through an AI assistant.
- A client's file editor shows and revokes only the public links that client made.
- Names and file names in notification emails are shown as text, so nobody can plant links or
  images in mail the site sends.
- A prepared zip is refused once its requester can no longer see a file in it. Zips prepared before
  this update must be prepared again.
- Marking a file as a new version can no longer share it, or the original, with people the staff
  member could not share with.
- A finished upload goes into its folder only if the uploader may still put it there.
- A staff member limited to some clients now sees and decides only their own clients' account
  requests, selects only their own library, and no longer has the orphan file tool.
- The first sign-in that links an existing account to a provider by email address still asks for
  two-factor.
- Erasing an account now also removes its API tokens.

**Fixed**

- Interrupted upload parts are kept, and very large files finish.
- A rate-limited API request answers 429 with a time to retry, not a 500.
- The virus scanner's error says what answered at the address when it is not ClamAV.
- The dashboard, the API page and the API guide fit on a phone.
- Third-party libraries are updated, including fixes for published security advisories.

Thanks to [@nguyenvantiennani](https://github.com/nguyenvantiennani),
[@Ratstack2](https://github.com/Ratstack2), [@auuftakt](https://github.com/auuftakt),
[@HuzaifaDal](https://github.com/HuzaifaDal), [@Haind03](https://github.com/Haind03),
[@echosecure](https://github.com/echosecure), [@Liam-G](https://github.com/Liam-G),
[@einsz](https://github.com/einsz), [@BradleyIRP](https://github.com/BradleyIRP),
[@CrawdadWarden](https://github.com/CrawdadWarden), [@BakerLabsBrad](https://github.com/BakerLabsBrad),
[@laurensprimusz](https://github.com/laurensprimusz), [@agardiol](https://github.com/agardiol),
[@JensS](https://github.com/JensS), [@veenone](https://github.com/veenone) and
[@mash2k3](https://github.com/mash2k3) for reporting and fixing.

### Issues closed since 2.7.1

The summary above is what changed. This is the paper trail, for anyone who wants to read the
original report.

- [#1791](https://github.com/projectsend/projectsend/issues/1791) — MFA dialog is shown even when OpenID Connect already has requested a second factor
- [#1819](https://github.com/projectsend/projectsend/issues/1819) — Branding logo does not appear v2.7.0 (zip install)
- [#1822](https://github.com/projectsend/projectsend/issues/1822) — [docker] Critical php RCE fixes available
- [#1824](https://github.com/projectsend/projectsend/issues/1824) — Update-safe way to register a custom email theme (local service providers)
- [#1828](https://github.com/projectsend/projectsend/issues/1828) — [Feature Request] Require Microsoft sign-in for staff while preserving local authentication for clients
- [#1831](https://github.com/projectsend/projectsend/issues/1831) — API: rate-limited requests return 500 instead of 429

## 2.7.1 — 22 October 2026

A security update for PHP itself, and fixes for email encryption, SELinux servers, older MariaDB
and MySQL, and the branding logo.

### ⚠️ Important — do these yourself

Nothing here stops the upgrade.

- **If you installed from the zip, update PHP on your server to 8.4.27 or later.** PHP fixed two
  critical flaws (CVE-2026-109618, CVE-2026-109619) that let anyone who can reach a site run code on
  it, with no login needed. ProjectSend cannot fix this for you, because it is in PHP itself. Restart
  PHP-FPM or Apache afterwards. System → About shows which PHP your web server is running. The
  Docker image already includes the fixed PHP.
- **The Encryption choice in Settings → Email now does what it says.** Until now it was ignored:
  "SSL" only worked on port 465, and "None" still switched to TLS whenever the mail server offered
  it. If mail stops after this update, check that setting. Use "TLS" for port 587 or 25, and "SSL"
  for port 465. "TLS" behaves exactly as before.

**Fixed**

- **Choosing "None" for email encryption now really sends without TLS**, for a mail server on your
  own network whose certificate doesn't match its name. The screen warns that this also sends the
  password unencrypted.
- **The branding logo shows up even when `APP_URL` doesn't match the address people use** to reach
  the site. Before, it was the only thing on the page that broke.
- **Updating with `update.sh` no longer takes the site down on servers with SELinux.** Files kept the
  label of the temporary folder they were unpacked in, and the web server could not read them.
- **Fresh installs work on MariaDB before 10.10 and on MySQL 5.7.** The install stopped at the
  notifications table. On those servers, an invitation's expiry date could also reset every time the
  invitation changed.

**Security**

- The Docker image now runs on PHP 8.4.27, and refuses to build on anything older.
- Updated `follow-redirects` and `vite` for their published advisories.

Thanks to [@oleasteo](https://github.com/oleasteo), [@jnewman67](https://github.com/jnewman67),
[@Puohis](https://github.com/Puohis), [@gregkuhnert](https://github.com/gregkuhnert) and
[@jiits](https://github.com/jiits) for reporting.

### Issues closed since 2.7.0

The summary above is what changed. This is the paper trail, for anyone who wants to read the
original report.

- [#1815](https://github.com/projectsend/projectsend/issues/1815) — [ Feature Request ] SMTP STARTTLS certificate verification cannot be disabled in email settings
- [#1816](https://github.com/projectsend/projectsend/issues/1816) — bug: create_notifications_table migrate fails on MariaDB/MySQL with explicit_defaults_for_timestamp=OFF (error 1067)
- [#1817](https://github.com/projectsend/projectsend/issues/1817) — SElinux blocks access to site after ProjectSend2 is updated with ./update.sh
- [#1819](https://github.com/projectsend/projectsend/issues/1819) — Branding logo does not appear v2.7.0 (zip install)
- [#1822](https://github.com/projectsend/projectsend/issues/1822) — [docker] Critical php RCE fixes available

## 2.7.0 — 6 October 2026

Four security fixes, folders you can manage over the API, and logo cropping.

### ⚠️ Important — do these yourself

Everything else in this release happens on its own, and nothing here stops the upgrade.

- **If your installation cannot send email, accounts that sign in through Google, Microsoft or
  another provider can no longer set their own first password.** The link to set it now arrives by
  email. Until mail works, an administrator can set the password from the person's account screen.
- **If an integration changes its own account's email address, password or two-factor
  authentication through the API, it now gets a 403.** Do that from the profile screen instead.
  Changing somebody else's still works as before.

**Added**

- **Folders in the API**: list, create, rename, move, delete and share them, and see where each one
  sits in the tree.
- **Crop your logo** from Branding → Logo, and put the original back at any time.
- **"Import all" on the orphan files screen** adopts every match in the background and shows its
  progress.
- **LDAP clients can sign in with their username** as well as their email address, once you name the
  username attribute in Settings → LDAP.

**Security**

- An API token can no longer change its own owner's password, email address or two-factor
  authentication, and setting somebody else's password now signs them out of the API.
- The staff screen no longer changes your own email address or password without your current
  password.
- An account that signs in through a provider now gets its first password from a link sent to its
  own email, not from whoever holds its browser session.
- A staff member limited to some clients now sees and revokes only their own invitations and those
  into their clients' groups.
- A staff member limited to some clients no longer sees the names of folders above the ones they can
  reach.
- The orphan file tool can no longer be pointed at a file that belongs to somebody.
- Creating a folder inside a public folder now needs permission to publish.

**Changed**

- **Your logo is shown larger** on the sign-in and download pages.
- **The API token screen warns you** when "Edit clients" or "Edit users" is chosen: a token with
  either can sign in as the accounts it may edit.
- The Docker image no longer fills its log with passing health checks; failing ones still show.

**Fixed**

- In the client portal, changing the sort inside a folder no longer jumps back to the top, and the
  chosen sort stays when you open a folder.
- Third-party libraries are updated, including fixes for published security advisories.

Thanks to [@simjiun](https://github.com/simjiun), [@sbouabid-sec](https://github.com/sbouabid-sec),
[@veenone](https://github.com/veenone), [@jiits](https://github.com/jiits),
[@cookiebaker](https://github.com/cookiebaker) and
[@01110111000001](https://github.com/01110111000001) for reporting and fixing.

### Issues closed since 2.6.0

The summary above is what changed. This is the paper trail, for anyone who wants to read the
original report.

- [#1798](https://github.com/projectsend/projectsend/issues/1798) — [ Feature request ] Branding : Site Name, Logo Size and Image Crop
- [#1806](https://github.com/projectsend/projectsend/issues/1806) — Sorting doesn't work with subfolders

## 2.6.0 — 25 September 2026

Mostly fixes: files and folders are easier to tidy, the sign-in pages carry your brand better, and
someone who deletes their own account stops being published straight away.

**Added**

- **Delete several files at once** from the selection bar on the Files page, with one confirmation.
- **Upload inside a folder puts the files in that folder**, and brings you back to it afterwards.
- **Show your site name under the logo** on the sign-in and download pages, from Branding → Logo.
- **Choose what happens to someone's files when they delete their own account**: removed right away,
  or at the end of the grace period — and whether that applies to everyone or only to clients.
  Found under Settings → Privacy.

**Changed**

- **A deleted account's files stop being shared at once.** From the moment someone deletes their own
  account, their files are visible only to staff — not to the clients and groups they were shared
  with, not through share links, not on the public pages — until the account is erased. Restoring
  the account brings them back.
- **Your logo is shown larger on the sign-in and download pages**, so a square logo is clearly
  visible. The Branding screen now says what size to use.

**Fixed**

- Saving a settings form could show an error dialog containing `{"count":0}` instead of saving.
- A file upload running several parts at once could be refused near the end with "too large".
- The System card reported the server's free disk space as file storage on installations that keep
  files in S3; it now shows the two separately.
- Confirming your password no longer throws away the form you were filling in.
- LDAP accounts can now get past the password confirmation, which kept them from turning on
  two-factor authentication.
- With per-client folders on, a client choosing "No folder" moved the file out of their own folder.
- PHP 8.5 no longer prints deprecation warnings from the database configuration.
- The Docker quick start now saves `compose.yaml`, so the `docker compose` commands in the other
  guides work as written. If you saved `compose.example.yaml`, rename it to `compose.yaml`.
- The Docker and migration guides now cover installing Docker, and migrating from a Legacy install
  on the same machine.

Thanks to [@JensS](https://github.com/JensS), binghuo, [@lukatong](https://github.com/lukatong),
[@jjoelc](https://github.com/jjoelc), [@jiits](https://github.com/jiits),
[@0xVavaldi](https://github.com/0xVavaldi) and [@lolgufdHD](https://github.com/lolgufdHD) for
reporting and fixing.

### Issues closed since 2.5.0

- [#1635](https://github.com/projectsend/projectsend/issues/1635) — Docs: consider moving the Docker quick start above screenshots
- [#1795](https://github.com/projectsend/projectsend/issues/1795) — Slightly confused regarding the reviews
- [#1796](https://github.com/projectsend/projectsend/issues/1796) — PHP 8.5.10: PDO::MYSQL_ATTR_SSL_CA Deprecated Warning
- [#1799](https://github.com/projectsend/projectsend/issues/1799) — Inertia error from notifications/unread-count because of JSON response
- [#1800](https://github.com/projectsend/projectsend/issues/1800) — Add Bulk-Edit for Moving and Deleting files
- [#1801](https://github.com/projectsend/projectsend/issues/1801) — Add Upload into Folder

## 2.5.0 — 18 September 2026

**Added**

- **Uploaded files can be checked for viruses before anyone can download them.**
- **Files the virus scanner refuses go to a Quarantine screen, where you can read the verdict and
  release a file if the report is wrong.**
- **You choose what happens to a file the scanner cannot open, and to uploads arriving while the
  scanner is unreachable.**
- **A Test button says which scanner answered and how old its virus definitions are, and fails a
  scanner that reports password-protected archives as clean.**
- **The dashboard says when virus scanning has stopped protecting anything.**
- **Files uploaded before scanning was switched on can be worked through in the background, at a
  pace you set.**
- **Invite a client to set their own password instead of handing them one.** Contributed by
  [@mash2k3](https://github.com/mash2k3).
- **Staff who administer clients are notified in the bell when a client account appears.**
- **Client accounts can expire on a date.** Requested by
  [@Drardollan](https://github.com/Drardollan) in
  [#1310](https://github.com/projectsend/projectsend/issues/1310).
- **Each role, and each person, can choose where they land after signing in.** Requested by
  [@Zodiac1978](https://github.com/Zodiac1978) in
  [#1777](https://github.com/projectsend/projectsend/issues/1777).
- **Each client can be given a folder of their own, which becomes their root.**
- **The file library filters by uploader, by the uploader's role, by public or private, by whether
  a file was ever downloaded, and by current or outdated version.**
- **The same five filters are available through the API.**
- **Files whose bytes have gone missing from storage are found daily and listed on their own
  screen.**
- **Clients see on each file the date it stops being available.**
- **Clients can make a public link for a file they uploaded**, where their role may publish files —
  the switch that marks a file public now comes with the link it promises, and they can copy or
  revoke it. Reported by Ricardo Cazati.
- **A public page says when the file on it was never checked for viruses**, which happens where an
  installation scans and chooses to let through what the scanner could not read.

**Closed holes in who can see what**

- **A staff member limited to certain clients can no longer reach past that limit through an
  invitation.** The invitation form listed every group on the installation, and an invited client
  could be pre-assigned into a group outside that staff member's own clients. Reported by
  [@hackchang](https://github.com/hackchang).

**Fixed**

- **Setting a folder inside the bucket no longer breaks every page that touches storage.** Reported
  by [@veenone](https://github.com/veenone) in
  [#1788](https://github.com/projectsend/projectsend/issues/1788).
- **The Microsoft sign-in settings now say why new accounts still wait for approval.** They wait
  until the `xms_edov` optional claim is added to the app registration, because until then Microsoft
  does not confirm that the person owns the address — which is a rule ProjectSend already had and
  nothing on screen said. Reported by Ricardo Cazati.
- **Someone who signs in with Microsoft, Google or another provider can now set a password.** The
  screen asked for the current one, which such an account never had — so it could not get a
  password, and therefore could not turn on two-factor authentication. Where two-factor is
  compulsory, that locked those accounts out of everything. Reported by Ricardo Cazati.
- **Creating a client with a storage quota typed in no longer answers with an error page.**
- **A file expiry date sent to the API as a number now answers 422 instead of 500.**
- **A quarantined file, or one missing from storage, is no longer listed in the library with a
  download that cannot work.**
- A dependency advisory in the bundled `js-yaml`.

### Issues closed since 2.4.1

- [#1310](https://github.com/projectsend/projectsend/issues/1310) — expire client accounts
- [#1658](https://github.com/projectsend/projectsend/issues/1658) — Docker services have no restart
  policy
- [#1770](https://github.com/projectsend/projectsend/issues/1770) — Docker upgrade fails with
  external storage configured
- [#1776](https://github.com/projectsend/projectsend/issues/1776) — logo gives error 404
- [#1777](https://github.com/projectsend/projectsend/issues/1777) — customization / branding
  (start pages)
- [#1778](https://github.com/projectsend/projectsend/issues/1778) — error 500 on install
- [#1779](https://github.com/projectsend/projectsend/issues/1779) — client invitations
- [#1788](https://github.com/projectsend/projectsend/issues/1788) — a bucket folder makes every
  storage page a 500
- [#1789](https://github.com/projectsend/projectsend/issues/1789) — S3 not working after an update

## 2.4.1 — 11 September 2026

### ⚠️ Important — do these yourself

Everything else in this release happens on its own. These do not: each one leaves something working
differently from how you expect until you act on it. Nothing here stops the upgrade or the
installation from starting.

- **If you set `PROJECTSEND_CAPTCHA_DISABLED`, check what you set it to.** Only `true` or `1`
  switches the CAPTCHA off now. Anything else — including `no`, `off`, `yes` and a misspelling —
  used to be read as "yes, disabled" and is now read as "leave it on". If you meant it off, write
  `true`.
- **If a staff role uploads into public folders, give it "Upload to public folders".** That
  permission was not being asked of staff, and now is. Roles holding "Upload public files" are
  unaffected, and ordinary uploads need nothing new.
- **If you use Microsoft sign-in, add the `xms_edov` optional claim to your app registration.** In
  the Entra portal: your app registration → Token configuration → Add optional claim → ID →
  `xms_edov`. Until you do, Microsoft sign-in keeps working and keeps creating new accounts, but it
  will no longer attach itself to an account that already exists.

**Added**

- **Your logo now appears on the sign-in screen.** Requested by
  [@Zodiac1978](https://github.com/Zodiac1978) in
  [#1777](https://github.com/projectsend/projectsend/issues/1777).
- **An installation on AWS can authenticate as its own IAM role instead of storing an access key.**
- **Clients can now see how often their own files were downloaded, and when.**

**Fixed**

- **A lookalike domain can no longer hand somebody else's account to an OIDC sign-in.** Reported by
  [@choewonwoo1817](https://github.com/choewonwoo1817).
- **Changing your own email address now asks for your password.** Reported by
  [@Noorkhalel](https://github.com/Noorkhalel).
- **Microsoft sign-in now checks that the person owns the address they presented.** Reported by
  [@archnexus707](https://github.com/archnexus707).
- **Two people filling in the first-run setup screen at the same moment can no longer both become
  administrators.** Reported by [@ry2811](https://github.com/ry2811).
- **Uploading into a public folder now needs a permission that says so.** Reported by
  [@skeletonsec](https://github.com/skeletonsec).
- **Moving a file into a public folder now needs that same permission.** Reported by
  [@skeletonsec](https://github.com/skeletonsec).
- **A staff member limited to some clients can no longer see or change other people's groups.**
  Reported by [@Drescargot](https://github.com/Drescargot).
- **Deleting a client can no longer hand their files to a client you do not manage.** Reported by
  [@skeletonsec](https://github.com/skeletonsec).
- **Erasing a staff account no longer hands their files to a client.**
- **An interrupted upload can no longer park unlimited bytes on the server.** Reported by
  [@ry2811](https://github.com/ry2811).
- **The password reset screen no longer says whether an email address has an account here.**
- **An expired password reset link now says so before asking for a new password.**
- **A Docker upgrade no longer fails when external storage is already configured.**
  [#1770](https://github.com/projectsend/projectsend/issues/1770).
- **A public gallery no longer renders the same thumbnail several times at once.**
- **`PROJECTSEND_CAPTCHA_DISABLED` no longer reads a "no" as a "yes".**

### Issues closed since 2.4.0

The summary above is what changed. This is the paper trail, for anyone who wants to read the
original report.

- [#1768](https://github.com/projectsend/projectsend/issues/1768) — Search in file not restricted in directory
- [#1773](https://github.com/projectsend/projectsend/issues/1773) — Feature Request : Support AWS IAM roles / default credential provider chain for S3 storage
- [#1774](https://github.com/projectsend/projectsend/issues/1774) — HTTP Error by upload on R2098
- [#1778](https://github.com/projectsend/projectsend/issues/1778) — [Documentation] Error 500 on install

## 2.4.0 — 8 September 2026

Clients can now look after the files they uploaded, and this release closes three ways somebody
could see a little more than they should.

**New**

- **Clients can edit and delete the files they uploaded**, with the name, description, expiry,
  categories, download limit and public flag each behind the permission that already governs it.
  A file shared *with* a client is still not theirs to touch.
- **A switch to stop this installation fetching the project news**, on Settings → General. On by
  default; off means the request is never made.

**Closed holes in who can see what**

- A staff member limited to their assigned clients could read other clients' names, and their IDs,
  out of file details and the uploader filter. Reported by
  [@Noorkhalel](https://github.com/Noorkhalel) (GHSA-whmp-p9hv-r7j7).
- Download links to external storage now last a minute instead of an hour. Previews keep the hour.
- Eight advisories in bundled dependencies, including an XSS bypass in the markdown renderer that
  builds your email templates.

**Fixed**

- A failed upload keeps its parts, so retrying it works instead of needing the whole file again.
- `projectsend:captcha-off` no longer claims success on an installation whose CAPTCHA keys are
  supplied centrally, where it changed nothing.

### Upgrade notes

- **Resuming an interrupted download from external storage more than a minute after it started now
  fails.** Start it again from ProjectSend. Local-disk installations and zip bundles are unaffected.
- **If your temporary directory is on a small or separate volume, allow headroom for twice your
  largest allowed upload.** Only while a file is being assembled, and nothing needs configuring.

Thanks to [@Noorkhalel](https://github.com/Noorkhalel), [@denkfabrik-li](https://github.com/denkfabrik-li)
and [@mehmedturk](https://github.com/mehmedturk) for reporting and fixing.

### Issues closed since 2.3.0

The summary above is what changed. This is the paper trail, for anyone who wants to read the
original report.

- [#1765](https://github.com/projectsend/projectsend/issues/1765) — Projectsend 2.2.1 thumbnail issue after file upload
- [#1771](https://github.com/projectsend/projectsend/issues/1771) — Permissions granted to the Client role are not applied to client accounts

## 2.3.0 — 1 September 2026

If you run ProjectSend on Apache or LiteSpeed, this is the release to take. It installed fine on
both before. Then every download arrived empty and every thumbnail was broken. That is fixed, and
you do not have to configure anything. Installations on nginx were never affected and nothing
changes for them.

The rest is mostly security work. Most of it is the same kind of thing: a screen or an API endpoint
that showed a little more than the person asking was allowed to see.

**New**

- **Downloads work on any web server.** Your files sit outside the web root, so ProjectSend checks
  permission on every download before anything is sent. The fast way to finish is to hand the file
  to the web server. Each web server wants that asked for differently, and until now ProjectSend
  only knew how to ask nginx. On Apache and LiteSpeed it asked anyway, nothing answered, and the
  visitor got an empty file. Now it works out what it is talking to. If it cannot hand the file
  over, it sends the file itself, which is slower under load but works everywhere.
- **Apache and LiteSpeed can still have the fast version.** Install `mod_xsendfile` (LiteSpeed
  needs no module), point `XSendFilePath` at your storage directory, and set
  `PROJECTSEND_FILE_DELIVERY=xsendfile`. See the upgrade notes.
- **The dashboard tells you which way downloads are going out.** If PHP is sending them, there is a
  warning next to it and a short explanation of what that costs you and how to change it. This is
  the kind of thing that is invisible until the day the site falls over, so it says so up front.
- **Your logo and your watermark, on every installation.** Upload a logo and it replaces ours in
  the sidebar and on your public pages. Add a watermark and it goes on the thumbnails and previews
  your clients and visitors see. Staff still see the originals, and the watermark is never written
  into the stored file, so you can turn it off again.
- **You can find out which build you are running.** Two images can say "2.2.1" and contain
  different code. `projectsend:status` now reports the commit it was built from.
- **You will know if the nightly jobs stop running.** When the scheduler dies, nothing looks wrong.
  You find out weeks later, when a file you expired is still downloadable. ProjectSend now reports
  when its scheduled work last ran and whether any of it failed.
- **You get told when the mailbox stops working**, even when a send noticed the problem before the
  scheduled check did.

**Closed holes in who can see what**

- [#1745](https://github.com/projectsend/projectsend/pull/1745) — Gate the comment moderation
  surfaces on reading, not just on the library. Permission to moderate comments was letting somebody
  read them, which is not the same thing: on the moderation screen and through the API, a role that
  could moderate comments but could not open any file was shown every comment in the installation —
  the text, staff-only notes, the client each conversation belongs to, and a visitor's IP address —
  about files it would be refused on. Approving a comment over the API handed back its body the same
  way.

  **Who this affected.** Only installations with a custom role built that way. None of the roles
  ProjectSend ships is affected: Account Manager, the only one that moderates comments, can read
  files as well, and so can a System Administrator. If you did build such a role, it can no longer
  moderate — give it one of the file permissions (upload, edit files, or edit other people's files)
  and it works again, now seeing only the comments on files it can actually open.

- [#1759](https://github.com/projectsend/projectsend/pull/1759) — Publish the example Docker
  quickstart on the loopback address instead of every network interface. The example set
  `TRUSTED_PROXIES: "*"`, which tells ProjectSend to believe the client address forwarded by
  whoever connects to it. That is right behind a reverse proxy and wrong when anyone can reach the
  container directly, because then anyone can claim any address: enough to walk past the login
  lockout, every rate limit, and the address written to the download log and to guest comments.

  **Who this affected.** Installations started from `compose.example.yaml` or from the Docker Hub
  page, where port 8080 was reachable from outside the machine. A published Docker port is not
  covered by a host firewall such as `ufw`, so this was often open without anyone intending it.

- [#1760](https://github.com/projectsend/projectsend/pull/1760) — Have the Docker image default to
  production. On first boot the image copied its settings from the development template, which sets
  `APP_ENV=local` and `APP_DEBUG=true`. Two things followed that you could not see from inside the
  application: every server error showed its stack trace — file, line and surrounding source — to
  whoever triggered it, signed in or not; and **"reject known-breached passwords" never actually
  ran**, while the security settings screen went on reporting it as switched on.

  **Who this affected.** Anyone who started the container without setting those two values: a plain
  `docker run` with a database address, the Portainer, unRAID and TrueNAS templates, or a Kubernetes
  manifest naming only the database and `APP_URL`. Installations using `compose.example.yaml`, which
  sets both correctly, were never affected.

- The client portal dashboard lists only files that client can open. The API dashboard's recent
  activity is cut the same way.
- Three lists were showing more than the viewer was allowed to see: the reassignment picker, the
  account conversion list, and the membership an API member write handed back.
- Mail and storage credentials no longer end up in the boot configuration cache. A settings form
  that gets rejected no longer sends the credential back to the browser.
- Connecting a sign-in provider asks for your password again. Every password prompt in front of an
  account now has its own rate limit instead of sharing one. A two-factor code is claimed in a
  single step, so the same code cannot be used twice.
- An expired file no longer locks a whole group shut for staff assigned to particular clients. A
  shared folder's contents count towards what a client can reach. A client is added to the roster
  of the staff member who created them.
- Whether something is an API request is decided by the route, not by a header the caller sets.
- The interface font is served from your own installation. Loading a page no longer tells a font
  CDN who is reading it.
- A stored filename can no longer push a control character into a response header.

**Fixed**

- The zip progress bar stops polling when you leave the page.
- A zip that fails to build no longer tells the person who asked for it why, in the server's words.
- Previews are written to a temporary file first, so a half-written one is never served. A file's
  previews are deleted even when its storage cannot be reached.
- An expiry date no longer moves because somebody else saved the file at the same time. Setting one
  through the API means what it means on the web form.
- Updating a client through the API no longer wipes custom fields the request never mentioned.
- The transfers chart lines up with the timezone its data is stored in.
- Creating an account over a deleted one's email address is refused instead of crashing.
- A comment still shows who wrote it after that account is deleted.
- Marking a file as a new version no longer emails people about a file they already had.
- The password reset and confirm-password screens say where the account's password actually lives,
  which matters if you use LDAP or a sign-in provider.
- A refused upload names the quota you are actually up against. A bulk edit that is refused says
  which permission was missing.
- Uploaded folders get the permissions the storage library actually asks for.
- The public preview log no longer records the same view repeatedly.
- Updating with `update.sh` no longer silently switches off route, event and view caching. The
  script wiped the compiled caches while replacing the files, which is also how ProjectSend
  recognised that you had cached them in the first place — so it rebuilt nothing, and every update
  quietly left the site slower than the install instructions promised.
- Every new screen in this release is translated into all sixteen languages.

**Before you upgrade, read the notes below.**

### Upgrade notes

- **This upgrade adds two indexes to the activity log, and on a big installation that takes
  minutes.** It is the slowest part. Nothing goes offline while it runs — the application keeps
  answering — but do not expect the migration to finish in seconds.
- **On Apache or LiteSpeed you need to do nothing, but there is something worth doing.** Downloads
  will start working on their own. PHP will be sending them, which ties up a worker process for the
  whole of each download. That is fine on a quiet site and not fine on a busy one. To move to the
  fast path: install `mod_xsendfile` (LiteSpeed needs no module), allow your storage directory with
  `XSendFilePath`, then set `PROJECTSEND_FILE_DELIVERY=xsendfile` in `.env`. The dashboard will
  confirm the change.

- **If you copied the example Docker file, `http://<your-server-ip>:8080` will stop answering.**
  That is the change. Reach the application through your reverse proxy, as `APP_URL` describes. If
  your proxy runs on a different machine, publish the port on the interface it arrives from and
  replace `TRUSTED_PROXIES: "*"` with that address or subnet — the two settings only make sense
  together.

- **Docker: `APP_ENV` and `APP_DEBUG` set inside `storage/.env` no longer take effect.** The image
  now sets them itself, and a real environment variable always beats that file. If you had turned
  debug on by editing `storage/.env`, pass `-e APP_DEBUG=true` (or `environment:` in compose)
  instead. Anything you already set that way keeps working unchanged.

Thanks to [@denkfabrik-li](https://github.com/denkfabrik-li), who wrote all forty-four pull
requests in this release, and to [@prbt2016](https://github.com/prbt2016), who reported the Apache
download failure that started the delivery work.

### Pull requests merged since 2.2.1

The summary above is what changed. This is the paper trail, for anyone who wants to read the
original change. No issues were closed in this cycle — the work arrived as pull requests.

- [#1718](https://github.com/projectsend/projectsend/pull/1718) — Narrow the reassignment picker to what a viewer may see
- [#1719](https://github.com/projectsend/projectsend/pull/1719) — Count a shared folder's contents as reach, not just the folder
- [#1720](https://github.com/projectsend/projectsend/pull/1720) — Stop an expired file locking a group shut for a scoped staff member
- [#1721](https://github.com/projectsend/projectsend/pull/1721) — Scope the API dashboard's recent actions to what the viewer may read
- [#1722](https://github.com/projectsend/projectsend/pull/1722) — Show the portal dashboard the files a client can actually open
- [#1723](https://github.com/projectsend/projectsend/pull/1723) — Stop a client PATCH clearing custom fields it never mentioned
- [#1725](https://github.com/projectsend/projectsend/pull/1725) — Write a rendition through a temporary file, and never serve an empty one
- [#1726](https://github.com/projectsend/projectsend/pull/1726) — Delete a file's renditions even when its own disk cannot be resolved
- [#1727](https://github.com/projectsend/projectsend/pull/1727) — Give an API expiry date the same meaning the web gives it
- [#1728](https://github.com/projectsend/projectsend/pull/1728) — Stop an expiry moving because somebody else saved the file
- [#1729](https://github.com/projectsend/projectsend/pull/1729) — Decide what is an API request from the route, not from the caller's headers
- [#1730](https://github.com/projectsend/projectsend/pull/1730) — Refuse to provision over a deleted account's address instead of crashing
- [#1731](https://github.com/projectsend/projectsend/pull/1731) — Fail a zip build without handing the requester the server's reason
- [#1732](https://github.com/projectsend/projectsend/pull/1732) — Debounce the public preview log the way the signed-in one already is
- [#1734](https://github.com/projectsend/projectsend/pull/1734) — Name the quota a client is actually held to when an upload is refused
- [#1735](https://github.com/projectsend/projectsend/pull/1735) — Stop an editable-once checkbox locking before anybody ticks it
- [#1736](https://github.com/projectsend/projectsend/pull/1736) — Put a client on the roster of the scoped staff member who created them
- [#1737](https://github.com/projectsend/projectsend/pull/1737) — Compare the transfers window against the column's own timezone
- [#1738](https://github.com/projectsend/projectsend/pull/1738) — Claim a TOTP code atomically instead of checking then writing
- [#1739](https://github.com/projectsend/projectsend/pull/1739) — Refresh a mailbox on the schedule under the lock a send would hold
- [#1740](https://github.com/projectsend/projectsend/pull/1740) — Leave the caches update.sh's own update command needs to see
- [#1741](https://github.com/projectsend/projectsend/pull/1741) — Ask about the zips queue on every path that could answer it
- [#1742](https://github.com/projectsend/projectsend/pull/1742) — Set the directory permission Flysystem actually reads
- [#1743](https://github.com/projectsend/projectsend/pull/1743) — Check the read half of the redirect rule at every door, not one
- [#1744](https://github.com/projectsend/projectsend/pull/1744) — Stop a version link telling people about a file they already had
- [#1745](https://github.com/projectsend/projectsend/pull/1745) — Gate the comment moderation surfaces on reading, not just on the library
- [#1746](https://github.com/projectsend/projectsend/pull/1746) — Say what expiry does to a client-scoped staff member's library
- [#1747](https://github.com/projectsend/projectsend/pull/1747) — Say which permission a bulk edit was actually missing
- [#1748](https://github.com/projectsend/projectsend/pull/1748) — Let a password reset know where the account's credentials live
- [#1749](https://github.com/projectsend/projectsend/pull/1749) — A deleted account is still the person who wrote the comment
- [#1750](https://github.com/projectsend/projectsend/pull/1750) — Tell the admins the mailbox is dead, even when a send noticed first
- [#1751](https://github.com/projectsend/projectsend/pull/1751) — Keep the mail and storage credentials out of the boot-config cache
- [#1752](https://github.com/projectsend/projectsend/pull/1752) — Bound the two preference endpoints by their own registries
- [#1753](https://github.com/projectsend/projectsend/pull/1753) — Narrow the conversion list to the clients its own refusal allows
- [#1754](https://github.com/projectsend/projectsend/pull/1754) — Narrow the membership an API member write hands back
- [#1755](https://github.com/projectsend/projectsend/pull/1755) — Give every password check in front of an account its own bucket
- [#1756](https://github.com/projectsend/projectsend/pull/1756) — Make linking a provider re-prove the password
- [#1757](https://github.com/projectsend/projectsend/pull/1757) — Stop a rejected settings form flashing the credential it carried
- [#1758](https://github.com/projectsend/projectsend/pull/1758) — Let the confirm-password screen ask where the password lives
- [#1759](https://github.com/projectsend/projectsend/pull/1759) — Publish the quickstart on loopback, since it trusts any proxy
- [#1760](https://github.com/projectsend/projectsend/pull/1760) — Have the production image default to production
- [#1761](https://github.com/projectsend/projectsend/pull/1761) — Serve the interface font from the installation, not from a font CDN
- [#1762](https://github.com/projectsend/projectsend/pull/1762) — Run the auth and settings screens through the translator
- [#1763](https://github.com/projectsend/projectsend/pull/1763) — Stop the zip poll when its page goes away
- [#1764](https://github.com/projectsend/projectsend/pull/1764) — Honour Laravel's placeholder case convention in t()

## 2.2.1 — 28 August 2026

A security release. Most of it closes ways somebody could reach past a boundary the rest of the
application already enforced — including two that could lock you out of your own installation.

**Merged**

- [#1708](https://github.com/projectsend/projectsend/pull/1708) — Let an enforced user reach the far side of the confirm-password screen
- [#1716](https://github.com/projectsend/projectsend/pull/1716) — Refuse the last administrator deleting themselves, and keep setup shut
- [#1710](https://github.com/projectsend/projectsend/pull/1710) — Stop a folder deleting the files inside it that its owner may not delete
- [#1714](https://github.com/projectsend/projectsend/pull/1714) — Hold the group edit screen to the same library boundary as the rest
- [#1717](https://github.com/projectsend/projectsend/pull/1717) — Keep a private reply private after the client is deleted
- [#1713](https://github.com/projectsend/projectsend/pull/1713) — Refuse self-deactivation over the API however the boolean is written
- [#1709](https://github.com/projectsend/projectsend/pull/1709) — Ask the seat cap where a pending client is approved through edit()
- [#1715](https://github.com/projectsend/projectsend/pull/1715) — Add a file to a zip once, however many ways the selection reaches it
- [#1707](https://github.com/projectsend/projectsend/pull/1707) — Leave the test workflow one concurrency block, so it parses again
- [#1711](https://github.com/projectsend/projectsend/pull/1711) — Stop the update tests emptying bootstrap/cache for every other worker
- [#1712](https://github.com/projectsend/projectsend/pull/1712) — Make the storage durability dashboard test assert the verdict

**Also fixed**

- The plain-text version of an email no longer shows the link twice, wrapped in brackets.
- The message you get when an account would exceed a limit no longer reads "limited to 1 staff
  accounts".

### Upgrade notes

- **Nothing to do.** Drop in the new files and run `php artisan migrate` as usual; this release adds
  no migrations, no settings and no new environment values.

- **One thing changes behaviour.** If somebody on your team has been deleting a folder as a way of
  clearing out files other people uploaded, that now refuses and says how many files are in the way.
  It is the same rule the file list has always applied one screen over — the folder was the way
  around it, and what it removed was not recoverable.

Thanks to [@denkfabrik-li](https://github.com/denkfabrik-li), who reported, diagnosed and fixed
every one of the above.

### Issues closed since 2.2.0

The summary above is what changed. This is the paper trail, for anyone who wants to read the
original report.

- [#1706](https://github.com/projectsend/projectsend/issues/1706) — V1 migration imports $2a$ bcrypt hashes that cause HTTP 500 on login

## 2.2.0 — 27 August 2026

A big release. Most of it closes holes in who can see what. The rest is a handful of new things.

**New**

- Google Cloud Storage can hold your files, alongside S3.
- You can set a maximum size for a zip download.
- Zip downloads no longer hold up your email.
- ProjectSend warns you if nothing is building your zip downloads.
- A deleted account's email address can be used again.

**Closed holes in who can see what**

- A staff member limited to their own clients now stays limited everywhere: client records, file
  names, groups, comment moderation, and the dashboard's activity and expired-file lists.
- Private notes on a publicly shared file stay private.
- Clients no longer see the names of folders they cannot open.
- The maximum file size now applies to large uploads too.
- A download limit now holds when a zip is collected.
- A large upload cannot be finished twice at once.
- A two-factor recovery code can only be used once.
- Your notification settings accept only the switches the screen offers.

**Fixed**

- People whose accounts came from ProjectSend v1 can sign in again.
- Sessions no longer break behind a reverse proxy.
- No more 502 Bad Gateway behind a reverse proxy.
- Saving after your session expires takes you to the login page, not an error.
- An upload that cannot be stored now fails instead of vanishing.
- Downloads, thumbnails and share links work when files are kept in cloud storage.
- A zip download is never offered when the archive was not actually written.
- Deleting an account either finishes completely or does nothing at all.
- A file can no longer be put into a folder that has been deleted.
- Declining a group membership request now happens once, not twice.
- Creating something with a create-only role no longer ends in an error page.
- Connecting Google or Microsoft to an account you already have now works.
- Downloads work on cPanel and Plesk, where the web server is not PHP's user.
- The dashboard no longer fails on shared hosting.
- An installation built from source is no longer told to pull an image.
- `docker logs` now shows the web server's log.
- The repair tool no longer mistakes cached previews for stray files.
- One confirmation message instead of two.

**Before you upgrade, read the two notes below.** One of them needs you to do something if you
installed ProjectSend by hand.

### Upgrade notes

- **Manual installs: your background worker needs one more queue.** Building a zip download now runs
  on its own queue, so a worker started before this version watches the wrong one — it will keep
  sending email perfectly while no zip download ever finishes, and nothing in any log will say why.
  `update.sh` spots this and offers to fix the service file for you, keeping a copy of the old one,
  so for most people there is nothing to do but say yes. If you upgrade by hand, change the
  `ExecStart` line in `/etc/systemd/system/projectsend-worker.service` to read
  `queue:work --queue=default,zips …`, then `sudo systemctl daemon-reload && sudo systemctl restart
  projectsend-worker`. INSTALL.md has the full file, and the two-worker setup if you would rather
  keep the two kinds of work apart. Docker installations need no change.

  If it is ever missed, ProjectSend now says so on screen: staff who can see system information get
  a banner naming the problem and the fix.

- **If you run behind a reverse proxy, check `TRUSTED_PROXIES`.** It is now read correctly, which it
  was not before. Set it in `.env`, and do not run `config:cache`, which stops `.env` being read at
  all.

Thanks to [@denkfabrik-li](https://github.com/denkfabrik-li), who found, diagnosed and fixed most
of the boundary work above, and to [@mstewart14](https://github.com/mstewart14),
[@elibrachas](https://github.com/elibrachas), [@mueller7382](https://github.com/mueller7382) and
[@pabloalvarez44](https://github.com/pabloalvarez44) for reports and fixes.

### Issues closed since 2.1.0

The summary above is what changed. This is the paper trail, for anyone who wants to read the
original report.

- [#1627](https://github.com/projectsend/projectsend/issues/1627) — Errors while installing via Docker
- [#1648](https://github.com/projectsend/projectsend/issues/1648) — A deleted account's email address can never be used again
- [#1661](https://github.com/projectsend/projectsend/issues/1661) — Docker update instructions do not update ProjectSend when using official Compose setup
- [#1662](https://github.com/projectsend/projectsend/issues/1662) — Preview files not available on v2.1.0
- [#1663](https://github.com/projectsend/projectsend/issues/1663) — Dashboard 500s on shared hosting: container detection trips open_basedir
- [#1664](https://github.com/projectsend/projectsend/issues/1664) — INSTALL.md: the nginx-in-front-of-Apache path needs the buffer advice too
- [#1668](https://github.com/projectsend/projectsend/issues/1668) — INSTALL.md: X-Accel downloads fail when nginx and PHP-FPM run as different users
- [#1672](https://github.com/projectsend/projectsend/issues/1672) — Projectsend 2 behind Traefik issues 419 when logging in or hitting an error?
- [#1673](https://github.com/projectsend/projectsend/issues/1673) — Projectsend 2: Setting Widget Columns throws error
- [#1675](https://github.com/projectsend/projectsend/issues/1675) — Success toast shows twice after create/delete redirects
- [#1706](https://github.com/projectsend/projectsend/issues/1706) — V1 migration imports $2a$ bcrypt hashes that cause HTTP 500 on login

## 2.1.0 — 18 August 2026

Updating, mostly. ProjectSend now tells you when there is a new version, ends an update somewhere
rather than just stopping, and — on a server you run yourself — reduces the whole procedure to one
command that asks before each step. This is also the first release published as an official Docker
image.

### Added

- **Ask for an update the moment you want to know.** ProjectSend checks once a day on its own,
  which is no help to somebody who has just read that a release fixes the thing biting them. There
  is now a **Check now** button, and it says what it found rather than only that it ran.
- **Updating ends somewhere.** The first time an administrator opens ProjectSend after an update,
  they land on a page saying which version they are now on and what came with it — and pointing at
  ProjectSend's Discord, which is where release news and help actually live.
- **A first run that shows you around.** A brand-new installation greets its administrator once,
  with a short list of the things worth doing before real files go in. Steps tick themselves off as
  you do them.
- **About says when this installation was last updated**, and to which version. It is the answer to
  "when did this change?", asked after something looks different or by whoever inherited the server.
- **The database stops quietly filling up with things nobody reads.** Permanently failed emails and
  already-read notifications both grew forever. Both now have a retention window you set under
  **Housekeeping** on the Scheduler screen, with a nightly purge that honours it — thirty days and
  ninety by default, and `0` to keep everything. Unread notifications are never deleted, whatever
  their age.
- **The update is in the activity log**, filterable like everything else. Until now the biggest
  change that can happen to an installation was the one thing its history did not record.
- **One command to update, and it asks first.** *(Self-hosted)* `sudo ./update.sh` is the whole
  procedure: it offers to back up, verifies what it downloaded, and stops rather than guessing. Its
  useful options — take the backup for me, just tell me what would happen — are one click away on
  the update screen, and **[UPDATE.md](UPDATE.md)** documents the whole thing.

### Changed

- **The browser tab takes its name from your site**, not from the build, and from the moment the
  page starts drawing rather than once it has loaded.
- **Each edition points at its own home**, and hosted customers are no longer asked to donate to
  something they already pay for.
- **The Scheduler screen names its tasks in your language.** Ten of them were English on an
  otherwise translated page.

### Fixed

- **A deleted folder, file or group no longer takes its name with it.** Deleting any of the three
  left the name reserved for good: creating another with that name failed with *"The slug has already been
  taken"*, naming a conflict with a row the interface will not show you, and there was no way to
  release it. Deleting now hands the name back, and names held by things you deleted earlier are
  released when you update.
  ([#1645](https://github.com/projectsend/projectsend/issues/1645))
- **The Legacy migration tool installs with the command the guide gives you.** It is published now,
  so `composer require projectsend/v1-migration-tool` works as written on any installation, with
  nothing to add to your `composer.json`. The guide also says plainly what a zip or Docker install
  can and cannot do — see the upgrade notes.
- **A non-standard port no longer disappears from links** in email and on public pages.
- **An update no longer stops over a symlink's ownership**, and the official image runs nginx as the
  user php-fpm writes as.
- **Translations shipped by a package now reach the screen that wrote them.**

### Upgrade notes

- **Nothing is required beyond the usual update.** No new configuration, no new permissions to
  grant. The one database change runs itself.
- **There is now an official Docker image**, `projectsend/projectsend`. If you have been building
  from a clone, you can switch to it: it ships with its dependencies and frontend already compiled,
  so it needs neither Composer nor Node. `compose.example.yaml` in `docker/production/` is a
  working starting point.
- **Migrating from ProjectSend Legacy?** Read the first step of
  **[MIGRATING-FROM-V1.md](MIGRATING-FROM-V1.md)** before you start. It now differs by how you
  installed: a release zip and the official Docker image have no `/system/migrate` screen, because
  the frontend they ship was built before the tool existed, and the `projectsend:migrate:*` commands
  are the whole interface there. Nothing about the migration itself is missing.
- **Reporting a security issue** now has a front door: the **Report a vulnerability** button on the
  repository's Security tab, or `contact@projectsend.org`. See
  [SECURITY.md](SECURITY.md).

## 2.0.0 — 2026-08-14

ProjectSend, rebuilt from the ground up. This is a new application rather than an update to the one
before it: it installs fresh, and an optional tool imports your existing site into it. The previous
generation continues to be available as ProjectSend Legacy.

Everything you already rely on came across — roles and granular permissions, folders, two-factor
authentication, LDAP, single sign-on, per-file download limits, S3-compatible storage, custom
fields, per-client quotas, zip downloads, resumable uploads, thumbnails, editable email templates,
client portal themes and the activity log. What follows is what is genuinely new. **If you are
running the previous version, read the upgrade notes first.**

### Added

- **A REST API.** Files, clients, groups and comments — and, on self-hosted installations, staff
  accounts — with scoped tokens, documentation the application generates itself, and a usage
  dashboard. There was previously no programmable surface at all.
- **Comments on files.** The conversation about a file lives on the file, with a per-comment choice
  of who can see it and strict separation between clients.
- **File versions.** Mark an upload as replacing an earlier one and the two stay linked: the older
  file is marked *Outdated* everywhere, sharing is inherited, and recipients are notified.
- **A notification centre.** A bell with an unread count on every screen for staff and clients
  alike, a full history, and per-person control over what also arrives by email.
- **Folder sharing.** Share a folder with a client or group and everything inside follows, including
  files added later.
- **Per-person timezones.** Every date reads in the timezone of whoever is looking at it, detected
  once from their browser, instead of one setting for the whole installation.
- **Recoverability, and real erasure.** Deleted files and accounts can be restored. Self-service
  account deletion runs a disclosed grace period and then a genuine erasure, activity log included.
- **Scoped staff access.** "Limit to assigned clients" narrows a staff member's entire view, not
  just who they may upload to.
- **Sixteen languages, and a menu you curate.** Choose which languages your clients are offered and
  which one everybody starts in.
- **Themed email.** Four independent email themes, with a live preview of a real message.
- **Guided setup.** A two-minute first run or fully unattended provisioning, a database that
  migrates itself on boot, and upgrade instructions matching how you actually installed.

### Improved

- **Uploads survive the connection.** Pause and resume, retrying the missing pieces rather than the
  whole file — and clients and API callers get the identical pipeline staff use.
- **Zip downloads no longer tie up the server.** Built in the background, with the folder structure
  preserved inside, and still recorded against every file bundled.
- **The interface.** Dark mode, drag-and-drop moves, a details panel with sharing and history in
  tabs, and the same search-and-filter toolbar on every list — with filters in the address bar, so a
  view can be bookmarked or sent to a colleague.
- **A storage floor, not just ceilings.** A site-wide default quota that individual clients
  override, so an installation with self-registration is no longer unbounded.
- **Categories your clients can read.** Flat, cross-cutting and colour-coded, shown everywhere the
  file appears — including the client portal and public pages.

### Upgrade notes

- **This is not an in-place upgrade.** Install this release fresh, then bring your old site into it
  with the migration tool. Nothing is ever written to your old installation, it keeps running
  throughout, and any import can be undone with a single command.
- **Your clients will sign in with their email address**, not their username. Their existing
  passwords keep working, so nobody has to reset anything.
- **Three things the previous version could do are not here yet:** IP allow and deny lists, email as
  a second factor, and encryption at rest. If you depend on any of them, stay where you are for now.
- **Two accounts cannot share an email address.** The previous version allowed it, because it signed
  people in by username. The import refuses to start and names the accounts to fix, rather than
  silently merging two people into one.
- **Nested categories become flat.** Each keeps its full path as its name — "Clients / Acme /
  Invoices" — so nothing is lost and no two categories collapse into one. Rename them afterwards if
  you would rather.
- **Files encrypted at rest, or held on external cloud storage, are not imported.** They are listed
  for you individually before the run starts, and skipped rather than guessed at.

