Skip to content

v0.23.0

Choose a tag to compare

@heliocosta-dev heliocosta-dev released this 24 Oct 17:33
· 171 commits to main since this release
a10cb08

MillionSend v0.23.0 is a security release. The image moves to Node 24.22.1 for Node's October 2026 security release (#191), and the libraries that receive and parse mail move past three advisories and several slow spots (#182, #184). More mail that fails authentication is now held (#192), and a broadcast built from blocks can switch to HTML (#170). There are no database migrations, though pg-boss updates its own schema on first start. If you run an MX, update it too.

Security

  • Node 24.22.1 (#191, reported in #190). The image ran Node 24.19.0. 24.22.1 is the 24.x build of Node's October 2026 security release, which fixes eight CVEs in Node 24, four of them rated high. It also brings OpenSSL 3.5.9, c-ares 1.34.9 and undici 7.30.1. It also refuses certificate chains through the eight eMudhra/emSign intermediate CAs whose keys were compromised.
    • The MX runs the same image, so update it along with the app.
  • Slow spots in reading received mail are fixed (#182, #184). One crafted message could keep mailauth (now 8.0.1, on the MX) or mailsplit (now 5.4.27, under mailparser in the app) busy for a long time.
  • nodemailer 10.1.7 (#184) fixes GHSA-8c8m-p3m8-cjg8, GHSA-m943-m63m-w62w and GHSA-85x2-q946-cp5h: crafted addresses were read differently from the way RFC 5322 reads them.
    • Received mail no longer records a From address other than the one the header names. In a few cases it now records no From address at all.
    • The headers the worker builds now show the same addresses the API accepted.
    • Who receives a message was never affected: the worker gives SES the recipients stored with the email, not the ones in its headers.
  • Failed authentication now counts even when the From can't be read (#192). This applies to inboxes that accept mail from anyone. Before, mail whose From isn't one readable address read as unauthenticated even when it failed authentication: it failed DMARC under p=reject or p=quarantine, or it failed SPF with no aligned DKIM signature and no DMARC policy. An agent inbox screened such mail and could deliver it, and a people inbox delivered it marked suspicious. It now reads failed:
    • Agent inboxes quarantine it.
    • People inboxes send it to spam.
    • Its spam_score gets the 40 points a failed sender adds.
  • The email editor (#183) moves to prosemirror-view 1.42.6, which fixes a paste-handling XSS (GHSA-c8x8-7fp4-3x9w).
  • Mail libraries (#182):
    • smtp-server closes a STARTTLS plaintext-injection race.
    • mailparser no longer takes an address out of an encoded display name.
  • Transitive packages (#183, #194): markdown-it, undici 6 and 7, source-map-js and zod move past their advisories. Some of the old copies never ran in production:
    • The old undici 7 copy was used only by the docs site, which reads a local OpenAPI file.
    • The old zod copy was used only by the tests.
    • source-map-js runs only at build time.
  • The Postgres image (#194) moves to the 21 October build of 17-alpine. It is the same PostgreSQL 17.11, now on Alpine 3.24.3 with OpenSSL, musl and zlib fixes. The backup image is built on it.

Receiving and the SMTP relay

  • The MX and the SMTP relay no longer drop a session for being idle while they are still working on its command (#184). Before, if a reply took longer than the idle timeout, the session ended with 421, and the client could send again a message that had already been accepted. With default settings only the SMTP relay could hit this, since its idle timeout is 60 s.
  • A HELO or EHLO name with control characters or over 300 characters is refused with 501 (#182). Before, both servers accepted it, and the MX cleaned it up before storing it.
  • When a received message repeats a header such as From, Subject or Date, the first copy is used (#182). That is the copy mail clients show. Before, the last copy was used.
  • Received mail with a broken MIME structure reads the way mail clients read it (#182, #184).
    • A part after an inner multipart that was never closed is no longer swallowed into the text before it.
    • An encoded attachment name is decoded once.
  • Quoted lines in plain-text mail sent as format=flowed now unwrap correctly (#184). Lines at different quote levels are no longer run together, and no > is left in the middle of a line. Mail already received reads the new way too, except for the reply text that was pulled out when it arrived.
  • A message with an extremely long header block is stored with empty metadata, and its raw message is kept. The SMTP relay answers such a message with 451.

In the dashboard

  • Broadcasts → Edit as HTML (#170): a broadcast built from blocks can switch to the code editor.
    • Blocks with a Show if condition are left out of the HTML.
    • Until you save, Back to blocks restores the blocks exactly.
    • A recovered draft reopens in the mode it was written in.
    • Thanks to @pedr0v.
  • Contacts (#181): the dashboard checks number and date property values the way the API does.
    • A value the API would refuse turns its field red and disables Save.
    • Saving a contact no longer trims spaces from a property key that was set through the API. Before, the trim renamed the key.

Self-hosting

  • Branded tracking hosts (#180): without TRACKING_EDGE_HOST, a team's tracking subdomain points at the app's own host. Whatever serves HTTPS for the app then also needs a certificate for that subdomain, or the team's links fail. The self-hosting guide has a new Branded tracking hosts section that lists the fixes:
    • Add the names to certbot.
    • Run the tracking edge, a Caddy container that gets certificates on demand, now in deploy/tracking-edge.
    • Use Cloudflare for SaaS.
    • Have teams skip the tracking subdomain.

Upgrading a self-hosted instance

  • No database migrations and no new environment variables. Run docker compose pull and docker compose up -d, or on Kubernetes upgrade with --version 0.23.0.
  • If you receive mail, update the MX as well, after the app.
    • On a separate MX host, set MILLIONSEND_TAG=0.23.0 in its .env, then run docker compose pull && docker compose up -d.
    • The mx service of the main compose file runs the app's image, so the usual pull and up -d cover it.
    • The MX's mail-checking library moved to a new major version. Afterwards, send one message to a receiving address and check that it arrives with its SPF, DKIM and DMARC results.
  • pg-boss upgrades its own schema from 45 to 46 when the first process starts. The change is additive, and its indexes build concurrently. After upgrading, rolling back to v0.22.1 or earlier crashes the worker unless the pgboss schema is rolled back too. Fix forward instead.
  • After deploying, try a paste and a drag-and-drop in the template and broadcast editors. The editor's ProseMirror packages moved (#183), and the automated tests don't cover a real paste.
  • The new Postgres digest restarts the postgres container on docker compose up -d if you use the updated compose file. It is the same 17.11, so no dump or restore is needed.