For developers
Integrations (for developers)
How outside services plug in behind interfaces in packages/integrations, so a vendor can be swapped and nothing needs a real key to develop.
The pattern
Every outside service sits behind a TypeScript interface in packages/integrations. Each
interface has at least two implementations: the real vendor, and a simulated one that gives
realistic answers with no account. The app picks one at start-up from configuration, so:
- a vendor can be swapped without touching the screens,
- development, tests and demos need no real keys,
- what is simulated is always labelled as such on screen.
The company-facing switchboard is the Integrations page.
The interfaces today
| Interface | File | Real provider | Simulated / fallback | Chosen by |
|---|---|---|---|---|
Mailer | mailer.ts | SMTP (Nodemailer) | Console mailer (prints the email) | SMTP_HOST set |
FileStore | storage.ts | Any S3-compatible store (SeaweedFS locally) | S3_* settings | |
CarrierRegistry | fmcsa.ts | FMCSA QCMobile (QcMobileRegistry) | SimulatedRegistry | A key saved on the Integrations page |
Billing | billing.ts | Stripe (StripeBilling) | SimulatedBilling (test mode) | STRIPE_SECRET_KEY set |
Mailer
interface Mailer {
send(message: MailMessage): Promise<void>;
}
FileStore
Keys are always under the org: <orgId>/…. assertSafeKey() and assertSafePrefix() refuse
anything else, and the store lists an org's keys for export and erase.
CarrierRegistry (FMCSA)
interface CarrierRegistry {
readonly provider: 'fmcsa-qcmobile' | 'simulated';
lookup(query: FmcsaQuery): Promise<FmcsaRecord | null>;
}
Returns authority, insurance, safety and the registered email (used by
R3). Throws RegistryUnavailable when the
service cannot be reached; the screen then says so instead of guessing.
Billing (Stripe)
interface Billing {
readonly provider: 'stripe' | 'simulated';
readonly testMode: boolean;
createCustomer(...): Promise<string>;
createCheckout(...): Promise<string>;
createPortalSession(...): Promise<string>;
cancelSubscription(id: string): Promise<void>;
changePlan(...): Promise<void>;
parseWebhook(rawBody: string, signature: string | null): Promise<NormalizedBillingEvent[]>;
}
Webhooks are signature-checked (WebhookSignatureError when forged) and each event is applied
once, so a retried webhook cannot double-apply.
Adding a new integration
Define the interface
Inpackages/integrations/<service>.ts: the smallest set of calls the app needs, in the app's own words, not the vendor's.Write the simulated provider first
Realistic, deterministic answers. Tests and the demo use it.Write the real provider
Map the vendor's response into the app's types. Errors become a typed "unavailable" error.Choose at start-up
From configuration or a key saved on the Integrations page. Keys stay on the server.Label it on screen
Anything simulated says Simulated where it is shown.Document it
Here, on the Integrations page, and in the changelog.
Planned
Load boards (DAT, Truckstop), truck tracking (Samsara, Motive), ocean milestones and last free day (Project44) and the AI assistant's live model are on the Roadmap. Each will follow this pattern.