Four templates, one layout. Two of them — the maintenance announcement and its
cancellation — still wore Laravel's markdown component, which brings its own
logo, its own button and its own footer: the product was sending mail that
looked like two different companies, one of which was the framework.
New: an order confirmation, sent when the payment lands rather than when
provisioning finishes. Those are minutes apart at best and can be much longer,
and somebody who has just been charged and heard nothing assumes the worst
about both the charge and the product. It deliberately promises no time for the
cloud itself — provisioning takes what it takes, CloudReady announces the end,
and a promised minute that slips is worse than no promise. Queued after the
transaction, never inside it, and never allowed to fail the webhook: Stripe
retries anything that is not a 2xx, and a retry would re-enter provisioning
over a mail server having a bad minute.
The finished-cloud mail now names what was actually built — address, plan,
storage, location — because somebody who ordered a fortnight ago is checking it
against what they bought, and that is a comparison rather than a sentence. Still
no credential in it.
Two tests earn their place. One holds every template on the shared layout, so a
new one written in a hurry cannot copy-paste its way back to the framework's.
The other proves the finished-cloud mail carries no password — asserted against
the data the mail is given and the HTML it renders, not against the template's
source, because the first version of that test tripped over the word in a
comment and would have passed a template that leaked the value through a
variable. It tested the prose, not the mail.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The password no longer travels through third-party mail servers or sits
forever in an inbox: it moves onto the instance (encrypted, same as
nc_admin_ref) and shows on the dashboard until the customer clicks
"Notiert", which deletes it for good.
A mutation the reviewer ran (if ($box !== null) -> if (true)) left every
test green: CloudReady hand-rolled the same null-mailbox guard as
SendsFromMailbox, but nothing exercised its null branch. Added the
missing test, then extracted the decision itself (null mailbox -> no
sender fields, no_reply -> no Reply-To) into one shared method so an
Envelope-shaped and a MailMessage-shaped consumer can no longer disagree.
- applyFirewall sets policy_in=DROP so only 80/443 are exposed.
- Capacity/placement account for disk_gb (the real VM allocation), not the
smaller Nextcloud user quota — no systematic overcommit.
- CloudReady sent synchronously again; the step retries on mail failure with the
password preserved (no lost credential in a failed queue job).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- CloudReady receives the ENCRYPTED password (decrypts only when rendering), so
the queued payload never holds plaintext.
- RegisterBackup uses a valid Proxmox calendar expression (02:00).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Accept checkout.session.async_payment_succeeded (async methods) and dedupe on
the checkout session id, not the event id.
- committedGb still counts a failed instance while its VM exists (has vmid);
releases quota only when no VM was created — no overcommit vs no leak.
- CloudReady is queued (durable). Credential delivery is documented at-least-once
(a rare duplicate welcome email beats a lost credential; exactly-once = v1.1 outbox).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>