← Tenancy

TENANCY FEATURE

Queue Tenant Propagation

Tenant context travels with queued jobs — _tenant key injected automatically.

Feature Guide

A quick orientation block that answers the essential questions: what this feature does, how it works, why it matters, and the key concepts behind it.

What this does

Queued jobs keep tenant context attached so background work stays scoped after the HTTP request is gone.

How it works

The serializer wraps the message with a tenant envelope, and the worker restores that context before executing the job.

Why it matters

Without this, multi-tenant background processing quietly becomes dangerous.

Tenant context travels with queued jobs — no manual propagation.

Producer (request context)

// Dispatch while tenant = 'acme'
$serializer->wrap([
    'action' => 'generate_report',
    'product_id' => 42,
]);
// ↓ _tenant injected automatically
→ queue →

Serialized payload

{
    "action": "generate_report",
    "product_id": 42,
    "_tenant": {
        "org": "acme",
        "locale": "en",
        "theme": "acme-theme",
        "env": "production"
    }
}
→ worker →

Worker side

1
Worker receives message
Queue consumer dequeues job payload
2
TenantAwareJobSerializer::unwrap()
Extracts _tenant key, strips it from clean payload
3
TenantContext::set($context)
Context restored — ORM, templates, queues all scoped
4
Handler runs with tenant scope
Queries hit tenant_id = 'acme' automatically

Wrap and unwrap API

$serializer = new TenantAwareJobSerializer();

// Producer:
$payload = $serializer->wrap(['action' => 'generate_report', 'product_id' => 42]);
// → ['action' => ..., '_tenant' => ['org' => 'acme', 'locale' => 'en', ...]]

// Worker:
[$cleanPayload, $tenantContext] = $serializer->unwrap($queueMessage);
TenantContext::set($tenantContext);
// ORM, templates, everything scoped to 'acme' now

Design decision: the default tenant (no active context) is NOT propagated — only explicit tenant contexts travel.

Why this matters for SaaS

Without tenant propagation, a queued job runs in the wrong scope — a report job for Acme reads Globex's data. Every job needs manual tenant_id plumbing.

With TenantAwareJobSerializer, tenant context is an invisible envelope on every job. Workers restore it transparently. Zero plumbing in application code.