TL;DR
- On a deployment platform: added a real nginx reverse-proxy tier with health gating, provider capability enforcement, a standard blueprint library, and release rollback.
- On a CRM app: shipped email automations (journeys) — trigger-based sequences with enrollment and send tracking — plus organisation member management.
- Squashed a sharp multi-tenancy bug: self-registered users were created without a tenant, so they couldn't create anything. Two repos, same class of fix.
Two products moved meaningfully today (plus a dependency bump elsewhere). Honest rundown below.
Deployment platform: routing and safety rails
The headline is a tier-3 reverse proxy — a real nginx layer in front of workloads, with routing gated on workload health so traffic only reaches something that's actually up. Around it: infra providers now enforce capabilities (a provider advertises what it can do via a checklist, and the platform refuses operations it can't back), a standard blueprint library with MySQL/MariaDB provisioners, and release rollback so a bad deploy has a way home.
I also fixed two gotchas from spawning docker/kubectl out of PHP — the 30s web-request timeout and the empty-environment trap. That earned its own focused post today, so I won't repeat it here.
CRM: email journeys
The big feature is email automations — call them journeys or sequences. A contact hits a trigger (a tag added, a form submitted), gets enrolled, and walks through timed email steps. The design leans on the usual suspects:
| Piece | Role |
|---|---|
Enums (SequenceTrigger, SequenceStepType, SequenceSendStatus) |
Typed state with label()/color()
|
| Listeners on domain events | Enroll a contact when a tag is added |
| Send-status sync from mail events | Keep each step's delivery state honest |
Modelling the send as its own tracked entity — not just "fired and forgot" — is what makes the sequence auditable later.
The bug worth naming: a missing tenant
Same shape landed in two apps today. Self-registered users were being created without a tenant assignment, so downstream every "create a record" call failed — the record had nowhere to live. The fix is to make tenant assignment part of user creation, not an afterthought.
// in the create-user action, not left to chance later
$user->tenants()->attach($tenant->id, ['role' => 'owner']);
Lesson I keep relearning: in a multi-tenant app, "which tenant?" is not a later question. If a user can exist without one, some code path will find that gap.
What's next
Wire the reverse-proxy tier to real health checks end-to-end, and put the email-journey enrollment under a proper Pest suite before it touches live contacts.
Top comments (0)