Skip to main content

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).
NamingValue
ObjectEmail
Resource type (JSON:API type)email
Collection / records rootemails
REST base/v1/emails
Entity classEmail
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

OperationMethod & pathStatus
List (nested)GET /v1/companies/{id}/emails · GET /v1/people/{id}/emails✅ Implemented
RetrieveGET /v1/emails/{id}✅ Implemented
Create (nested)POST /v1/companies/{id}/emails✅ Implemented
UpdatePATCH /v1/emails/{id}🟡 Planned
Delete (nested)DELETE /v1/companies/{id}/emails/{subId}✅ Implemented

Data model

Attributes

FieldTypeRequiredConstraintsAllowed valuesDescription
email_idstring, UUID, 🔒 system✅ Yesunique; generated by gen_random_uuid() on insertPublic immutable identifier for the email address. Used in all API and GraphQL responses; internal pk is never exposed.
emailstring✅ Yesmax 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_atstring (ISO 8601 datetime), 🔒 system✅ Yesset on insert via MikroORM onCreate lifecycle hook; never nullTimestamp at which the email address record was first created.
updated_atstring (ISO 8601 datetime), 🔒 system⚪ Noset on insert and on every update via MikroORM onCreate/onUpdate hooks; nullable in entity declarationTimestamp of the most recent mutation to this record.
deleted_atstring (ISO 8601 datetime) | null⚪ Nonullable; soft-delete sentinel — set to current timestamp by EmailRepository.delete(); all live queries must filter deleted_at IS NULLSoft-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

NameTypeRequiredDescription
workspaceto-one (workspace)⚪ NoOptional 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_emailsto-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_emailsto-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