Blog /
Two-way appointment confirmations on Medplum, in five minutes
·
Setup is one command:
npx @visitconfirmed/medplum
Run it and every appointment in your Medplum project gets AI reminders and confirmations that work out of the box. They are two-way, so patients reply in their own words, your schedule stays current, and your front desk stops chasing no-shows. What you get:
- SMS, AI voice, and smart calendar invites. Patients confirm in natural language over text, get a real phone conversation if they go silent, and receive an .ics calendar invite by email that updates automatically when they reschedule.
- Bidirectional FHIR write-back. Confirmations, cancellations,
reschedules, and at-risk escalations land in Medplum as native
resources.
Appointment.statusflips on confirm and cancel,Appointment.startandAppointment.endmutate in place on reschedule,Communicationcaptures the thread, andTaskhands off escalations to your care team.
Here is what one of your patients sees. Your clinic's name is on every message, the patient reschedules in their own words, and no one in your office picks up the phone:
Riverside Cardiology: Hi Maria, this is Riverside Cardiology reaching out about your follow-up with Dr. Patel on Thursday at 2:00 PM. Reply YES to confirm or let me know if you need a different time.
Maria: thursday is bad for me, can I do friday morning?
Riverside Cardiology: I have openings with Dr. Patel on Friday: 9:30 AM, 10:15 AM, or 11:00 AM. Which works?
Maria: 10:15
Riverside Cardiology: Done, your follow-up with Dr. Patel is moved to Friday at 10:15 AM. You'll get a calendar invite shortly.
Behind the scenes, Maria's Appointment moved to Friday at 10:15 AM
in Medplum (start and end mutated in place, same resource ID), a
Communication resource captured the full thread on Maria's patient
record, and Maria received an updated calendar invite for the new
slot. Your care team sees the schedule change in Medplum the moment
Maria does, with no inbox to triage and no phone tag.
VisitConfirmed is production-ready by default: timezone-aware quiet hours, retry windows, consent handling, and idempotent write-back that never overwrites manual edits made by your staff.
Medplum stays the source of truth. VisitConfirmed handles the engagement layer. Let's get you set up.
Step 1: Sign up for VisitConfirmed and grab your API key
Click Get Started Free. No credit card. The Pilot tier is free for thirty days and covers up to 500 appointments. That's plenty to validate the integration with real patients before deciding whether to upgrade. Full pricing is on the pricing page.
Once you are signed in, the dashboard's first screen is Connect Medplum. You see your VisitConfirmed API key and the connection command on this screen. Keep the key handy.
Step 2: Run the connection command
In your terminal:
npx @visitconfirmed/medplum
The command prompts for two things:
- Your VisitConfirmed API key (paste it from the dashboard).
- Your Medplum base URL. The default is
https://api.medplum.com. For self-hosted Medplum, enter your URL.
It then verifies your Medplum CLI login. If medplum login is
current, you are good. If not, the tool tells you what to run and
exits cleanly.
The command itself takes a few seconds. The
@visitconfirmed/medplum package is on npm
and the source is on GitHub
if you want to see exactly what it does before running it.
Step 3: What the command provisioned
Everything the tool creates lives in your Medplum project, where you can inspect, edit, or delete it.
An AccessPolicy that scopes VisitConfirmed to exactly the FHIR resources it needs:
{
"resourceType": "AccessPolicy",
"name": "VisitConfirmed Integration",
"resource": [
{ "resourceType": "Appointment", "readonly": false },
{ "resourceType": "Patient", "readonly": true },
{ "resourceType": "Practitioner", "readonly": true },
{ "resourceType": "Location", "readonly": true },
{ "resourceType": "Communication", "readonly": false },
{ "resourceType": "Task", "readonly": false },
{ "resourceType": "Subscription", "readonly": false }
]
}
Read access for context: who is the patient, which doctor, where the appointment is. Write access for the outcomes VisitConfirmed produces: status updates, message logs, escalation tasks. Nothing more.
A ClientApplication bound to that policy. Medplum returns a fresh Client ID and Client Secret. The tool sends those credentials directly to VisitConfirmed's API, authenticated with your VisitConfirmed API key. No manual copy-paste, and your Medplum admin password never leaves your machine.
A Subscription that fires when an Appointment is created or updated:
{
"resourceType": "Subscription",
"status": "active",
"reason": "VisitConfirmed appointment confirmations",
"criteria": "Appointment?status=pending,proposed",
"channel": {
"type": "rest-hook",
"endpoint": "https://visitconfirmed.com/api/medplum/fhir-appointment/",
"header": ["X-Medplum-Api-Key: <token-issued-to-your-org>"]
}
}
The criteria field uses standard FHIR search syntax, so you can
scope the trigger as tightly as you want. Filter by practitioner,
location, appointment type, or anything else Medplum indexes. You can edit
the Subscription, pause it, or delete it from the Medplum admin UI at
any time.
Step 4: Watch a real confirmation flow through
Open Medplum admin and create a test Appointment with your own phone number and email address on the Patient resource. Within a few seconds, you receive an SMS:
Hi Sarah, this is Riverside Cardiology reaching out about your follow-up with Dr. Patel on Thursday at 2:00 PM. Reply YES to confirm or let me know if you need a different time.
Reply YES. Within another few seconds, three things happen: two in your Medplum project, one in your inbox.
The Appointment status updates from pending to booked:
{
"resourceType": "Appointment",
"id": "...",
"status": "booked",
"...": "..."
}
A Communication resource appears, capturing the SMS thread:
{
"resourceType": "Communication",
"status": "completed",
"category": [{
"coding": [{
"system": "http://hl7.org/fhir/communication-category",
"code": "notification"
}]
}],
"subject": { "reference": "Patient/..." },
"about": [{ "reference": "Appointment/..." }],
"sent": "2026-04-29T15:32:14Z",
"received": "2026-04-29T15:33:02Z",
"payload": [
{ "contentString": "Hi Sarah, this is Riverside Cardiology..." },
{ "contentString": "YES" }
]
}
And a calendar invite lands in your email. Open the .ics file and the event drops onto your calendar with Dr. Patel as the practitioner, the location pulled from the Medplum Location resource, and Riverside Cardiology as the organizer. It is a real RFC 5545 invite, so Apple Calendar, Google Calendar, and Outlook all parse it natively.
If the patient reschedules over SMS later, a fresh invite arrives with the same UID and an incremented SEQUENCE, so the existing event on their calendar updates in place rather than spawning a duplicate.
That is the full loop. Subscription fires, VisitConfirmed reaches out, patient responds, Medplum gets updated, and the patient's calendar gets updated. No code on your side.
If the patient does not respond, retries are spaced according to your
quiet-hours and timezone settings. If they still do not respond by the
configured threshold, AI voice escalation kicks in on the Scale tier
and a real conversation happens, with the full transcript written
back as a Communication.
What gets written back
The write-back is what makes this FHIR-native instead of "a reminder service that calls an API."
| Patient outcome | What VisitConfirmed writes to Medplum |
|---|---|
| Confirmed | Appointment.status → booked, plus a Communication capturing the conversation |
| Cancelled | Appointment.status → cancelled, plus a Communication |
| Rescheduled | Appointment.start and Appointment.end updated in place (same resource ID), plus a Communication |
| No answer or at-risk | Task assigned to your care team, plus a Communication log of attempts |
These are standard FHIR resources. They show up in patient histories, they feed into your existing dashboards, and they let your care team see what happened in the same UI they already use.
Alongside the FHIR write-back, the patient receives an .ics calendar invite by email on confirm and reschedule, sharing one UID across the lifecycle so the event updates in place on their calendar instead of spawning a duplicate.
The write-back is also idempotent. If you edit an Appointment manually after VisitConfirmed has already updated it, your edit wins. VisitConfirmed never overwrites a human change.
Security boundary
Three things worth being explicit about.
Your Medplum credentials never reach VisitConfirmed. The npx tool runs on your machine, uses your local Medplum CLI authentication, and only sends the output of provisioning (a single ClientApplication's credentials) to VisitConfirmed.
VisitConfirmed only ever has the scopes you granted. Delete the AccessPolicy or revoke the ClientApplication in Medplum and VisitConfirmed loses access cleanly. There is no other channel.
Everything is auditable in Medplum. The Subscription, AccessPolicy, and ClientApplication all live in your project. Medplum's AuditEvent records every read and write. You see exactly what VisitConfirmed has done.
VisitConfirmed is HIPAA-compliant with BAAs in place for SMS, email, and voice. PHI is handled appropriately end-to-end.
What if you do not want to run a CLI
The npx flow is the fastest path, but it is not the only one. If you prefer explicit credential control, or you cannot run Node on the machine that has Medplum CLI access, you can connect manually:
- In Medplum admin, paste the AccessPolicy JSON from above and save it.
- Create a new ClientApplication and attach the AccessPolicy.
- Copy the Client ID, Client Secret, and your Medplum base URL into the VisitConfirmed dashboard.
- VisitConfirmed creates the Subscription on your behalf and verifies the connection.
Same end state, more clicks.
Get started
Sign up free, run the npx command, and let real
appointments flow through for a few weeks. The full
Medplum appointment confirmation integration page
has more on positioning and architecture. The
@visitconfirmed/medplum package
is on npm. The full source, including the optional advanced Bot
path, is on GitHub.
Further reading
- Why appointment reminders are not appointment confirmations: the difference between "we sent the message" and "the patient is actually coming"
If you hit anything confusing during onboarding, the team is in Medplum's Discord and on email at hello@visitconfirmed.com.
Five minutes. One command. SMS, AI voice, and smart calendar invites for your patients. Native FHIR write-back to Medplum for your care team.