Skip to main content
The relationships module (@voyant-travel/relationships) owns the canonical identity layer for Voyant. Before a sales pursuit, a program, or a booking can exist, there has to be someone (or some company) it belongs to. Relationships is where those identities live: the people an operator knows, the organizations they buy and sell through, the ways to reach them, and the history of every interaction. It is deliberately the CRM core and nothing more. It does not model deals, commitments, or money. Other modules reference its records by plain id and never import its schema, which keeps identity decoupled from the pipeline and commitment chain that sit on top of it. When the quotes module stores a personId, or a booking snapshots a personId plus organizationId, the truth behind those ids is owned here.

Key concepts

Person
canonical CRM identity
An individual contact known to the operator, the canonical CRM identity record. Avoid “customer”, “client”, or “contact” as entity names; the buyer on a booking is captured as a personId snapshot, not a first-class customer record. See Person.
Organization
legal entity
A company or legal entity that represents a buyer, supplier, agency, or other counterparty. A Person may belong to zero or more Organizations, and both can appear on quotes, programs, and bookings. See Organization.
Contact Point
way to reach an identity
An email, phone, or website attached to a Person or Organization. Contact points are normalized (a normalizedValue column) so person search can match across formatting variations. See Contact Point.
Address
postal location
A postal address attached to a Person or Organization. See Address.
Participant
role on a transaction
A role-bearer on a quote, quote version, booking, program, or booking item (traveler, booker, decision-maker, finance). Participant is the broad role-on-a-transaction concept; a Traveler is the narrower person-who-actually-travels. The Participant role itself is stamped on the owning module’s records, while the identity it points at is a Person here. See Participant.
Activity
logged interaction
A logged interaction (call, email, meeting, task, follow-up) on a Person or a Quote. Activities carry links and participants so a single logged touch can attach to several records at once. See Activity.
Segment
targeting list
A named list of People or Organizations grouped by criteria, used for targeting or bulk action. See Segment.
Person document
KMS-encrypted PII record
A passport, ID card, or similar identity document attached to a Person. Document numbers and other sensitive fields are encrypted through a KMS provider, and reads decrypt on demand. A booking traveler can pre-fill a travel snapshot from a person document so PII is captured once and reused.
Customer signal
behavioral event
A captured signal about a Person (kind, source, status) that downstream targeting, scoring, or automation can act on. Signals are emitted as events through the module’s ./events entry.

What it owns

Relationships owns the identity graph and the runtime that protects its PII.
  • People and organizations (./schema), with notes, relationships between people, and per-entity custom fields.
  • Contact points and addresses, attached polymorphically to either a Person or an Organization, with normalized values for search.
  • Activities (activities, activity_links, activity_participants) and the communication log, so interactions can fan out across multiple records.
  • Segments (segments, segment_members) for targeting and bulk action.
  • Person documents and person payment methods, with KMS-backed encryption for the sensitive fields.
  • Customer signals and their event taxonomy, emitted through @voyant-travel/relationships/events.
  • The PII route runtime (./route-runtime): a configurable factory that injects a KMS resolver so admin routes can decrypt person documents on demand. Voyant Cloud wires its Vault resolver in here.
It does not own deals, pipelines, commitments, money, or travel state. A Person never carries a balance or a booking; those belong to quotes, bookings, and the finance module, which reference identities by id.

Working with it

Mount the module, optionally with a custom KMS resolver for person-document decryption.
import { createApp } from "@voyant-travel/hono";
import { createRelationshipsHonoModule } from "@voyant-travel/relationships";

const app = createApp({
  modules: [
    createRelationshipsHonoModule({
      // Supply a KMS resolver so admin PII routes can decrypt person
      // documents on demand. Defaults are used when omitted.
      resolveKmsProvider: (bindings) => buildVaultKmsProvider(bindings),
    }),
  ],
});
The service methods take a Drizzle database handle as their first argument, which keeps them transaction-friendly and free of hidden global state.
import { relationshipsService } from "@voyant-travel/relationships";

// Create a Person, then attach a contact point and an address.
const person = await relationshipsService.createPerson(db, {
  firstName: "Amara",
  lastName: "Henderson",
});

await relationshipsService.createContactMethod(db, "person", person.id, {
  kind: "email",
  value: "amara@example.com",
});

await relationshipsService.createAddress(db, "person", person.id, {
  line1: "12 Riverside Walk",
  city: "Bristol",
  countryCode: "GB",
});
Search resolves across name columns and normalized contact points, so a single query string matches a person by name, email, or phone.
const results = await relationshipsService.listPeople(db, {
  search: "henderson",
});
Person documents flow through the PII-aware service. Sensitive fields are encrypted at rest, and the same snapshot can later seed a booking traveler so the operator captures passport details once.
import { relationshipsService } from "@voyant-travel/relationships";

await relationshipsService.createPersonDocument(db, person.id, {
  documentType: "passport",
  documentNumber: "X1234567",
  documentExpiry: "2031-04-30",
  documentIssuingCountry: "GB",
});
Reads and writes of person-document fields are governed by the KMS resolver wired into the route runtime. Decryption happens on demand at the admin route boundary, never eagerly across a list.
  • quotes references People and Organizations by plain id. A Quote belongs to one Person and/or Organization but never imports relationship schema.
  • bookings snapshots personId plus organizationId as client fields on each Booking, and can pre-fill a booking traveler’s travel snapshot from a Person document.
  • trips validates traveler parties against the identities that own the component bookings it groups.
  • The finance and legal modules target an Organization or Person when they issue invoices, policies, and policy acceptances.

React package

@voyant-travel/relationships-react provides the client tier: TanStack Query hooks (usePeople, and the organization, activity, document, and signal equivalents), query keys, the VoyantProvider, and reusable UI under @voyant-travel/relationships-react/ui (PersonCard, PersonDialog, PersonList). Admin route contributions live under @voyant-travel/relationships-react/admin. Styled components require the optional @voyant-travel/ui peer. Quote panels surface on person and organization detail pages as extension slots, but the quote lifecycle hooks themselves ship from @voyant-travel/quotes-react.

Next steps

Quotes

The sales pipeline that turns a Person into a tracked pursuit and a proposal.

Bookings

The durable commitment that snapshots the buyer and carries the travelers.

Trips

Trip envelopes that group component bookings into one customer itinerary.

Glossary

The shared vocabulary: Person, Organization, Contact Point, Participant, and more.