Frequently Asked Questions
Start by creating a new RedwoodJS app using yarn create redwood-app ./vonage-scheduler
. Then, install necessary dependencies like @vonage/server-sdk
, uuid
, and date-fns
using yarn. Set up your database connection in api/db/schema.prisma
, define the Appointment
model within the schema, and apply the migration using yarn rw prisma migrate dev
.
The Vonage Messages API is used to send SMS confirmations and reminders to users who book appointments through your RedwoodJS application. It's integrated using the @vonage/server-sdk
which is initialized with your Vonage API key and secret.
RedwoodJS utilizes Prisma as its Object-Relational Mapper (ORM) for database interactions. This simplifies database access, migrations, and management within the application. Prisma also makes it easy to switch databases (PostgreSQL, SQLite, MySQL).
The article suggests sending SMS reminders a configurable amount of time before the appointment. The timing is controlled by an environment variable (APPOINTMENT_REMINDER_MINUTES_BEFORE
) and an external scheduler triggers the reminder function.
Yes, RedwoodJS, through Prisma, supports SQLite and MySQL in addition to PostgreSQL. Adjust the provider and URL settings within your schema.prisma
file to switch databases.
Generate a RedwoodJS page with yarn rw g page AppointmentBooking /book
and modify the generated component to include a form with fields for selecting the appointment time and entering a phone number. The form submission should trigger the createAppointment
mutation.
An external scheduler like Vercel Cron Jobs or OS cron is used to periodically trigger a RedwoodJS function (/api/sendReminders
) responsible for checking upcoming appointments and sending reminder SMS messages. This function interacts with both the database and the Vonage API.
Create a helper function (api/src/lib/vonage.js
) to initialize the Vonage client using @vonage/server-sdk
. Then within your services, import and call this function to send SMS messages via the Vonage API. Ensure API keys and secrets are stored securely in environment variables.
The sendSms
helper function in the article is designed to handle common Vonage API errors by returning a specific object that includes success status, any error messages and details, and an optional message ID. The service then logs these details and uses the success status to update appointment confirmation status appropriately.
The booking code provides a unique identifier for each appointment. Although not fully implemented in this example, it's intended to facilitate appointment cancellation or lookup functionalities in a more complete application.
Reminders are sent via a dedicated Redwood Function (/api/sendReminders
) triggered externally by a scheduler. This function queries the database for upcoming appointments and sends SMS reminders using the Vonage API.
The E.164 format ensures consistent and reliable phone number handling, crucial for SMS delivery. It's the internationally recommended standard and using it avoids issues caused by varying national number formats. Libraries like libphonenumber-js
provide robust validation.
You'll need Node.js, Yarn, a Vonage API account with a virtual number, access to a database (PostgreSQL, SQLite, or MySQL), and basic command-line familiarity. The frontend examples also assume Tailwind CSS is set up.
Build an Appointment Scheduler with SMS Reminders Using RedwoodJS and Vonage
Learn how to build a full-stack appointment scheduling application with automated SMS reminders using RedwoodJS v8.x and the Vonage SMS API v3.24.1.[1][4] RedwoodJS – an opinionated, full-stack framework built on React, GraphQL, and Prisma – lets you create modern web applications with minimal configuration.[1] Combine it with Vonage's reliable SMS API, and you can send appointment confirmations and reminders to your users automatically.[4]
Real-world applications: This pattern is essential for medical practices (reducing no-show rates by 20-30%), service businesses (HVAC, automotive repair), consulting firms, salons, and educational institutions. Automated SMS reminders improve customer satisfaction and operational efficiency by ensuring clients remember their commitments without manual intervention.[5]
In this tutorial, you'll build a complete appointment scheduling system. Users can book appointments through a web interface, and your application will send SMS confirmations immediately and reminders 24 hours before each appointment. You'll work with RedwoodJS v8.x (2025), Node.js v22 LTS (active until October 2025, maintained until April 2027), Prisma ORM v6.16.0, and the Vonage SDK v3.24.1.[1][2][3][4]
Estimated completion time: 2-3 hours for basic implementation plus 1-2 hours for production deployment and testing. By the end, you'll have a production-ready appointment scheduler with automated SMS notifications.
This guide provides a complete walkthrough for building a web application using the RedwoodJS framework that enables users to book appointments and receive SMS reminders via the Vonage Messages API. We'll cover everything from project setup and core feature implementation to deployment and troubleshooting.
By the end of this tutorial, you'll have a functional RedwoodJS application featuring:
Target Audience: Developers familiar with JavaScript and Node.js, with some exposure to React and database concepts. Prior RedwoodJS experience is helpful but not strictly required.
Technologies Used:
@vonage/server-sdk
v3.24.1).[4]Project Overview and Goals
Business Context: No-shows cost service businesses an estimated $150 billion annually in the US alone. Automated appointment reminders can reduce no-shows by 20-30%, directly impacting revenue and operational efficiency. This system addresses the core problem: ensuring customers remember their commitments while minimizing manual administrative overhead.
We aim to build an application that solves the common problem of scheduling appointments and reducing no-shows through automated reminders with minimal human intervention.
Core Features:
System Architecture:
cron
) runs periodically to trigger a Redwood Function (/api/sendReminders
) which checks for upcoming appointments and triggers reminder SMS via the Vonage API.Prerequisites:
yarn
commands, butnpm
equivalents generally work.1. Setting Up the RedwoodJS Project
Let's initialize our RedwoodJS application and configure the basic structure.
Create RedwoodJS App: Open your terminal and run:
Follow the prompts. Choose TypeScript if you prefer, though this guide uses JavaScript.
Common installation issues:
PORT=3000
in.env
yarn --version
)node --version
Install Dependencies: We need the Vonage Server SDK v3.24.1,
uuid
, anddate-fns
.[4]Note:
node-cron
is removed as the scheduling logic relies on an external trigger for the function, not an in-process cron scheduler.Database Setup (Prisma): RedwoodJS uses Prisma v6.16.0 for database interaction.[3]
api/db/schema.prisma
.datasource db
block for your chosen database. For PostgreSQL with timezone support:Define Database Schema: Add the
Appointment
model toapi/db/schema.prisma
:Field explanations:
slotDateTime
: Stores the exact date and time using PostgreSQL'sTIMESTAMPTZ(6)
type, which stores timestamps in UTC with timezone awareness. This prevents timezone-related bugs in distributed systems.[9]phoneNumber
: Stores the recipient number for SMS in E.164 format (+[country code][number], max 15 digits). E.164 is the international standard required by Vonage APIs.[8]bookingCode
: A unique identifier generated during booking (8-character UUID substring).confirmed
,reminderSent
: Boolean flags to track SMS delivery status.slotDateTime
accelerates booking availability checks. The composite index(reminderSent, confirmed, slotDateTime)
optimizes the reminder query that filters by status flags and time range.[9]Create and Apply Migration: This command creates SQL migration files based on your schema changes and applies them to your database.
Enter a name for the migration when prompted (e.g.,
add_appointment_model
).Environment Variables (.env): RedwoodJS uses a
.env
file at the project root for environment variables. Create it if it doesn't exist and add your database connection string and Vonage credentials. Consider using.env.defaults
for non-secret default values likeAPPOINTMENT_REMINDER_MINUTES_BEFORE
.Security best practices for credentials:[10][11]
.env
to.gitignore
(RedwoodJS does this by default).Initialize Vonage Client (API Side): Create a utility file to initialize the Vonage client instance using the v3.24.1 SDK.[4]
Error handling notes:
sendSms
returns a consistent{ success: boolean, ... }
object for graceful error handling.2. Implementing Core Functionality (Booking)
Now, let's build the GraphQL API and the service logic for creating appointments.
Generate SDL and Service: Redwood's generators scaffold the necessary files.
Define GraphQL Schema (SDL): Modify the generated
appointments.sdl.js
(or.graphql
file if preferred) to include a specific input type for creation and define thecreateAppointment
mutation.CreateAppointmentInput
: Defines the data needed from the client (web side).createAppointment
: The mutation the web side will call.@skipAuth
: For simplicity in this guide, we bypass authentication. In a real app, you'd use@requireAuth
and implement Redwood Auth (yarn rw setup auth ...
).Query
block, although not the focus here.Implement the Service Logic: Update the
createAppointment
function inapi/src/services/appointments/appointments.js
.Race condition handling:
$transaction
to wrap availability check and creation in a single database transaction.@@unique([slotDateTime])
constraint in Prisma schema for database-level enforcement.Error response structure for clients:
UserInputError
: GraphQL returns400
with{ errors: [{ message: "...", extensions: { code: "BAD_USER_INPUT" } }] }
Error
: Returns500
with{ errors: [{ message: "..." }] }
error.graphQLErrors[0].extensions.code
to distinguish user errors from server errors.3. Building the Frontend (Web Side)
Let's create a simple React page with a form to book appointments.
Generate Page:
Create the Form Component: Modify
web/src/pages/AppointmentBookingPage/AppointmentBookingPage.js
.Form validation strategy:
onBlur
mode validates fields when user leaves the field.Accessibility considerations:
role="main"
landmark for main content.aria-describedby
links help text to form fields.aria-label
provides context for submit button states.role="alert"
announces errors to screen readers.Run the Development Server:
Navigate to
http://localhost:8910/book
(or the port specified). You should see the form. Try booking an appointment. Check your terminal logs (api
side) and your phone for the SMS confirmation! Check the database to see theconfirmed
flag.4. Implementing Scheduled Reminders
We need a mechanism to periodically check for upcoming appointments and send reminders. We'll use a RedwoodJS Function triggered by an external scheduler. Running cron within a serverless function is unreliable because serverless instances are ephemeral.
Scheduling Approaches Comparison:
Recommended: Use platform-native solutions (Vercel Cron or Netlify Scheduled Functions) for simplicity, or system cron for maximum reliability.[6][7]
Create a Redwood Function:
Implement the Function Logic: Edit
api/src/functions/sendReminders.js
:Retry logic and failure recovery:
reminderSent: false
, so next cron run retries automatically (idempotent).reminderSent: true
immediately to prevent duplicates if function times out.await new Promise(r => setTimeout(r, 100))
between sends.Configure External Scheduler:
Option 1: Vercel Cron Jobs[6]
Create or edit
vercel.json
in project root:schedule
: Cron expression.*/15 * * * *
= every 15 minutes.Vercel Setup Steps:
yarn rw setup deploy vercel
thenyarn rw deploy vercel
CRON_SECRET
environment variable in Vercel dashboard (Settings → Environment Variables)vercel.json
to project root and redeployOption 2: Netlify Scheduled Functions[7]
Edit
api/src/functions/sendReminders.js
to add config export:Alternatively, use
netlify.toml
:Netlify Setup Steps:
yarn rw setup deploy netlify
thenyarn rw deploy netlify
CRON_SECRET
environment variable in Netlify dashboard (Site settings → Environment variables)Option 3: System Cron (Linux/macOS)
Edit crontab:
crontab -e
@reboot
entry to ensure cron survives server restartstail -f /var/log/appointment-reminders.log
Option 4: GitHub Actions (for testing/open-source)
Create
.github/workflows/send-reminders.yml
:5. Deployment Considerations
Deploy your RedwoodJS application to platforms like Vercel, Netlify, or Render.
Key Environment Variables:
DATABASE_URL
: Production database connection string (use connection pooling for serverless: PgBouncer, Supabase, or Neon)VONAGE_API_KEY
: Your Vonage API keyVONAGE_API_SECRET
: Your Vonage API secretVONAGE_FROM_NUMBER
: Your Vonage virtual number (E.164 format)APPOINTMENT_REMINDER_MINUTES_BEFORE
: Reminder timing (default: 60)CRON_SECRET
: Secret for securing cron endpoint (generate withopenssl rand -hex 32
)Database Migrations: Run migrations in production:
Platform-Specific Deployment Guides:
Vercel Deployment:
yarn rw setup deploy vercel
npm i -g vercel
yarn rw deploy vercel
vercel.json
includes cron configuration (see Section 4)https://your-app.vercel.app
Netlify Deployment:
yarn rw setup deploy netlify
npm i -g netlify-cli
yarn rw deploy netlify
https://your-app.netlify.app
Render Deployment:
yarn install && yarn rw build
yarn rw serve
Security Best Practices:[10][11]
Never commit
.env
files: Ensure.gitignore
includes.env
(RedwoodJS default)Environment-specific configurations: Use different API keys for dev, staging, production
Secrets management services: For production, migrate from environment variables to dedicated secrets managers:
Example with 1Password CLI:
Implement rate limiting: Use RedwoodJS middleware or API gateway (Cloudflare, AWS API Gateway) to limit requests/IP
Add authentication:
yarn rw setup auth
then choose provider (Auth0, Supabase, Clerk). Replace@skipAuth
with@requireAuth
Input validation: Always validate on server-side. Client validation is convenience only.
HTTPS in production: Enforce HTTPS redirects (Vercel/Netlify automatic, Render: add HTTPS redirect rule)
Credential rotation: Plan quarterly rotation of Vonage API keys and database passwords
Monitor logs: Never log full
process.env
or secrets. Redwood's logger redacts sensitive fields by default.CRON_SECRET security: Treat as sensitive. Rotate if exposed. Consider IP allowlisting if possible.
6. Testing Your Application
Test the Booking Flow:
/book
in your browserconfirmed: true
if SMS was successfulTest the Reminder System:
APPOINTMENT_REMINDER_MINUTES_BEFORE
to 5 minutes for quick testing)reminderSent: true
in databaseAutomated Testing Strategy:
RedwoodJS includes Jest for unit and integration testing.
Service Tests (
api/src/services/appointments/appointments.test.js
):Run tests:
Common Test Scenarios:
confirmed: true
confirmed: false
and log errorreminderSent: true
after success7. Troubleshooting Common Issues
SMS Not Sending:
VONAGE_API_KEY
,VONAGE_API_SECRET
, andVONAGE_FROM_NUMBER
in.env
+[country][number]
, no spaces). Test with regex:^\+?[1-9]\d{1,14}$
[8]0
: Success1
: Throttled (rate limit)2
: Missing parameters3
: Invalid parameters4
: Invalid credentials5
: Internal error6
: Invalid message7
: Number barred8
: Partner account barred9
: Partner quota exceededDatabase Connection Errors:
DATABASE_URL
formatReminder Function Not Triggering:
vercel.json
ornetlify.toml
syntaxtail -f /var/log/cron.log
APPOINTMENT_REMINDER_MINUTES_BEFORE
calculationGraphQL Mutation Failures:
appointments.sdl.js
types match service return valuesapi/src/functions/graphql.js
Frequently Asked Questions
How do I change the reminder timing for appointments?
Adjust the
APPOINTMENT_REMINDER_MINUTES_BEFORE
environment variable in your.env
file (or hosting platform environment config). Set it to60
for 1 hour,1440
for 24 hours, or any value in minutes. ThesendReminders
function uses this value to calculate the reminder window:For multiple reminder times (e.g._ 24 hours AND 1 hour before)_ add a
remindersSent
JSON field to track which reminders were sent_ and modify the function logic accordingly.Can I send reminders through WhatsApp instead of SMS?
Yes. Vonage supports WhatsApp messaging through the Messages API. Modifications required:[4]
sendSms
function:Reference: Vonage WhatsApp API Documentation
How do I prevent double-booking appointments?
The
createAppointment
service uses Prisma transactions to prevent race conditions:[9]Alternative: Database-level constraint:
Then run migration:
yarn rw prisma migrate dev
The database constraint is more robust for high-traffic scenarios, but throws less user-friendly errors.
What phone number format should users enter?
Users should enter phone numbers in E.164 format:
+[country code][number]
with no spaces or special characters.[8]Examples:
+14155551234
(country code 1, area code 415, number 5551234)+442071234567
(country code 44, area code 20, number 71234567)+34912345678
(country code 34, area code 91, number 2345678)E.164 specification:
+
(optional in code, required by Vonage)Recommended: Use
libphonenumber-js
for robust validation:Client-side: Add country selector dropdown with
react-phone-number-input
for better UX.How do I add user authentication to the booking system?
RedwoodJS supports multiple authentication providers through RedwoodJS Auth:[1]
Setup steps:
Update SDL to require auth:
Use auth in services:
Frontend: Use
useAuth
hook:For user-specific appointment views, add
userId
toAppointment
model and filter queries bycurrentUser.id
.Can I customize the SMS message content?
Yes. Modify the
confirmationText
andreminderText
in your code:Confirmation SMS (in
appointments.js
service):Reminder SMS (in
sendReminders.js
function):Best practices:
How do I handle different time zones for appointments?
Best practice: Store in UTC, display in local time.[9]
Database storage:
Client-side: Convert to local time:
Server-side: Accept timezone from client:
Key principles:
DateTime
with@db.Timestamptz
does this automatically)Intl.DateTimeFormat().resolvedOptions().timeZone
)What happens if SMS delivery fails?
The
sendSms
function returns a{ success: boolean }
object. On failure:Booking confirmation failure:
confirmed: false
confirmed: false
and contact users manuallyReminder failure:
reminderSent
remainsfalse
Implementing a retry queue with RedwoodJS Background Jobs:[1]
Always log failures for monitoring and debugging:
Set up alerts (Sentry, Datadog, or simple email on error) to notify admins of persistent failures.
How much does it cost to send SMS through Vonage?
Vonage pricing is per-message, varying by destination country:[4]
Common rates (as of 2025):
Multi-part messages:
Cost estimation:
New accounts: Receive free trial credits ($2-10 depending on region) for testing.
Cost optimization:
Check current rates: Vonage SMS Pricing
How do I deploy this application to production?
Recommended platforms for RedwoodJS:
1. Vercel (Easiest):
vercel.json
(see Section 4)2. Netlify:
3. Render:
yarn install && yarn rw build
yarn rw serve
Pre-deployment checklist:
yarn rw build
locally to verify build succeedsyarn rw prisma migrate deploy
after deploymentcurl https://your-app.com/.redwood/functions/sendReminders
Production monitoring:
References
[1] RedwoodJS. "Releases." GitHub and RedwoodJS Community. Retrieved from https://github.com/redwoodjs/redwood/releases and https://community.redwoodjs.com/
[2] Node.js Release Working Group. "Node.js Releases." Retrieved from https://nodejs.org/en/about/previous-releases and https://nodesource.com/blog/nodejs-v22-long-term-support-lts
[3] Prisma. "Changelog and Releases." Retrieved from https://www.prisma.io/changelog and https://github.com/prisma/prisma/releases
[4] Vonage. "@vonage/server-sdk." npm. Retrieved from https://www.npmjs.com/package/@vonage/server-sdk and https://developer.vonage.com/
[5] Healthcare IT News. "No-show rates cost healthcare billions annually." 2024. Statistical data on appointment no-show impacts.
[6] Vercel. "Cron Jobs Documentation." Retrieved from https://vercel.com/docs/cron-jobs and https://vercel.com/guides/how-to-setup-cron-jobs-on-vercel
[7] Netlify. "Scheduled Functions Documentation." Retrieved from https://docs.netlify.com/build/functions/scheduled-functions
[8] Vonage. "What Is E.164 Format?" Developer Blog. Retrieved October 2024 from https://developer.vonage.com/en/blog/what-is-e-164-format
[9] Deiaa, Basem. "How to Fix Prisma DateTime and Timezone Issues with PostgreSQL." Medium. September 2025. Retrieved from https://medium.com/@basem.deiaa/how-to-fix-prisma-datetime-and-timezone-issues-with-postgresql-1c778aa2d122
[10] Liran Tal. "Do not use secrets in environment variables." Node.js Security Blog. October 2024. Retrieved from https://nodejs-security.com/blog/do-not-use-secrets-in-environment-variables
[11] FullStack Labs. "Best Practices for Scalable & Secure React + Node.js Apps in 2025." July 2025. Retrieved from https://www.fullstack.com/labs/resources/blog/best-practices-for-scalable-secure-react-node-js-apps-in-2025