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.
The email resource is a canonically-stored email address shared across the financial graph. It functions as an atomic contact channel rather than a top-level business entity: a single Email row holds exactly one RFC 5321 address and is linked to companies via the CompanyEmail pivot and to people via the PersonEmail pivot — both pivots carry metadata (is_primary, is_verify, label). The record is workspace-scoped through an optional @ManyToOne to Workspace, and is deduplicated per workspace via a find-or-create pattern keyed on (email, workspace_pk, deleted_at IS NULL).
| Naming | Value |
|---|
| Object | Email |
Resource type (JSON:API type) | email |
| Collection / records root | emails |
| REST base | /v1/emails |
| Entity class | Email |
Read access today: this object is readable via the universal POST /v1/records/query endpoint with root: "emails". A dedicated GET /v1/emails endpoint is Planned.
API operations
| Operation | Method & path | Status |
|---|
| List (nested) | GET /v1/companies/{id}/emails · GET /v1/people/{id}/emails | ✅ Implemented |
| Retrieve | GET /v1/emails/{id} | ✅ Implemented |
| Create (nested) | POST /v1/companies/{id}/emails | ✅ Implemented |
| Update | PATCH /v1/emails/{id} | 🟡 Planned |
| Delete (nested) | DELETE /v1/companies/{id}/emails/{subId} | ✅ Implemented |
Data model
Attributes
| Field | Type | Required | Constraints | Allowed values | Description |
|---|
| email_id | string, UUID, 🔒 system | ✅ Yes | unique; generated by gen_random_uuid() on insert | — | Public immutable identifier for the email address. Used in all API and GraphQL responses; internal pk is never exposed. |
| email | string | ✅ Yes | max 320 characters (RFC 5321: 64 local-part + 1 @ + 255 domain); no uniqueness constraint at table level — uniqueness is enforced per-workspace by the find-or-create query scoped on (email, workspace_pk, deleted_at IS NULL) | — | The normalized email address string, stored trimmed and lower-cased by the repository. Widened from varchar(50) to varchar(320) in migration 20260427 to handle long modern addresses. |
| created_at | string (ISO 8601 datetime), 🔒 system | ✅ Yes | set on insert via MikroORM onCreate lifecycle hook; never null | — | Timestamp at which the email address record was first created. |
| updated_at | string (ISO 8601 datetime), 🔒 system | ⚪ No | set on insert and on every update via MikroORM onCreate/onUpdate hooks; nullable in entity declaration | — | Timestamp of the most recent mutation to this record. |
| deleted_at | string (ISO 8601 datetime) | null | ⚪ No | nullable; soft-delete sentinel — set to current timestamp by EmailRepository.delete(); all live queries must filter deleted_at IS NULL | — | Soft-delete timestamp. When non-null the record is considered logically deleted and is excluded from Hasura user-role queries and from the find-or-create deduplication key. |
Relationships
| Name | Type | Required | Description |
|---|
| workspace | to-one (workspace) | ⚪ No | Optional link to the Workspace that owns this email address. Nullable because emails imported from global enrichment sources (or created before the workspace-scope requirement was introduced) may not carry a workspace reference. When present, it forms part of the uniqueness key for find-or-create deduplication. Targets core_api.workspaces. |
| company_emails | to-many (company_email) | — | Pivot records linking this email address to one or more Company rows. Each CompanyEmail carries is_primary, is_verify, and label metadata. A partial unique index on company_emails (company_pk) WHERE deleted_at IS NULL AND is_primary IS TRUE enforces at most one primary per company. Targets core_api.company_emails. |
| person_emails | to-many (person_email) | — | Pivot records linking this email address to one or more People rows. Each PersonEmail carries is_primary, is_verify, and label (enum: work / personal / other) metadata. Partial unique index uniq_person_emails_primary_person enforces at most one primary per person. Targets core_api.person_emails. |
System-computed
- email_id is generated by gen_random_uuid() at the database default level and also explicitly set in EmailRepository.findOrCreate() via Node.js randomUUID() to ensure the value is available before flush.
- created_at is set on insert by MikroORM onCreate: () => new Date().
- updated_at is set on both insert and update by MikroORM onCreate/onUpdate lifecycle hooks.
- deleted_at is null on creation; set to the current timestamp by EmailRepository.delete() as a soft-delete operation. Live queries must always filter deleted_at: null.
- Find-or-create deduplication: EmailRepository.findOrCreate() normalizes the input address via .trim().toLowerCase(), then queries (email, workspace, deleted_at: null). When a workspace is not provided the scope falls back to (email, deleted_at: null) globally. A new Email row is only created when no matching live record is found.
- Composite views: the
emails records root exposes two composites — composite_companies_list (relation_list of company_id + company.name, via company_emails reverse) and composite_people_list (relation_list of person_id + full_name, via person_emails reverse). These are defined in composites.yml and reconstructed at query time.
- Hasura RLS for the user role filters deleted_at IS NULL and resolves workspace tenancy indirectly: an email is visible if any linked company_email or person_email traces to a workspace matching X-Hasura-Workspace-Id (direct or parent-workspace cascade via workspace_group).
Example
{
"data": {
"type": "email",
"id": "b3f2a1c4-9d7e-4f01-8a2b-3c5d6e7f8a9b",
"attributes": {
"email": "sophie.martin@qonto.com",
"created_at": "2025-11-14T09:32:11.000Z",
"updated_at": "2025-11-14T09:32:11.000Z",
"deleted_at": null
},
"relationships": {
"workspace": {
"data": { "type": "workspace", "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890" }
},
"company_emails": {
"data": [
{ "type": "company_email", "id": "d4e5f6a7-b8c9-4d0e-1f2a-3b4c5d6e7f8a" }
]
},
"person_emails": {
"data": []
}
}
}
}
Source: apps/api/src/database/entities/Email.ts · domain: financial-graph · tier: Supporting