Founder & Lead Engineer, RAITHub
RAITHub ships and tests production software. See QA as a Service or talk to us.
A prescription refill workflow routes a patient's request to a clinician, who decides; the software never prescribes. Build it as a state machine (requested, in review, approved, declined, dispensed), put requests in a clinician review queue, audit every step, and test that no request is auto-approved and none goes quiet. This is engineering guidance only: the prescribing decision is clinical, and your compliance partner sets the data rules.
This post is about building the request-and-review software, not clinical or prescribing advice. Whether a refill is appropriate is a clinical decision made by a qualified clinician. What patient and medication data you may store, and how it must be protected, are governed by health-data and pharmacy law that differs by jurisdiction; this is general information, so confirm the rules with your compliance partner and adviser. If you would rather have it built for you, see how RAITHub would build this below.
What does the software actually do, and not do?
It captures a request, routes it to a clinician, records the clinician's decision, and tracks the outcome. It does not decide whether the refill is safe or appropriate: that is prescribing, a clinical act. Building this line in from the start keeps the system honest and safe: every approval has a clinician behind it, and the software's job is to make that review fast, complete and auditable, never to shortcut it.
| The software does | The software does not |
|---|---|
| Capture the patient's refill request with the details a clinician needs | Decide whether the refill is clinically appropriate |
| Route it to the right clinician's review queue | Auto-approve based on rules alone |
| Record the decision, reason, clinician and time | Make or alter a prescription |
| Track and surface requests that are waiting too long | Replace clinical judgement or pharmacy checks |
What does the data model look like?
A refill request has a current state, a link to the patient and the medication reference, and a history. The decision is recorded against a clinician, never inferred.
CREATE TABLE refill_requests (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
patient_id bigint NOT NULL REFERENCES patients(id),
medication_ref text NOT NULL, -- a reference to the record, access controlled
state text NOT NULL DEFAULT 'requested',
-- requested | in_review | approved | declined | dispensed | cancelled
state_since timestamptz NOT NULL DEFAULT now(), -- for the overdue check
requested_at timestamptz NOT NULL DEFAULT now()
);
CREATE TABLE refill_decisions (
id bigserial PRIMARY KEY,
request_id uuid NOT NULL REFERENCES refill_requests(id),
outcome text NOT NULL CHECK (outcome IN ('approved','declined','needs_appointment')),
reason text,
by_clinician bigint NOT NULL REFERENCES clinicians(id), -- a human, always
created_at timestamptz NOT NULL DEFAULT now()
);
CREATE INDEX refill_requests_by_state ON refill_requests (state, state_since);
Store a reference to the medication record and control access to the detail separately, keeping stored patient data to what the review needs. What you may hold is for your compliance partner to define.
How does the clinician review queue work?
A request enters in_review and appears in a queue of the right clinician or team. The queue shows what the clinician needs to decide, and the decision is an explicit action that writes a refill_decisions row and moves the request to approved, declined or needs_appointment. The crucial rule: the system never writes an approval without a clinician action behind it. If you later add rules (for example, flagging requests that are clearly within an existing valid prescription), they triage the queue, they do not decide, a clinician still confirms. That boundary is what keeps the software on the right side of "not prescribing."
How do you make sure no request is forgotten?
With the same overdue check used for referrals and SLAs: each state has a target time, and a scheduled, idempotent job surfaces requests that have waited too long. A patient waiting on a refill is time-sensitive, so an overdue request should be escalated, not left in a queue. The pattern is identical to the maintenance SLA engine in a maintenance ticketing SaaS with SLA escalation, applied to clinical turnaround times your clinicians set.
-- Requests waiting too long for review (the overdue worklist).
SELECT id, medication_ref, state, state_since
FROM refill_requests
WHERE state IN ('requested', 'in_review')
AND state_since < now() - make_interval(hours => $1); -- target review time
Why audit every step, and how?
Because a medication decision must be traceable to a clinician, and patient data access is reviewable. Record every state change and decision as an append-only event: the from-state, the to-state, the clinician or patient who acted, the reason and the time. Never overwrite. This gives a defensible record of who approved or declined a refill and when, which is essential in clinical software. The audit pattern is in designing an append-only audit log, and patient-level access is controlled with the roles in designing SaaS authorization.
How do you test a refill workflow?
The two tests that matter most: nothing is approved without a clinician, and nothing is forgotten.
import { describe, it, expect } from 'vitest'
import { decide, overdue, stateOf } from './refills'
describe('refill workflow', () => {
it('cannot be approved without a clinician decision', async () => {
const req = await seedRequest({ state: 'in_review' })
// No automatic path to 'approved' exists; only decide() with a clinician can.
await decide(req.id, { outcome: 'approved', byClinician: 12, reason: 'within valid script' })
const d = await lastDecision(req.id)
expect(d.byClinician).toBe(12)
expect(await stateOf(req.id)).toBe('approved')
})
it('surfaces a request waiting too long for review', async () => {
const req = await seedRequest({ state: 'requested' })
await ageStateSince(req.id, { hours: 48 })
const list = await overdue({ targetHours: 24 })
expect(list.map((r) => r.id)).toContain(req.id)
})
})
Add a test that each decision writes exactly one audit event, and one that a patient cannot read another patient's requests.
Buy, build or hire?
| Option | Choose this when | The catch |
|---|---|---|
| A patient-portal or EHR module | Your practice uses one platform that already includes refill requests | You fit its workflow and data model; your own review queue and channels may not map |
| Phone and a paper list | Very low volume | No audit trail, no overdue tracking, no patient self-service |
| Custom web app or PWA | You want patient self-service, your own review workflow, audit and reach on low-end phones | You own the workflow and the record; prescribing stays clinical and data rules come from your compliance partner |
How long does it take to build yourself, and what is the risk?
For an experienced developer building the request workflow, review queue, overdue check and audit trail as a web app or PWA, our estimate is 4 to 6 weeks at a fixed scope. The main risks are two. First, the safety boundary: the software must never approve a refill without a clinician, so review that path hard. Second, the data layer: health and medication data carry strict obligations that differ by jurisdiction, and no app is compliant on its own. This is general information, so build it with your compliance partner setting the rules and clinicians owning every decision.
Why RAITHub for this
- Workflows, roles and reach. RAITHub built PadhAI with channel parity across a PWA, WhatsApp and Telegram, and enforces per-tenant isolation and roles on Sundor Skin (530+ tests) and PropDesk (1,024 tests).
- Healthcare adjacency, stated honestly. RAITHub has built a healthcare scheduling app for a client; it has not shipped a regulated health product, and there is no published health case study, so weigh the evidence accordingly.
- Safe by design. The approval path is built so only a clinician can approve, and every step is audit-logged.
When you don't need us
- Your portal's refill module fits. If its workflow and data model match your practice, use it.
- Volume is tiny. A documented manual process may be enough.
- You need a regulated, validated system. RAITHub builds no GxP-validated systems; use a vendor cleared for that.
How RAITHub would build this
- Request workflow: a state machine where only a clinician decision can approve, every transition audit-logged.
- Review queue: requests routed to the right clinician, with optional rule-based triage that flags but never decides.
- Overdue check: an idempotent scheduled job that escalates requests waiting too long.
- Reach and data: a PWA that works on low-end phones, with patient data minimised and protected to your partner's rules.
Timeline: a first version fits the 4 to 6 week fixed-scope range; EHR or pharmacy integration pushes it toward the 6 to 12 week backend range. See SaaS development and the HealthTech industry page. A related build is a referral management system for clinics.
You receive: the workflow, queue and audit code, tests gated in CI, handover docs, and full IP in your name under NDA. Clinical decisions stay with clinicians; data rules come from your compliance partner.
Frequently asked questions
Does a prescription refill app decide whether to refill?
No. The software captures the request, routes it to a clinician and records their decision. Deciding whether a refill is appropriate is prescribing, a clinical act, so every approval has a clinician behind it. The software never auto-approves on rules alone.
How is the refill request modelled?
As a state machine: requested, in review, approved, declined, needs appointment, dispensed, cancelled. Each request links to the patient and a medication reference, and the decision is recorded against a clinician in its own row, never inferred from the state.
How do I make sure a refill request is not forgotten?
With a scheduled, idempotent job that surfaces requests waiting longer than the target review time clinicians set. A patient waiting on a refill is time-sensitive, so an overdue request is escalated rather than left sitting in a queue.
Why does a refill workflow need an audit trail?
Because a medication decision must be traceable to a clinician, and patient data access is reviewable. Record every state change and decision as an append-only event with the from-state, to-state, the person who acted, the reason and the time, and never overwrite.
Can patients request refills from a basic phone?
Yes, if you build it as a PWA that works on low-end devices and poor connections. RAITHub built PadhAI for exactly that kind of reach, with the same experience across a web app and messaging channels, which the same approach can bring to a refill request flow.
Does RAITHub handle the compliance side?
RAITHub builds the workflow, access control and audit trail. What patient and medication data you may store, and how it must be protected, are governed by health-data and pharmacy law that differs by jurisdiction, and no app is compliant on its own. This is general information; confirm the rules with your compliance partner and adviser.
Related posts
Ready to discuss your project?
Book a free 15-minute technical audit with our engineering team.