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
fromemail + 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.
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.
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.
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-glpDB) - Some reseller / API-only GuideGLP partners bypassed the orchestrator and called pharmacy-router directly
- Physician tables were duplicated (
guide_physiciansvs YourEra's internal physician records) - Two parallel order models (
guide_ordersvs 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 |
|---|---|
| organizations | Identity, slug, kind (dtc|b2b), status, Canvas Organization id |
| organization_branding | Logo, colors, support contact, legal URLs, email footer HTML |
| organization_portal_config | Feature flags: messaging on/off, refill controls, appointment scheduling, onboarding redirect |
| organization_notification_config | Per-org SES-verified from email, Twilio number, per-template overrides |
| organization_custom_domains | Client-owned domains for patient portal / admin portal / intake, with TLS lifecycle |
| admin_users + admin_org_memberships | Who can access this org's admin surfaces, at what role (admin / viewer) |
| physician_org_memberships | Which 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.