Documentation Index
Fetch the complete documentation index at: https://docs.wellapp.ai/llms.txt
Use this file to discover all available pages before exploring further.
EmailEvent is an append-only event log for the outbound email lifecycle. One row is written per observed state transition — both internal (MailService called the provider) and provider-side (delivery and engagement webhooks from SendGrid). Multiple rows per logical send are correlated via message_id, a UUID generated at send time and echoed back by the provider in every webhook. The table is provider-agnostic: no column names SendGrid directly, so future provider swaps require no schema change. The entity is associated to a Workspace via a nullable SET NULL foreign key so bounce/suppression history outlives workspace deletion.
| Naming | Value |
|---|
| Object | EmailEvent |
Resource type (JSON:API type) | email_event |
| Collection / records root | — (not a records root) |
| REST base | /v1/email-events |
| Entity class | Migration20260507094935_email_events, Migration20260507120000_email_events_workspace_set_null, Migration20260507121000_email_events_drop_created_at |
Internal object. Not currently exposed on the public REST API. The operations below describe the intended contract.
API operations
| Operation | Method & path | Status |
|---|
| List | GET /v1/email-events | 🟡 Planned |
| Retrieve | GET /v1/email-events/{id} | 🟡 Planned |
| Create | POST /v1/email-events | 🟡 Planned |
| Update | PATCH /v1/email-events/{id} | 🟡 Planned |
| Delete | DELETE /v1/email-events/{id} | 🟡 Planned |
Data model
Attributes
| Field | Type | Required | Constraints | Allowed values | Description |
|---|
| email_event_id | uuid (🔒 system) | ✅ Yes | unique | — | Public UUID for this event row. Generated at insert by gen_random_uuid(); never set by callers. |
| message_id | uuid | ✅ Yes | — | — | Logical send correlator. Assigned by MailService at send time and embedded in provider customArgs so every provider webhook event for the same logical send echoes the same UUID. Ties the internal ‘sent’ row to all downstream provider lifecycle rows. |
| email_type | text | ⚪ No | — | welcome, sync_complete, weekly_digest, document_forward, invitation, member_welcome | Email category from MailService. Stored as TEXT (no DB CHECK) so adding a new template requires only a new entry in EMAIL_TYPES, no migration. NULL for provider webhook events that arrive before the corresponding ‘sent’ row is correlated. |
| recipient_email | text | ✅ Yes | length <= 254 (CHECK email_events_recipient_length) | — | Email address of the recipient. Denormalised on every row so suppression queries (bounce list, re-invite guard) keep working even when workspace is NULL after workspace deletion. |
| status | text | ✅ Yes | CHECK email_events_status_values: one of the 13 allowed values | sent, failed, processed, dropped, deferred, delivered, bounced, opened, clicked, spam_reported, unsubscribed, group_unsubscribed, group_resubscribed | Lifecycle status of the email at the moment this event row was written. Two categories: internal (sent, failed — written by MailService) and provider (all others — written from provider webhooks). A DB CHECK constraint enforces the vocabulary to prevent silent typo inserts. |
| occurred_at | timestamptz | ✅ Yes | — | — | When the event happened. Provider timestamp for webhook events; wall clock for internal events. Paired with received_at to compute webhook delivery latency. |
| received_at | timestamptz (🔒 system) | ✅ Yes | default now() | — | When this row was written to the database (wall clock, set by onCreate hook). Paired with occurred_at; the difference is webhook delivery latency. Defaults to now() at insert. |
| provider | text | ⚪ No | — | sendgrid | Active email provider for this event. NULL for internal-only events that never reached a provider (status=failed before provider call). The only current value is ‘sendgrid’; the vocabulary lives in EMAIL_PROVIDERS. |
| provider_message_id | text | ⚪ No | — | — | Provider’s per-message identifier. Useful for support tickets and cross-referencing against the provider’s own dashboards. NULL for internal events. |
| provider_event_id | text | ⚪ No | partial unique index idx_email_events_provider_event_id_unique on (provider, provider_event_id) WHERE provider_event_id IS NOT NULL | — | Provider’s per-event idempotency key. A partial unique index on (provider, provider_event_id) WHERE provider_event_id IS NOT NULL prevents duplicate webhook inserts. Internal events have this as NULL and are exempt from the constraint. |
Relationships
| Name | Type | Required | Description |
|---|
| workspace | to-one (Workspace) | No | The workspace that owns this email event. Nullable: SET NULL on workspace deletion so bounce/spam/unsubscribe history outlives the workspace and prevents re-emailing a previously hard-bounced recipient after re-invitation. Rows with workspace=NULL are naturally hidden from workspace-scoped Hasura RLS queries. |
System-computed
- email_event_id — gen_random_uuid() default at insert, never caller-supplied
- received_at — set by MikroORM onCreate hook to new Date(); represents the DB write timestamp
- workspace FK on delete SET NULL — workspace deletion orphans rows rather than cascading deletes, preserving suppression history
- Partial unique index on (provider, provider_event_id) WHERE provider_event_id IS NOT NULL — idempotency guard for provider webhook retries; index maintained by Postgres, not application code
- No created_at / updated_at / deleted_at — this entity is append-only (no soft-delete, no update semantics); created_at was dropped in Migration20260507121000 as redundant with received_at
Example
{
"data": {
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"type": "email_event",
"attributes": {
"email_event_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"message_id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"email_type": "invitation",
"recipient_email": "alice@example.com",
"status": "delivered",
"occurred_at": "2026-05-07T14:32:10.000Z",
"received_at": "2026-05-07T14:32:11.243Z",
"provider": "sendgrid",
"provider_message_id": "sg-msg-00aabbcc",
"provider_event_id": "sg-evt-112233445566"
},
"relationships": {
"workspace": {
"data": { "id": "9f3e4a71-1234-5678-abcd-000000000001", "type": "workspace" }
}
}
}
}
Source: apps/api/src/database/entities/EmailEvent.ts — migrations: apps/api/src/database/migrations/Migration20260507094935_email_events.ts, Migration20260507120000_email_events_workspace_set_null.ts, Migration20260507121000_email_events_drop_created_at.ts · domain: platform · tier: Activity