Pipeline Architecture

Under Canvas-first, one unified microservice pipeline serves YourEra DTC and every GuideGLP B2B client. The backend services are identical; the differences are at the edges — hostname, branding, admin scope, sender identity — all driven by per-organization configuration.

One pipeline, many organizations

In the pre-Canvas-first world we had a "DTC pipeline" and a "GuideGLP pipeline." They shared some core services but diverged meaningfully — GuideGLP explicitly skipped Canvas, had its own orders table, its own physician tables, and its own orchestration paths. Every service had a source discriminator threading through it, and we maintained parallel implementations.

That split is gone. YourEra DTC and every B2B partner (Biologics, LCMC, Sleep Corner, and future clients) are now FHIR Organization resources inside the same Canvas instance. Every patient, every Rx fill, every shipment flows through the same services, regardless of which org owns the patient. The tenant discriminator moves from a service boundary to a data column: organization_id on every tenant-scoped row, backed by Patient.managingOrganization in Canvas.

What differs per tenant is all at the edges:

  • Hostname — which intake site / portal they land on
  • Branding — logo, colors, support copy, legal URLs
  • Sender identity — per-org SES-verified from email + Twilio number
  • Admin scope — tenant-scoped admin portal filtered by admin_org_memberships
  • Portal feature flags — messaging on/off, refill controls, appointment scheduling

None of those differences justify a separate service or a branching pipeline. The backend is the same — we just configure it per org.

Shared service flow (every fill, every tenant)

This is what happens internally for any prescription fill — whether it's a YourEra DTC patient or a Biologics patient — from intake completion through delivery.

Intake Gateway host → org resolution atomic Canvas write on submit Canvas FHIR (shared, multi-org) Patient.managingOrganization = Organization/{org_id} Provider reviews + approves in RxQueue MedicationRequest.status = active Webhook Receiver normalizes Canvas webhooks fans out on event bus event bus (EventBridge + SQS, outbox-driven, at-least-once) Prescription Orchestrator (saga) preflight → payment → pharmacy → shipping → notify our SureScripts substitute Refill Scheduler materialized view emits refill.due Payment Service Stripe bridge — charge, refund Pharmacy Router GMP primary · Strive fallback Shipping Service FedEx 2Day Express Notification Service per-org verified sender Commerce Service orders, subs, catalog, coupons GMP (primary) · Strive (fallback) external compounding pharmacies FedEx carrier API + tracking webhooks SES + Twilio per-org verified senders

Unified Canvas-first pipeline. Every Rx fill — DTC or any GuideGLP tenant — flows through exactly these services in exactly this order.

YourEra DTC patient journey

What a consumer patient actually experiences when they discover YourEra and complete a GLP-1 order. The backend services are as shown above; what's tenant-specific is the hostnames, branding, and sender identity.

1. Discovery yourera.com (marketing) → intake.yourera.com/start-online-visit/glp-1 2. Intake intake-gateway resolves host → Organization.kind='dtc', YourEra branding 3. Canvas Patient on submit: atomic write of Patient, Observation, QR, Consent 4. Commerce + SetupIntent commerce creates sub + order payment-service: Stripe SetupIntent 5. Provider review in Canvas YourEra physician sees Task in RxQueue (admin.hisera.com, Clerk SSO) 6. Orchestrator saga charges card per order.charge_method routes Rx to GMP or Strive 7. Pharmacy dispense GMP (or Strive) fills + packs webhook writes MedicationDispense 8. Label + shipment shipping fetches Patient.address live FedEx 2Day label → pharmacy prints 9. Patient notified notification-service sends email + SMS from care@yourera.com / YourEra SMS # 10. Patient portal access portal.yourera.com / dtc.portal.yourera.com Clerk email OTP → JWT w/ org_id 11. Live clinical reads canvas-client fetches live weight, Rx status, provider messages 12. Refill cycle refill-scheduler fires refill.due new saga → monthly charge (or prepaid skip) DTC-specific surface area Marketing: yourera.com · Intake: intake.yourera.com/start-online-visit/glp-1 Portal: portal.yourera.com · Admin: admin.hisera.com (Clerk SSO for YourEra staff)

YourEra DTC end-to-end journey. Same services as above, branded to YourEra. Organization.kind='dtc'.

GuideGLP B2B patient journey (Biologics example)

Same backend pipeline, different edges. This example uses Biologics; the same pattern applies to LCMC, Sleep Corner, and every future B2B partner. Each is an Organization.kind='b2b' in the shared Canvas.

1. Discovery biologicshealth.com marketing → intake.biologics.com (custom domain) 2. Intake (Biologics-branded) intake-gateway resolves custom domain → Organization.kind='b2b', Biologics branding 3. Canvas Patient same atomic write as DTC, but managingOrganization = Biologics 4. Commerce + SetupIntent Biologics-specific billing_plan (per-org pricing supported) 5. Provider review Task routed by physician_org_memberships to Biologics-affiliated physicians 6. Orchestrator saga identical to DTC flow scoped by organization_id 7. Pharmacy dispense same GMP/Strive routing (pharmacy-router re-evaluates per fill) 8. Label + shipment same FedEx 2Day Express shipping-service is tenant-agnostic 9. Patient notified from care@biologics.com Biologics-branded email template 10. Patient portal patients.biologics.com (custom domain) white-labeled, Biologics theme 11. Live clinical reads same canvas-client, same Canvas scoped to patient's canvas_patient_id 12. Refill cycle identical to DTC flow shared refill-scheduler service Biologics-specific surface area (per-org config) Intake: intake.biologics.com · Portal: patients.biologics.com · Sender: care@biologics.com Admin: admin.biologics.com (or shared admin.hisera.com) · B2B admins auth via email OTP + JWT, scoped by admin_org_memberships

Biologics end-to-end journey. Steps 4-8 and 12 use the exact same service code as the DTC journey; the differences are at the edges, all driven by organization_branding, organization_custom_domains, and organization_notification_config.

Same vs different: DTC and GuideGLP side-by-side

Green cells = identical backend behavior. Amber cells = configured per org. There are zero rows where the service logic actually branches on whether a tenant is DTC or B2B.

Surface YourEra DTC GuideGLP B2B (e.g., Biologics)
Marketing site yourera.com biologicshealth.com (partner's own)
Intake site intake.yourera.com intake.biologics.com (custom domain)
Intake form code Same intake-gateway, same Canvas-atomic-write logic Same intake-gateway, same Canvas-atomic-write logic
Organization.kind dtc b2b
Canvas instance Same shared instance Same shared instance
Patient.managingOrganization YourEra DTC org Biologics org
Branding YourEra logo, colors, copy Biologics logo, colors, copy (organization_branding)
Provider review (RxQueue) Canvas prescription_queue plugin Canvas prescription_queue plugin (org-aware routing)
Physician pool YourEra DTC physicians (physician_org_memberships) Biologics-affiliated physicians (may overlap with DTC)
Commerce service Same; orders/subs carry organization_id Same; orders/subs carry organization_id
Billing plan pricing DTC plans (billing_plans.organization_id = DTC) Biologics plans (may override DTC base prices)
Payment service Same Stripe customer flow Same Stripe customer flow
Statement descriptor on card YOURERA CARE BIOLOGICS HEALTH
Orchestrator saga Identical state machine Identical state machine
Pharmacy routing GMP primary, Strive fallback; re-eval per fill GMP primary, Strive fallback; re-eval per fill
Shipping carrier FedEx 2Day Express FedEx 2Day Express
Notification sender care@yourera.com (SES verified) care@biologics.com (SES verified per org)
Notification templates Base templates Base templates (with optional per-org overrides)
Patient portal host portal.yourera.com patients.biologics.com (custom domain)
Patient portal code Same Next.js app; theme injected at hostname resolution Same Next.js app; theme injected at hostname resolution
Patient auth Clerk email OTP, JWT with organization_id claim Clerk email OTP, JWT with organization_id claim
Admin portal host admin.hisera.com admin.biologics.com (optional) or shared admin.hisera.com
Admin auth provider Clerk (YourEra Workspace SSO) OTP + JWT (external partners)
Admin scope Superadmin full; staff scoped to DTC org if not superadmin org_admin scoped to Biologics via admin_org_memberships
Backend DB schema Same schemas across all services Same schemas across all services
Refill cadence Same refill-scheduler Same refill-scheduler (per-org reminder_lead_days if configured)

Reuse matrix: who uses which service

Under Canvas-first, this is trivially "everyone uses everything." The matrix is here for contrast with the old architecture where GuideGLP explicitly skipped Canvas + prescription-orchestrator.

Service YourEra DTC GuideGLP B2B (any partner)
organization-service
patient-service (directory)
intake-gateway
webhook-receiver
Canvas FHIR (shared)
prescription-orchestrator
refill-scheduler-service
pharmacy-router
physician-registry
payment-service
commerce-service
shipping-service
notification-service
admin-portal
patient-portal

Compare to the old pre-Canvas-first architecture:

  • GuideGLP skipped Canvas entirely (patients + prescriptions stored in a separate guide-glp DB)
  • Some reseller / API-only GuideGLP partners bypassed the orchestrator and called pharmacy-router directly
  • Physician tables were duplicated (guide_physicians vs YourEra's internal physician records)
  • Two parallel order models (guide_orders vs canvas-pioneer's order rows)

All that collapses to the unified matrix above.

Per-org configuration drives the differences

All tenant-specific behavior is encoded in organization-service tables. Adding a new B2B partner is a data operation: insert rows into these tables, provision Canvas Organization, done.

Table (in organization-service) What it controls
organizationsIdentity, slug, kind (dtc|b2b), status, Canvas Organization id
organization_brandingLogo, colors, support contact, legal URLs, email footer HTML
organization_portal_configFeature flags: messaging on/off, refill controls, appointment scheduling, onboarding redirect
organization_notification_configPer-org SES-verified from email, Twilio number, per-template overrides
organization_custom_domainsClient-owned domains for patient portal / admin portal / intake, with TLS lifecycle
admin_users + admin_org_membershipsWho can access this org's admin surfaces, at what role (admin / viewer)
physician_org_membershipsWhich physicians serve this org (mirrored to Canvas PractitionerRole)

See Architecture Overview for the full organization-service table definitions, and the organization-domain.md spec in the main yourEra repo for the complete schema.

What changed from the old pipeline architecture

Pre-Canvas-first, this page described multiple pipeline archetypes:

  • DTC (vertical) — YourEra-owned intake + physicians + fulfillment, Canvas-based
  • GuideGLP vertical archetype — we owned intake + physicians + fulfillment for the partner, but skipped Canvas
  • GuideGLP reseller / API-only — partners brought their own intake + physicians, called pharmacy-router directly, bypassed our orchestrator

Under Canvas-first, the archetypes collapse to exactly one. Every patient lives in the shared Canvas; every fill runs through the shared orchestrator; every fulfillment uses pharmacy-router + shipping-service. Partner variation happens at config time (branding, domain, physician pool), not at service boundaries. This drops multiple code paths, multiple DBs, multiple schemas from the stack.

If a future partner needs something truly pipeline-shaped that the current model can't express (e.g., a partner wants to run their own Canvas), we'd revisit. But as of today every prospective partner has been happy with perceived isolation — branded portals + scoped admin views + their own sender identity — which this architecture delivers with zero pipeline forking.