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-sdkv3.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:
yarncommands, butnpmequivalents 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=3000in.envyarn --version)node --versionInstall Dependencies: We need the Vonage Server SDK v3.24.1,
uuid, anddate-fns.[4]Note:
node-cronis 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 dbblock for your chosen database. For PostgreSQL with timezone support:Define Database Schema: Add the
Appointmentmodel 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.slotDateTimeaccelerates 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
.envfile 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.defaultsfor non-secret default values likeAPPOINTMENT_REMINDER_MINUTES_BEFORE.Security best practices for credentials:[10][11]
.envto.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:
sendSmsreturns 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.graphqlfile if preferred) to include a specific input type for creation and define thecreateAppointmentmutation.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@requireAuthand implement Redwood Auth (yarn rw setup auth ...).Queryblock, although not the focus here.Implement the Service Logic: Update the
createAppointmentfunction inapi/src/services/appointments/appointments.js.Race condition handling:
$transactionto 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 returns400with{ errors: [{ message: "...", extensions: { code: "BAD_USER_INPUT" } }] }Error: Returns500with{ errors: [{ message: "..." }] }error.graphQLErrors[0].extensions.codeto 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:
onBlurmode validates fields when user leaves the field.Accessibility considerations:
role="main"landmark for main content.aria-describedbylinks help text to form fields.aria-labelprovides 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 (apiside) and your phone for the SMS confirmation! Check the database to see theconfirmedflag.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: trueimmediately 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.jsonin project root:schedule: Cron expression.*/15 * * * *= every 15 minutes.Vercel Setup Steps:
yarn rw setup deploy vercelthenyarn rw deploy vercelCRON_SECRETenvironment variable in Vercel dashboard (Settings → Environment Variables)vercel.jsonto project root and redeployOption 2: Netlify Scheduled Functions[7]
Edit
api/src/functions/sendReminders.jsto add config export:Alternatively, use
netlify.toml:Netlify Setup Steps:
yarn rw setup deploy netlifythenyarn rw deploy netlifyCRON_SECRETenvironment variable in Netlify dashboard (Site settings → Environment variables)Option 3: System Cron (Linux/macOS)
Edit crontab:
crontab -e@rebootentry to ensure cron survives server restartstail -f /var/log/appointment-reminders.logOption 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 vercelnpm i -g vercelyarn rw deploy vercelvercel.jsonincludes cron configuration (see Section 4)https://your-app.vercel.appNetlify Deployment:
yarn rw setup deploy netlifynpm i -g netlify-cliyarn rw deploy netlifyhttps://your-app.netlify.appRender Deployment:
yarn install && yarn rw buildyarn rw serveSecurity Best Practices:[10][11]
Never commit
.envfiles: Ensure.gitignoreincludes.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 auththen choose provider (Auth0, Supabase, Clerk). Replace@skipAuthwith@requireAuthInput 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.envor 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:
/bookin your browserconfirmed: trueif SMS was successfulTest the Reminder System:
APPOINTMENT_REMINDER_MINUTES_BEFOREto 5 minutes for quick testing)reminderSent: truein 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: trueconfirmed: falseand log errorreminderSent: trueafter success7. Troubleshooting Common Issues
SMS Not Sending:
VONAGE_API_KEY,VONAGE_API_SECRET, andVONAGE_FROM_NUMBERin.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_URLformatReminder Function Not Triggering:
vercel.jsonornetlify.tomlsyntaxtail -f /var/log/cron.logAPPOINTMENT_REMINDER_MINUTES_BEFOREcalculationGraphQL Mutation Failures:
appointments.sdl.jstypes match service return valuesapi/src/functions/graphql.jsFrequently Asked Questions
How do I change the reminder timing for appointments?
Adjust the
APPOINTMENT_REMINDER_MINUTES_BEFOREenvironment variable in your.envfile (or hosting platform environment config). Set it to60for 1 hour,1440for 24 hours, or any value in minutes. ThesendRemindersfunction uses this value to calculate the reminder window:For multiple reminder times (e.g._ 24 hours AND 1 hour before)_ add a
remindersSentJSON 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]
sendSmsfunction:Reference: Vonage WhatsApp API Documentation
How do I prevent double-booking appointments?
The
createAppointmentservice uses Prisma transactions to prevent race conditions:[9]Alternative: Database-level constraint:
Then run migration:
yarn rw prisma migrate devThe 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-jsfor robust validation:Client-side: Add country selector dropdown with
react-phone-number-inputfor 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
useAuthhook:For user-specific appointment views, add
userIdtoAppointmentmodel and filter queries bycurrentUser.id.Can I customize the SMS message content?
Yes. Modify the
confirmationTextandreminderTextin your code:Confirmation SMS (in
appointments.jsservice):Reminder SMS (in
sendReminders.jsfunction):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:
DateTimewith@db.Timestamptzdoes this automatically)Intl.DateTimeFormat().resolvedOptions().timeZone)What happens if SMS delivery fails?
The
sendSmsfunction returns a{ success: boolean }object. On failure:Booking confirmation failure:
confirmed: falseconfirmed: falseand contact users manuallyReminder failure:
reminderSentremainsfalseImplementing 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 buildyarn rw servePre-deployment checklist:
yarn rw buildlocally to verify build succeedsyarn rw prisma migrate deployafter deploymentcurl https://your-app.com/.redwood/functions/sendRemindersProduction 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