Building the Shift Marketplace
for Modern Medicine

A ground-up SaaS platform connecting physicians with hospitals and locum companies - built across four interconnected portals, three environments, and a live app on both the App Store and Google Play.
NestJS
MongoDB
React Native
AWS EC2
Vercel
Ant Design
JWT Auth
Redux
Sendgrid
Twilio
Stripe
OpenAI

10-20 mins

Sync Time After
↓ 95% reduction (was 4.5 hrs)

-75%

AI Token Cost Reduction
via prompt caching + unified calls

Zero

Silent AI Features Post-Fix
real retry with exponential backoff

Overview

A Multi-Portal Workforce Platform for the Healthcare Industry

Physician scheduling in the US still runs on text threads, spreadsheets, and informal word-of-mouth. Shiftolic was built to replace this with a structured, real-time marketplace — connecting doctors who need coverage with hospitals and staffing companies that need reliable providers.

THE PROBLEM

Physicians needing shift coverage have no centralized platform. Locum companies manually manage pools of providers across spreadsheets and email chains. There is no structured way to post, discover, or claim shifts — resulting in last-minute scrambles and poor visibility for all parties.

The Solution

A four-portal SaaS platform where doctors can post and claim shifts, join specialty groups, verify credentials, and sync their calendar — while locum companies manage provider pools, post open shifts, and build out their facilities. One shared backend serves all portals.

Technology Stack

A four-portal SaaS platform where doctors can post and claim shifts, join specialty groups, verify credentials, and sync their calendar — while locum companies manage provider pools, post open shifts, and build out their facilities. One shared backend serves all portals.
NestJS
MongoDB
Mongoose
JWT Auth
React Native
React Native
Expo
Ant Design
Redux
AWS S3
Sendgrid
Twilio
Stripe
OpenAI
Google OAuth

Overview

A Multi-Portal Workforce Platform for the Healthcare Industry

Physician scheduling in the US still runs on text threads, spreadsheets, and informal word-of-mouth. Shiftolic was built to replace this with a structured, real-time marketplace — connecting doctors who need coverage with hospitals and staffing companies that need reliable providers.

THE PROBLEM

Physicians needing shift coverage have no centralized platform. Locum companies manually manage pools of providers across spreadsheets and email chains. There is no structured way to post, discover, or claim shifts — resulting in last-minute scrambles and poor visibility for all parties.

THE SOLUTION

A four-portal SaaS platform where doctors can post and claim shifts, join specialty groups, verify credentials, and sync their calendar — while locum companies manage provider pools, post open shifts, and build out their facilities. One shared backend serves all portals.

Core Product

Shift Marketplace - Drop, Swap & Locum Coverage

The core of Shiftolic is the shift post lifecycle — a doctor or locum company posts an open shift, physicians apply, and the posting party approves a provider. Two coverage types are supported: Drop (someone takes the shift outright) and Swap (a mutual exchange of dates between two doctors).

Each shift post embeds an array of ShiftDate subdocuments representing up to seven individual dates, each carrying its own time, shift type (day/night/swing), coverage state, and applicant list. Applicants are tracked in a coveredByIds array on each date, and once one is approved the coveredBy field is set and the date is locked.

Coverage Type Lock

One nuanced constraint required careful implementation: once any shift date has been claimed by a provider, the coverage type (drop vs swap) can no longer be changed for that post. This is enforced at both the backend — where the aggregation pipeline adds a hasClaimedDates boolean computed from the shiftDates array — and at the frontend, where the coverage toggle cards receive a disabled prop and a contextual lock hint is displayed.

Timezone Date Bug — Fixed
Dates were originally stored at midnight UTC which caused off-by-one errors for users in UTC+ timezones — a shift date of May 7 would display as May 6 in some regions. The fix: all dates are stored at noon UTC (T12:00:00Z). This ensures no timezone offset, positive or negative, can push the date to a different calendar day.
ICS Calendar Feed
Every doctor gets a unique private ICS feed URL generated from a CalendarFeedToken — a UUID stored in MongoDB. The URL is unauthenticated by design (calendar apps can't send auth headers), secured by the token's entropy. Doctors subscribe in Google Calendar, Apple Calendar, or Outlook for passive shift sync.
7
Shift dates per post, each independently tracked with applicants, coverage state, time, and pay amount

Groups & Communication

Shift Trading Groups & Real-Time Messaging

Doctors can create and join shift trading groups — private or public — organized around a hospital or department. Groups act as a scoped distribution layer: shift posts can be targeted at up to three specific groups rather than (or in addition to) the open marketplace. This lets physicians coordinate coverage within trusted peer networks.

Group invitations support both registered users and non-users (via email or SMS). When a non-user accepts an invitation via the link in their email or text, their account is pre-associated with the group on registration.

Real-Time Messaging

Every shift application opens a conversation scoped to that shift post between the applicant and the posting doctor. Messages are delivered in real time via WebSocket and persisted to MongoDB. If the recipient is offline, an Expo push notification is dispatched as a fallback. Each user can independently archive or delete a conversation without affecting the other party's view.

Sidebar MenuItems Hook Fix
The sidebar navigation relied on SIDEBAR_MENU_ITEMS called as a plain function — but it internally used useCurrentPlan(), a React hook, causing "must be used within provider" errors on SSR and fast refresh. Renamed to useSidebarMenuItems(), called inside the component tree, and the result passed down as a prop — eliminating the error class entirely.

Auth & Identity

Authentication, Google SSO,
& AI-Powered Verification

The platform supports two authentication paths — email/password with OTP verification, and Google SSO — across all portals. This distinction has non-trivial downstream effects on every feature that touches user credentials.

01

Dual Auth Paths

Email users go through OTP verification on signup. Google SSO users receive no passwordHash on their record. Every credential-sensitive operation (password change, account deletion) must first determine which path the user registered with via the hasPassword API.
Architecture

02

Google SSO Delete Account

Account deletion required a password confirmation — but Google users have no password. The fix: made password optional in the DTO, skipped the hash check when no passwordHash exists, and built a frontend modal that conditionally renders the password field only for email users after fetching hasPassword on modal open.
Bug Fix

03

OpenAI ID Verification

During onboarding, doctors upload a government-issued ID image which is sent to OpenAI's vision model for automated verification. The result updates verificationStatus on DoctorProfile (UNVERIFIED → PENDING → VERIFIED). Document images are stored in S3 and never served publicly.
Ai Integration

JWT-based authentication issues short-lived access tokens (1 day) and longer-lived refresh tokens (7 days). All passwords are bcrypt-hashed and excluded from all API responses via Mongoose's select: false directive — they cannot be accidentally leaked in any query response.

Google OAuth is implemented separately for the doctor portal, locum portal, and mobile app — each with its own redirect URI registered in Google Cloud Console. The mobile app uses @react-native-google-signin with platform-specific client IDs for iOS and Android.

Credentialing Vault

Beyond identity verification, doctors can upload a full set of credentialing documents — medical license, board certification, DEA certificate, malpractice insurance, and resume/CV. Each document type stores an array of file references (S3 URL, file name, upload timestamp). All uploads and deletes are audit-logged in a separate CredentialHistory collection

Mobile

iOS & Android — Live on Both App Stores

Live
Approved and live on Apple App Store and Google Play. Built with Expo EAS Build and submitted via EAS Submit.

The mobile app is a React Native + Expo application targeting both iOS and Android from a single codebase. It mirrors the doctor web portal's feature set — shift hub, marketplace, groups, messages, credentials, push notifications, and profile management.

Builds are managed via Expo EAS Build, which handles iOS code signing and Android keystore management without manual certificate setup. Submissions to both stores use EAS Submit. The Apple Developer Account and Google Play Console are owned by the client; the development team operated under delegated access.

Apple App Review — Permission String Fix

The first App Store submission was rejected under Guideline 5.1.1(ii): the camera and photo library permission strings were generic defaults from Expo's build system — "Allow Shiftolic to access your camera." Apple requires strings that explain the specific use with a concrete example.

The fix involved a careful audit of the codebase to determine exactly where each permission is used. Camera and photo library are used only for profile photo capture — not for ID verification (which uses the system document picker, requiring no permission string) and not for credentialing. The infoPlist block in app.config.ts was updated with precise, example-driven strings for all four permission types.

Push Notifications via Expo
Device tokens are registered in a DeviceTokens collection keyed by userId + token, with an isActive flag set to false when Expo reports an invalid token. Notifications fan out to all active tokens for a user, so multi-device users receive on all devices simultaneously.

Engineering Decision

Key Technical Decisions

A selection of non-trivial architectural and implementation decisions made during the build, each driven by a real constraint encountered in production or during app store submission.

Noon UTC Date Storage

All shift dates are stored at T12:00:00.000Z instead of midnight. A date stored at midnight UTC becomes "the previous day" for UTC+n users. Storing at noon means no timezone offset — positive or negative — can cross a day boundary. Nine instances of moment.utc(date) were replaced with a dateStringToNoonUTC() helper across the shift post service.

Single NestJS API for All Portals

Rather than separate backends per portal, a single NestJS application serves the doctor portal, locum portal, and mobile app. Role-based guards enforce access at the controller level. This avoids data duplication, simplifies shared features like messaging and notifications, and reduces infrastructure cost — one EC2 instance, one CI/CD pipeline, one MongoDB Atlas cluster.

SendGrid Click Tracking Disabled

On a SendGrid trial account, click tracking rewrites every link in outbound emails to route through SendGrid's HTTP tracking domain — converting https:// invitation links to http:// before the user clicks. This produced "site doesn't support a secure connection" warnings in Chrome. The fix: disable Click Tracking in SendGrid dashboard → Settings → Tracking.

Denormalized hasClaimedDates on ShiftPost

To enforce the coverage type lock in the frontend, the API needed to signal whether any date on a shift post had been claimed. Rather than a separate query, a $addFields stage was added to the getMyShiftPosts aggregation pipeline, computing hasClaimedDates as a boolean from the shiftDates.coveredBy array — zero additional database round-trips.
What Was Built
From a blank repository to a live, multi-portal SaaS with a published mobile app — the Shiftolic platform covers the complete physician workforce lifecycle: shift discovery, coverage management, group trading, real-time messaging, credentialing, identity verification, subscription billing, push and email notifications, and an ICS calendar feed. All from a single shared backend serving four distinct client surfaces.

4

Client portals served by one API

31

MongoDB collections in production

6

Third-party services integrated

2

Live app stores — approved first round

Outcomes

What Was Delivered

Full Shift Marketplace — Web & Mobile

Drop and swap shift posting, multi-date scheduling, applicant approval, and coverage tracking — live across doctor web portal and mobile app.

Group Trading Network

Public and private shift trading groups with hospital/department scoping, search, SMS invitations, and onboarding integration.

Real-Time Messaging

WebSocket-based chat scoped to shift interactions, with Expo push notification fallback and per-user archive/delete state.

AI-Powered Identity Verification

Government ID upload processed by OpenAI vision with four-state verification flow and full audit trail in S3 and MongoDB.

Credentialing Vault

Medical license, board certification, DEA certificate, malpractice insurance, and CV — uploaded via system document picker, stored in S3, audit-logged on every change.

Calendar Sync via ICS Feed

Private per-user ICS feed URL subscribed in Google Calendar, Apple Calendar, or Outlook — confirmed shift dates appear automatically across all calendar apps.

Subscription Billing

Stripe-powered plan management with feature flags controlling shift post limits, group creation, calendar sync, credentialing vault, and marketplace access per tier.

Mobile App — Live on Both Stores

React Native + Expo app approved and live on Apple App Store and Google Play. Passed App Store review with properly scoped permission strings after initial rejection.

Locum Company Portal

Full-featured portal for staffing companies — facility management, provider pool, shift posting, applicant review, analytics dashboard, and team management.

Your next product runs on AI. Let's build it.

Tell us what you're building and let's make it happen.
Get in Touch

Your next product runs on AI. Let's build it.

Tell us what you're building and we'll show you how AI can make it faster, smarter, and built to last.
Let's Talk