# RabbitMQ Event Catalog

All async communication between services uses RabbitMQ. Events follow
strict naming: `{domain}.{entity}.{verb}.v{n}` (past tense).

## Exchanges

| Exchange                  | Type     | Purpose                              |
|---------------------------|----------|--------------------------------------|
| `flatrender.events`       | topic    | Domain events (fan-out by routing)   |
| `flatrender.render`       | direct   | Render job dispatch (per queue)      |
| `flatrender.notify`       | direct   | Notification dispatch (per channel)  |
| `flatrender.dlq`          | fanout   | Dead-letter queue                    |

## Common envelope

Every message body has this shape:

```json
{
  "event_id": "uuid",
  "event_type": "render.job.completed.v1",
  "event_time": "2026-05-27T10:15:00Z",
  "tenant_id": "uuid",
  "user_id": "uuid",
  "trace_id": "uuid",
  "correlation_id": "uuid",
  "producer": "render-orchestrator",
  "data": { ... }
}
```

## Headers (AMQP)

| Header              | Required | Notes                                          |
|---------------------|----------|------------------------------------------------|
| `content-type`      | yes      | `application/json`                             |
| `content-encoding`  | yes      | `utf-8`                                        |
| `x-event-type`      | yes      | Same as `event_type` (for routing convenience) |
| `x-tenant-id`       | yes      | For tenant-aware consumers                     |
| `x-trace-id`        | yes      | Distributed tracing                            |
| `x-retry-count`     | optional | Incremented on requeue                         |
| `x-max-retries`     | optional | Default 3                                      |

## Routing keys (topic exchange `flatrender.events`)

```
identity.user.registered.v1
identity.user.email_verified.v1
identity.user.banned.v1
identity.tenant.created.v1
identity.tenant.suspended.v1
identity.plan.activated.v1
identity.plan.expired.v1
identity.payment.succeeded.v1
identity.payment.failed.v1
identity.payment.refunded.v1
identity.api_key.created.v1
identity.api_key.revoked.v1

content.template.published.v1
content.template.unpublished.v1
content.font.installed.v1
content.svg_preview.generated.v1

studio.project.saved.v1
studio.project.deleted.v1

render.job.queued.v1
render.job.started.v1
render.job.progress.v1
render.job.completed.v1
render.job.failed.v1
render.job.cancelled.v1
render.snapshot.requested.v1
render.snapshot.ready.v1

node.online.v1
node.offline.v1
node.crashed.v1
node.heartbeat.v1
node.cache.updated.v1

file.uploaded.v1
file.processed.v1
file.deleted.v1
file.quota_warning.v1
file.quota_exceeded.v1
file.cleanup.scheduled.v1
file.cleanup.executed.v1

notification.created.v1
notification.delivered.v1
notification.failed.v1

tenant.usage.recorded.v1
tenant.webhook.fired.v1
tenant.webhook.failed.v1
```

## Render dispatch (direct exchange `flatrender.render`)

```
render.queue.snapshot
render.queue.vip
render.queue.paid
render.queue.preview
render.queue.mockup
render.queue.voiceover
```

Each queue has priority `x-max-priority: 10`. Job priority encoded in
message priority property.

## Notification dispatch (direct exchange `flatrender.notify`)

```
notify.channel.push
notify.channel.email
notify.channel.sms
notify.channel.telegram
notify.channel.webhook
```

## Dead-letter routing

Failed messages (after `x-max-retries` exhausted) are republished to
`flatrender.dlq` with original routing key preserved in
`x-original-routing-key` header.
