YourEra Engineering Docs

Canvas-first microservice architecture. If you're onboarding, read this page top to bottom.

Canvas-first redesign (2026-04-21). YourEra's backend is organized around Canvas Medical FHIR as the medical-record database of record. Every service is a sidecar around it. One Canvas instance serves YourEra DTC and every GuideGLP B2B client via FHIR-native multi-tenancy. See Architecture Overview for the full system diagram and the Canvas-first principles doc for the doctrine.
Shipping status: Wave A complete. The foundation is in place. Five units have shipped:

What we are

YourEra is a prescription platform for GLP-1's and adjacent medications. Two product surfaces share one Canvas FHIR backend and one set of microservices:

  • YourEra DTC — consumer product at yourera.com. Patient completes intake, a licensed physician reviews in Canvas Medical, and a 503A compounding pharmacy ships the Rx. Modeled as FHIR Organization.kind='dtc'.
  • GuideGLP (B2B) — partners like Biologics, LCMC, Sleep Corner, and others. Each is a FHIR Organization.kind='b2b' inside the same Canvas instance. Their patients have Patient.managingOrganization pointing to their org. They get white-labeled patient portals, branded notifications, and tenant-scoped admin views — but share infrastructure.

Canvas is the medical-record database for everyone. Nothing clinical lives in a sidecar. Our services handle what Canvas doesn't model: identifiers, branding, custom domains, payments, shipping logistics, notification delivery, and operational sagas. Without SureScripts, we do our own Rx choreography through prescription-orchestrator. Read the Canvas-first principles doc for what can and cannot live in a sidecar.

How a prescription flows

Intake Gateway org resolved from host Canvas FHIR (shared, multi-org) intake-gateway writes Patient, Observation, Condition, Consent, Questionnaire... atomically on completion Provider approves in Canvas prescription_queue plugin · org-aware Webhook Receiver MedicationRequest.activated Prescription Orchestrator (saga) preflight · payment · pharmacy · shipping · notify Refill Scheduler materialized view · refill.due Payment Service Stripe bridge Pharmacy Router GMP primary · Strive fallback Shipping Service FedEx 2Day Express Notification Service email · SMS · campaigns Galleria (GMP) PRIMARY · PioneerRx backend Strive fallback pharmacy dispense callbacks → pharmacy-router writes Canvas MedicationDispense
  1. Intake. Patient reaches their org's intake host (e.g., intake.biologics.com). intake-gateway resolves the organization from the hostname via organization-service, creates an intake_sessions row, and captures the email as a lead via contact.captured (consumed by notification-service).
  2. Atomic Canvas write. On final submit, intake-gateway atomically writes Canvas resources via @yourera/canvas-client: Patient (with managingOrganization), Observation, Condition, AllergyIntolerance, QuestionnaireResponse, Consent, DocumentReference. In the same transaction, patient-service writes the patient_directory row + INSERT-only intake_snapshots.
  3. Provider review in Canvas. A Canvas Task is routed to eligible physicians (via physician-registry + the Canvas prescription_queue plugin, now org-aware). Provider reviews, approves, signs the Rx. Canvas emits MedicationRequest.status = active.
  4. Orchestrator saga. webhook-receiver ingests the Canvas webhook and emits canvas.medication_request.activated. prescription-orchestrator creates an rx_fill_sagas row and runs the state machine: preflight → payment (or skipped if prepaid plan) → pharmacy → shipping → notify.
  5. Pharmacy routing (live per fill). Orchestrator asks pharmacy-router for a route: Galleria (GMP) if licensed in the patient's state, else Strive. Routing is re-evaluated on every fill — a patient who went to Strive for fill #1 automatically routes to GMP for fill #2 if GMP adds a license in between. Pharmacy dispenses; router writes Canvas MedicationDispense on callback.
  6. Fulfillment + notification. shipping-service generates a FedEx 2Day Express label (fetching patient address live from Canvas, never cached). notification-service sends templated patient updates (shipped → out-for-delivery → delivered) using the org's verified sender identity.
  7. Refill cycle. refill-scheduler maintains a materialized view of upcoming refills fed by Canvas webhooks. On due date, it fires refill.due; orchestrator creates a new saga and runs the full pipeline again. Prepaid bundled plans (quarterly, 6-month) skip the charge stage — money was collected upfront.

Mental model: Canvas owns clinical truth; sidecars hold operational state

The one thing to internalize: Canvas FHIR is the medical-record database. Every clinical fact — demographics, vitals, conditions, allergies, prescriptions, dispenses, messages — lives in Canvas. Our services are sidecars that hold only what Canvas doesn't model.

The decision rule when designing anything new: Would a HIPAA auditor expect this data in an EMR? Then Canvas owns it. Would a Stripe auditor, FedEx tracking system, or CMS-style operational dashboard expect this data? Then a sidecar owns it.

Concrete consequences:

  • No caching Canvas data "just in case." If a read is hot, build a materialized view fed by webhooks (like refill-scheduler). Not an ad-hoc cache.
  • No clinical fields in sidecars. Shipping-service doesn't store patient names. Notification-service doesn't store Rx details. Everything references canvas_patient_id and fetches live.
  • Cross-service joins are forbidden. Every service has its own Postgres. Foreign keys don't cross service boundaries.
  • Bypassing the owner is forbidden. Need patient data? Call patient-service, which calls Canvas. The only service that can talk Canvas directly is the one whose domain it is.
  • No platform = 'yourera' | 'guideglp' discriminator. Tenants are FHIR Organizations. Organization.kind distinguishes DTC from B2B where branching is needed.

Gotchas before you touch anything

Read these before your first PR.
  • Production vs staging names are counterintuitive. yourera.com = prod (Bask → Canvas migration complete as of 2026-03-05). hisera.com = staging (Canvas was trialed on hisera first).
  • One Canvas, many orgs. YourEra DTC and every GuideGLP partner (Biologics, LCMC, Sleep Corner, etc.) share the same Canvas instance. Tenant isolation is enforced in our service layer via organization_id, not by Canvas.
  • Single-org-for-life. A patient belongs to exactly one Organization for their entire lifetime with us. organization_id is set once at patient_directory creation and never reassigned through normal flows.
  • Pharmacy Router is the single pharmacy entry point. Galleria (GMP) primary where licensed, Strive fallback elsewhere. Boothwyn was dropped. Routing is re-evaluated per fill — never pin a pharmacy to a patient.
  • canvas-pioneer-integration-service is retired. Its Canvas auth moved to @yourera/canvas-client (library). Canvas webhooks go to webhook-receiver. Stripe glue moved to payment-service. Patient records moved to Canvas. Don't add anything to this service.
  • HubSpot is gone. Contacts, campaigns, drip sequences, and opt-outs all live in notification-service now. Never write HubSpot sync code.
  • PioneerRx auth is SHA512 over UTF-16-LE. Not UTF-8. See pharmacy-router/src/adapters/pioneerrx.ts and the known-vector regression test. We've been bitten by this.

Environments & addresses

Domain Role Notes
yourera.com Production marketing + landing Post-Bask-migration (live as of 2026-03-05)
{slug}.portal.yourera.com + custom domains White-label patient portal per org Default + org-owned custom hostnames (e.g., patients.sleepcorner.com)
admin.hisera.com Admin Portal (superadmin + tenant-scoped) Canvas RxQueue, saga ops console, campaigns, refunds
intake.{org-domain} Intake sites, org-branded per B2B client Resolves to organization_id at the portal edge
hisera.com Staging (DTC + B2B, Canvas dev sandbox) Yes, staging is named differently from prod. Historical.
api.hisera.com Internal service HTTP endpoints ALB routes per service (path-based)
docs.hisera.com These docs

Where to go next

Depending on what you're working on:

Full service index

@yourera/canvas-client
Shared package, not a service. The single HTTP client every Canvas-touching sidecar imports. OAuth2, retry + rate-limit + timeout, FHIR primitives, 16 resource wrappers, webhook verifier. 1065 tests.
Live · A2
@yourera/crypto
Shared package, not a service. Sealed-envelope encryption for PHI-shaped JSONB columns. v1 uses AES-256-GCM with a versioned envelope; v2 is a KMS-backed upgrade path that will decrypt alongside v1. Ships with intake-gateway. 21 tests.
Live · A5
Organization Service
Tenants, branding, custom domains (DNS+TLS), portal config, notification config, admin users, admin memberships, physician × organization memberships. The backbone of multi-tenancy. 510 tests.
Live · A1
Patient Service
Directory-only. patient_directory + INSERT-only intake_snapshots. Two tables. Zero clinical data. Patient clinical truth lives in Canvas. 908 tests.
Live · A3
Webhook Receiver
Canvas FHIR bridge. Bidirectional. HMAC-authed ingress with verbatim forensic log; SQS-backed CanvasSyncWorker egress with idempotency-key ledger. 841 tests.
Live · A4
Intake Gateway
Public intake ingress. Host-resolves org, HMAC-signed __Host- cookie, encrypted answersSealed via @yourera/crypto, pre-Clerk email OTP, multi-step submit saga with partial-failure recovery, server-authoritative disqualifiers. 702 tests.
Live · A5
Prescription Orchestrator
Saga / state machine. Our SureScripts substitute. Coordinates preflight → payment → pharmacy → shipping → notification. Holds only saga state; no patient, payment, or pharmacy data.
Slimmed
Refill Scheduler
Event-driven materialized view of upcoming refills fed by Canvas webhooks. Fires refill.due; never writes Canvas directly. Replaces the old daily refill cron.
Planned
Pharmacy Router
Galleria (GMP, primary, PioneerRx backend) + Strive (fallback). Live routing re-evaluated per fill saga. Writes Canvas MedicationDispense on dispense callback. Boothwyn dropped.
Enhanced
Physician Registry
State licenses, NPI, routing rules. Coordinates with physician_org_memberships in organization-service so physicians can serve multiple orgs.
Live
Payment Service
Stripe bridge. Customers, PaymentIntents, subscriptions, refunds, disputes. Idempotent everywhere. Zero clinical data; statement descriptors generic per org.
Planned
Commerce Service
Orders as correlation keys joining Stripe PI + Canvas MR + shipment + coupon + saga. Catalog, SKUs, billing plans (monthly / prepaid quarterly / prepaid 6-month), coupons, business-level subscriptions.
Planned
Shipping Service
FedEx 2Day Express label + tracking. Patient address fetched live from Canvas at label time — never cached. Galleria print queue for pharmacy staff.
Enhanced
Notification Service
Delivery (SES + Twilio) + contacts (leads & patients) + campaigns (drips + scheduled + blasts) + preferences. Replaces HubSpot entirely. Per-org verified sender identity + template overrides.
Enhanced
Admin Portal
Tenant-scoped with RBAC. Superadmin, org_admin, org_user, support roles. Superadmin impersonate-org mode. Saga operations console. Campaign authoring. Full audit logging.
Enhanced
Patient Portal
White-labeled per org. Hostname resolution for custom domains. Clerk auth with org-scoped sessions. Live canvas-client reads; stateless.
Enhanced
Intake Forms
Onboarding forms for GLP-1, NAD+, Microdose. Per-org branding, Stripe payment via SetupIntent, ID verification, draft persistence via intake_sessions.
Live
E2E Testing Framework
Playwright-based test architecture. Smoke checks, API contracts, business flows, UI, full patient journeys. Tenant-isolation verification tests for multi-org scenarios.
Live
Integration Service
Retired under Canvas-first. Responsibilities redistributed: Canvas auth → canvas-client library, webhook handling → webhook-receiver, Stripe → payment-service, patient records → Canvas.
Retired
GuideGLP (standalone)
Retiring. GuideGLP partners are now Organizations in the shared Canvas instance, not a separate service. Their patients flow through the unified orchestrator + core services.
Retiring