v0.7.1
24 September 2026 Latest- Editing a sent quote no longer silently pushes its expiry to 30 days from now; validity only changes when the editor sets it.
- Two concurrent creates can no longer receive the same reference: numbers are claimed with a compare-and-set and
(channelId, reference)is unique. - Viewing, accepting/declining and converting are now single-shot: parallel first views produce one "opened" event and one notification, a double-submitted accept cannot notify twice, and a double "Convert" cannot create two draft orders (the quote is claimed before the draft is created and released if it fails).
- A revision now supersedes the original (which is cancelled) instead of leaving both acceptable.
- Catalogue search and request pricing work on Postgres (
enabled IS TRUEinstead of= 1) and in non-English shops (the admin's language, falling back to English). requestedquotes are no longer served on the public quote URL before they are priced.
- One pooled SMTP transport with 10 s connect / 30 s send deadlines instead of a new connection per email with nodemailer's 2 min / 10 min defaults. Merchant notifications and request acknowledgements are sent off the request path, so a slow mail server never stalls the customer's page or the hourly cron.
- The public request throttle keys on the real client address (no longer trusts a spoofable header), adds a per-email bucket, and evicts idle entries instead of wiping everyone's counters.
- Quote lines are written in one statement; long text fields are capped; config reads no longer swallow database errors and fall back to defaults.
- Chaser and expiry-reminder sweeps read each quote once; a skipped hourly tick is logged; the licence-server lead call has a timeout.
- Line and VAT rounding use integer arithmetic; emails and the quote page format the validity date the same way; expiry is judged by the database clock everywhere.
- Admin editor: search results that arrive out of order are ignored; timers are cleared on destroy.