Nobody could say where the kit was. Now the portal says it before anyone asks.
An at-home diagnostics platform ran a customer portal, Salesforce and a third-party logistics system that shared nothing. Staff created CRM records by hand, re-entered support actions into the portal, and opened a logistics dashboard to answer where a kit had got to. We built eight MuleSoft applications that keep all three in step.
- Client
- An at-home diagnostics platform
- Sector
- Digital Health
- Platforms
- MuleSoft · Salesforce · Logistics
- Engagement
- 6-month engagement
- Systems joined
- A health portal, Salesforce, and a third-party logistics API.
- Study
- 05 of five
One test kit, before and after
Struck through: the way it used to workWhy it stayed that way.
Four constraintsThe company's stack had grown around three independent systems. New customer registrations in the portal didn't automatically appear in Salesforce — support staff created them manually. When Salesforce agents flagged a case for a new kit, a blood draw reschedule, or a cancellation, someone had to translate that action back into the portal. Kit shipment status lived only in the logistics platform's dashboard; customers and support staff asking "where's my kit?" got no answer from either the portal or Salesforce.
- 01
Customer registrations in the portal required manual Account and Case creation in Salesforce. Every new member was an entry that someone created twice.
- 02
Case management was one-directional. Salesforce agents could log requests — new kit, blood draw reschedule, cancellation, telehealth follow-up — but those requests did not reach the portal automatically. A human had to transfer each one.
- 03
Shipment tracking lived in the logistics platform. Neither the portal nor Salesforce could show customers their kit status, tracking number, or estimated delivery date without a separate dashboard login.
- 04
Journey data in the portal and member data in Salesforce drifted apart over time. No scheduled sync, no external ID linkage, no single source of truth for member state.
What we ruled out first.
Four candidates, one of them chosenThe three systems involved — a proprietary health portal, Salesforce, and a third-party logistics API — have no native integration pathway. The portal's API uses API key authentication (X-Client-ID / X-Client-Secret) and exposes custom resources (journeys, orders, cases) that no off-the-shelf connector maps. The logistics platform uses Bearer token auth and returns structured shipment data with custom fields (kit_ids, tracking_status, carrier_code, service_code). Salesforce Person Accounts, custom fields (Portal_ID__c, Journey_ID__c, Integration_Timestamp__c), and the Member RecordType are all organisation-specific. No integration tool can connect these three systems without custom mapping — which is the integration, not a shortcut to it.
DoesConnecting popular SaaS apps with standard triggers and actions
Rules it outNo support for custom API auth patterns; no Object Store for watermarks; no batch operations
DoesPre-built connectors for popular business apps
Rules it outSame connector gap as Zapier for a custom portal API; no fine control over Salesforce Bulk API v2 behaviour
DoesDirect Salesforce → external API calls
Rules it outSalesforce-only; cannot orchestrate Portal → Salesforce flows; tight coupling; no observability across systems
DoesAPI-led architecture, DataWeave, Object Store, CloudHub
Why it wonHigher initial investment; right choice for multi-system orchestration with durable state and bidirectional flows
What we built instead.
Five flows across three systemsEight MuleSoft applications — three System APIs, four Process APIs, and one batch job — created a complete integration layer across the health portal, Salesforce, and the logistics platform. The architecture follows API-led connectivity: each system API wraps one external system behind a stable interface, and each process API orchestrates business logic without touching external systems directly.
Registration creates the Salesforce record
Portal → SalesforceWhen a new member registers on the health portal, the dh-customer-papi process API receives the registration payload and creates a Salesforce Person Account under the "Member" RecordType — splitting the full name, mapping email, phone, gender, Portal_ID__c as external ID, subscription ID, and start date. If the registration includes a Journey ID, a linked Case is created simultaneously with Journey_ID__c as the external identifier. Both Salesforce record IDs are returned to the caller.
Journeys sync to Salesforce on a schedule
Portal → SalesforceThe dh-journey-sync-papi scheduler fetches pending journeys from the portal (using portal user ID and journey ID as identifiers), queries Salesforce for existing Member Accounts by Portal_ID__c, and runs a RecordType filter to prevent accidental updates to non-Member records. Valid accounts are upserted with current journey data; Cases are upserted on Journey_ID__c. Portal IDs are zipped with Salesforce Account IDs after each batch to maintain the cross-system ID map.
An agent flag becomes a portal request
Salesforce → PortalSalesforce support agents flag cases using four boolean fields: New_Kit_Requested__c, New_Blood_Draw_Requested__c, Cancellation_Requested__c, New_Telehealth_Requested__c. The dh-case-sync-papi scheduler polls for cases where Integration_Timestamp__c exceeds the last-run timestamp (stored in Anypoint Object Store), transforms each flagged case into a structured request type ("new_kit", "reschedule_blood_draw", "cancellation", "follow_up"), and sends a bulk PATCH to the portal's journeys endpoint — up to 100 requests per batch. The Object Store watermark is updated after each successful run.
A kit order becomes a shipment
Portal → LogisticsWhen a diagnostic kit order is created in the portal, dh-order-to-shipment-papi POSTs it to the logistics platform via Bearer token authentication — creating the outbound shipment record in the logistics system.
Tracking comes back every five minutes
Logistics → PortalThe dh-order-status-sync-batch job fetches pending orders from the portal every five minutes, queries the logistics platform for each order's status and shipment details — tracking number, carrier code, service code, estimated delivery date, delivery timestamp, kit IDs — and PATCHes the order back to the portal. If an order has status updates but no shipments yet, the status is applied without shipment fields; once shipments exist, full tracking data is included.
Integration PlatformMuleSoft Anypoint Platform 4.x (8 applications)
DeploymentCloudHub
Salesforce ConnectivitySalesforce Connector — Bulk API v2, SOQL queries, upsert/create/update
Salesforce Data ModelPerson Accounts (Member RecordType), Cases (Journey_ID__c), Portal_ID__c external IDs, Integration_Timestamp__c
Health Portal ConnectivityHTTP (X-Client-ID / X-Client-Secret), Secure Properties
Logistics ConnectivityHTTP (Bearer token), SLP shipments/orders API
TransformationDataWeave 2.0
State ManagementAnypoint Object Store v2 (case sync watermark on Integration_Timestamp__c)
SecuritySecure Properties + Blowfish encryption
EnvironmentsMulti-env config (dev / qa / prod) via mule.env
Engineering notes.
The decisions that keep it exactThe watermark uses our timestamp, not the platform one
The case sync uses a dedicated custom field rather than Salesforce's LastModifiedDate to track which records have been processed. This was a deliberate v1.1.0 change noted in the flow code: LastModifiedDate can be updated by any system change, causing reprocessing of already-synced records. Integration_Timestamp__c is only updated when the integration itself processes a case, making the watermark exact.
The upsert checks the record type first
Before upserting accounts, the journey sync queries the Salesforce RecordType ID for "Member" accounts and filters the incoming payload to only include records where Portal_ID__c matches an existing Member-type account. This prevents the upsert from accidentally creating wrong-type Account records when a Portal_ID__c appears in the system but belongs to a different record type.
Lookups resolve in the system API, not the process layer
The Salesforce System API includes a generic lookup resolution layer: before upserting records, it detects lookup fields in the payload (identified by the presence of lookupTable and lookupField properties), queries Salesforce for the corresponding parent record IDs, and substitutes them into the payload. This keeps business logic out of the process layer and makes the SAPI reusable across any object with relational dependencies.
Every tracking field goes back to the portal
The order status batch fetches shipment-level detail for every shipment on the order: direction (inbound/outbound), estimated delivery date, tracking status, tracking number, tracking URL, carrier code, service code, delivery timestamp, and kit IDs. Each field is written back to the portal, making the full logistics picture visible without requiring direct access to the logistics platform.
What it settled.
Three figures, still truemanual Salesforce account creation — every portal registration creates the CRM record automatically
Request types an agent can raise in Salesforce and see land in the portal, unassisted
maximum shipment tracking lag — kit status, tracking URL, and estimated delivery date visible in portal within one polling cycle
What this one shows
Four things you can hold us to.
A case study is only worth reading if it generalises. These are the parts of this build that would show up again on yours.
- Different sync directions need different patternsPortal → Salesforce uses scheduled full-sync with RecordType filtering. Salesforce → Portal uses watermark-based incremental polling. Logistics → Portal uses a 5-minute batch. Each pattern matches its data type and latency requirement — applying the same pattern to all three would have made each one worse.
- External IDs are the architectural linchpinPortal_ID__c and Journey_ID__c make every upsert idempotent across all three sync patterns. Without them, every re-run creates duplicates; with them, every re-run is safe. External IDs are not a convenience — they are the safety property that makes scheduled sync reliable.
- Watermark field selection is a correctness question, not a convenienceThe v1.1.0 change from LastModifiedDate to Integration_Timestamp__c eliminated false re-triggers from unrelated field updates. A watermark on the wrong field produces duplicate work at best and inconsistent state at worst.
- Lookup resolution belongs in the system APIThe Salesforce SAPI's generic lookup resolver means process APIs pass field values, not Salesforce IDs. This keeps process APIs free from Salesforce-specific query logic and makes the SAPI reusable across any object with relational dependencies.