Laravel SaaS baseline with PostgreSQL, managed authentication, transactional email, and containerized deployment
A minimal Laravel SaaS stack using PostgreSQL as the system of record, Clerk for managed authentication, Resend for transactional email, and Docker packaging. Tenant membership, authorization, transactional outbox processing, deployment topology, backups, and operational controls remain application and infrastructure implementation work.
Executable examples and validation
No executable package has been validated for this architecture yet. The implementation sequence below is guidance, not a tested repository.
Validation badges are scoped to specific artifacts and recorded tests. They never certify a full stack unless that scope was actually tested.
Architecture and implementation
Architecture
Laravel is the application boundary for HTTP requests, tenant-scoped domain logic, authorization, database writes, authenticated webhook intake, and asynchronous work. PostgreSQL stores tenants, memberships, application records, sessions/revocation metadata where needed, the transactional outbox, and durably accepted webhook events. A single database transaction writes a domain change and its corresponding outbox record. A Laravel worker claims pending outbox rows, records sending with an attempt token and lease, sends through Resend, then records sent, unknown, or error according to the observed outcome. A crash or lease expiry after an attempted transmission must transition the row to unknown, not automatically back to pending, unless reconciliation safely establishes that no send occurred. A timeout after a transmit attempt transitions to unknown rather than error unless subsequent provider reconciliation establishes an outcome. Clerk supplies application authentication; Laravel must verify identity context and map it to a local user and tenant membership before every protected state change. For any selected Resend or Clerk webhook, Laravel verifies provider authenticity against the raw request before trusting payload fields, durably records accepted events, and processes them idempotently. Docker packages the Laravel web and worker processes for a selected hosting environment, which is not included in the required stack.
Implementation sequence
- Create the Laravel application and define a relational tenant model: tenants, users or external-identity mappings, memberships, roles/permissions, and tenant-owned domain records. Put tenant_id on every tenant-owned record and add database indexes and constraints appropriate to those relationships.
- Configure PostgreSQL as the sole system of record. Use migrations for schema evolution and ensure each request or job derives its tenant context from trusted server-side membership data, not merely a client-provided tenant identifier.
- Integrate Clerk authentication at Laravel's server boundary. Validate the authenticated identity according to the chosen integration approach, map its stable external subject to a local user record, and load active membership and role data server-side. Reject unauthenticated, unmapped, suspended, or non-member access.
- Implement authorization policies or equivalent service-layer checks for every state-changing route, command, and background job. Scope resource queries by the authorized tenant and verify that the resource belongs to that tenant before mutation; do not rely on route parameters or UI visibility as authorization.
- Create an outbox table containing a stable event ID, tenant/user context where appropriate, message type, versioned payload, provider message ID when returned, status (pending, sending, sent, unknown, error), attempt count, claim/lease timestamps, last error, and timestamps. Write outbox rows in the same PostgreSQL transaction as the domain state change that requires notification.
- Build a Laravel worker that atomically claims eligible pending rows, records sending plus a lease and attempt token, invokes Resend, and records the provider message ID and sent only after a confirmed response. On worker recovery, treat expired sending rows as unknown when a transmit attempt may have occurred; reconcile before any resend. Keep outbound request idempotency, if a supported provider mechanism is verified and selected, separate from inbound webhook event deduplication.
- On ambiguous outcomes such as a timeout after a request may have been transmitted, persist unknown rather than treating it as failed. Reconcile unknown rows through provider lookup by stored provider message ID, supported outbound idempotency behavior, or authenticated supported delivery events only if the selected provider integration demonstrably supports the chosen mechanism. Otherwise, expose these rows to an operator queue with payload, recipients, timestamps, and attempt history for manual resolution.
- For every selected provider webhook, verify the provider's documented signature or authentication over the unmodified raw request before parsing or trusting payload fields. Reject or quarantine unverified events without changing application or outbox state. For verified events, durably insert the raw event, provider event identifier, provider/source, receipt time, processing status, and payload metadata in a transaction, with a uniqueness constraint on provider/source plus provider event identifier. Acknowledge only after durable acceptance; return a retryable failure when durable acceptance is unavailable.
- Process accepted webhook records asynchronously with atomic claiming, leases, attempt history, and idempotent state transitions. In one database transaction, apply the outbox or local identity/membership update and mark the webhook processed; a crash before commit leaves it eligible for safe reprocessing. Do not mark an event processed before its related state update commits. Retain periodic reconciliation and operator handling for missing, invalid, duplicate, or ambiguous events.
- Establish an explicit tenant suspension and revocation policy. On each protected request, Laravel checks local membership status. If Clerk-side disablement or membership events are delayed, previously issued identity/session material may remain usable until the application performs that check or refreshes/reconciles local state; local suspension should deny access immediately on the next application authorization check. Define how authenticated provider events, administrative actions, and periodic reconciliation update local state.
- Create separate Docker images or process definitions for the web process and long-running outbox and webhook workers. Inject database, Clerk, and Resend credentials through the deployment environment rather than the image, run database migrations as a controlled release step, and ensure worker deployment supports graceful termination and lease recovery.
- As future operating work, select and configure hosting, TLS termination, DNS, CI/CD, PostgreSQL backup retention, encrypted offsite backup storage, restore drills, log retention, alerting, worker supervision, health checks, capacity management, and incident procedures. Test restores and outbox and webhook recovery before relying on them operationally.
Security and operations
- Apply tenant scope and authorization on the server for every mutation, including queued jobs and administrative endpoints. Treat client tenant IDs, role claims, and UI restrictions as untrusted hints until checked against server-side membership state.
- For selected webhooks, authenticate the raw request before trusting event identifiers, tenant data, delivery status, or identity changes. Use durable provider-event uniqueness and idempotent processing; duplicate, replayed, malformed, or unverified requests must not alter application state.
- Use least-privilege PostgreSQL credentials, restrict network access, encrypt connections where supported by the deployed environment, and keep database, authentication, webhook-verification, and email credentials in a secret-management mechanism provided by the eventual host.
- Store only necessary email payload and webhook data. Protect recipient addresses, raw webhook bodies, and message content in logs and operator tooling, redact secrets, and set retention/deletion rules for personal data.
- Use opaque event IDs and stable provider correlation IDs where available. Do not equate an HTTP timeout or an expired worker lease with non-delivery, and retain enough immutable attempt history for incident investigation.
- Clerk authentication does not itself establish application tenant authorization. Enforce local membership status at request/job execution time. Provider-side identity changes or event delivery can be delayed; access denial timing depends on the application's local checks and synchronization design.
- Back up PostgreSQL, protect backup encryption keys and access, limit who can restore data, and validate recovery using restore drills. Docker images are not a replacement for database backups.
Limitations
- No hosting, DNS, TLS termination, CI/CD provider, queue broker, monitoring service, backup destination, or secret-management product is included in the required component set; these are deployment prerequisites to select and configure.
- The catalogue descriptions do not establish whether Resend offers a particular idempotency key, message lookup, delivery-event capability, or webhook-verification mechanism for this use case. Until verified and implemented, ambiguous send results provide at-least-once behavior and can create duplicate emails. Operators must inspect unknown outbox records, correlate any available provider identifiers or authenticated events, decide whether to retry or mark resolved, and record the resolution.
- Webhook authentication format, event identifiers, retry semantics, and reconciliation capabilities are provider-specific and must be verified for each selected integration. Events that cannot be authenticated or durably accepted must not update state; missed events still require reconciliation or operator handling.
- PostgreSQL-backed outbox polling is intentionally the least-complex starting point. Worker throughput, locking behavior, retry schedules, lease duration, dead-letter/operator workflows, and crash recovery require load and failure testing.
- The brief permits PostgreSQL or MySQL; this design selects PostgreSQL. A MySQL implementation would require reassessing migrations, constraints, and the outbox and webhook row-claim approaches.
- Clerk-to-local membership synchronization, identity disabling, session lifetime, and webhook/event verification are project-specific controls that must be designed and tested. No instantaneous global account-disable guarantee is made.
- Docker packages the workload but does not provide a production hosting control plane, automatic scaling, certificate management, backups, or runtime observability.
Alternatives to consider
- Use MySQL (product 32) instead of PostgreSQL if organizational hosting or operational constraints favor it; adapt the outbox claim and migration design accordingly.
- Use Auth0 (product 42) or self-hosted Keycloak (product 41) instead of Clerk if their identity-management model better fits the project; retain Laravel-side tenant membership checks.
- Use Postmark (product 12) or SendGrid (product 13) instead of Resend for transactional email after validating the provider's correlation, reconciliation, webhook, and idempotency capabilities required by the outbox design.
- Add Redis (product 34), Valkey (product 35), or RabbitMQ (product 91) later if measured outbox throughput or broader asynchronous workloads exceed a database-polled worker design; this adds operational complexity.
- Use a selected cloud or server provider such as DigitalOcean (product 48), Hetzner Cloud (product 49), or GreatHosts (product 50) as future deployment infrastructure; validate PHP runtime, database connectivity, persistent storage, networking, backup, and secret-management requirements before selection.
- Add OpenTelemetry (product 99), Prometheus (product 100), Grafana (product 57), or Sentry (product 56) as future observability choices rather than prerequisites for the initial stack.
A reference architecture, not a tested deployment or guarantee of interoperability. Validate all external contracts, versions and prices before implementation.
Build this architecture with an AI agent
Turn this source-backed reference into a project-specific plan and code, including tests and implementation boundaries. It has not been execution-tested by Saitat.
View agent implementation prompt
Sources and review dates
Official pages are recorded with UTC retrieval timestamps. Retrieval confirms access to a page, not verification of every technical claim, price, or license.