Frequently Asked Questions
Use the Sinch SMS API's sms.batches.send
method with the send_at
parameter. This parameter allows you to specify the exact time the SMS should be sent, enabling scheduled reminders. The time must be provided in UTC using ISO 8601 format as a string in the API request body. It's crucial to convert times to UTC before scheduling reminders with the Sinch API.
Sinch provides the SMS API and Node.js SDK that enables sending and scheduling SMS messages programmatically. The Sinch API handles the actual sending of the SMS messages at the specified time, leveraging its robust infrastructure for reliable message delivery. It also offers number provisioning and verification services.
Next.js simplifies building the user interface and API routes needed for this application, providing file-based routing and serverless functions for cleaner code. Using a framework like Next.js provides structure and simplifies routing between pages. This makes it easy to manage your application’s frontend and backend within a single project.
Always convert the scheduled time to UTC before passing it to the Sinch API's send_at
parameter. The Sinch API expects UTC, and failing to convert can lead to reminders being sent at the wrong time. Luxon's .toUTC().toISO()
helps with this conversion.
Use npx create-next-app@latest sinch-scheduler --typescript
to initialize the project. Then install the Sinch SDK (@sinch/sdk-core
) and Luxon (luxon
) libraries using npm or yarn. Configure the Sinch credentials (Project ID, Key ID, Key Secret, FROM_NUMBER, SMS Region) as environment variables in a .env.local
file.
Luxon is essential for handling dates and times, particularly converting between time zones and calculating the correct UTC time for the Sinch API. Luxon provides robust methods for parsing, formatting, and manipulating dates and times, which is essential for accurate scheduling. It simplifies time zone conversions, a crucial aspect when working with UTC for the Sinch API.
Capture the user's time zone on the frontend (using Intl.DateTimeFormat().resolvedOptions().timeZone
) and send this to the Next.js backend. Then use Luxon to parse the date/time string with the user's time zone. For the simplified approach used in this tutorial, the server's time zone is implicitly used for parsing, but this is not suitable if users are across multiple time zones. However, always convert to UTC using .toUTC().toISO()
before sending to Sinch.
You need your Sinch Project ID, Key ID, and Key Secret from the Sinch Customer Dashboard to initialize the Sinch SDK. You also need a 'FROM_NUMBER', which is your provisioned Sinch virtual number and the Sinch SMS API region (us
or eu
), usually corresponding to your account location.
Phone numbers sent to the Sinch API must be in E.164 format (e.g., +1234567890). The tutorial provides a basic prefix check but recommends using the google-libphonenumber
library (or similar) in production for robust validation.
The FROM_NUMBER is your provisioned Sinch virtual number and is the number that will appear as the sender of the SMS messages. This number must be configured in your Sinch account. Ensure it's in E.164 format and set it in your .env.local
file.
A suggested database schema includes fields for patientName
, doctorName
, appointmentTime
(stored in UTC), patientPhone
(E.164 format), reminderSentAt
(timestamp), sinchBatchId
(for tracking), and standard createdAt
/updatedAt
timestamps.
Yes, database persistence is recommended for production applications. Choose a database (e.g., PostgreSQL, MySQL), install an ORM/client (e.g., Prisma), define a schema, modify the API route to save/update appointments, and handle data access. This will ensure the SMS will still be sent if the web app is down during the scheduled time.
Implement robust input validation (especially for phone numbers), add user authentication/authorization to protect the API route, use rate limiting, secure environment variables, and keep dependencies up-to-date.
Minimize API route cold starts by initializing clients outside the handler. Optimize database queries if you're using a database for persistence. Keep frontend bundle sizes reasonable and consider caching where appropriate. If possible, use a dedicated server rather than Serverless functions to avoid cold starts.
Use Vercel Analytics (if deploying on Vercel), monitor SMS delivery status in the Sinch Dashboard, implement structured logging, integrate error tracking services like Sentry, and set up uptime monitoring.
Learn how to build an automated SMS appointment reminder system using the Sinch SMS API and Next.js. This complete tutorial demonstrates scheduling SMS messages with the Sinch
send_at
parameter, implementing OAuth2 authentication, managing time zones with Luxon, and deploying a production-ready notification system. You'll create an application that schedules appointment reminders hours or days in advance using the Sinch Batches API with proper UTC time conversion.This step-by-step guide covers everything from initial project setup through production deployment, showing you how to integrate the Sinch Node.js SDK with Next.js API routes for automated patient appointment reminders.
Important: Sinch SMS API Authentication Methods
Sinch SMS API supports two authentication methods (Source: @sinch/sdk-core npm documentation, verified January 2025):
us
) and EU (eu
) regions only. RequiresprojectId
,keyId
, andkeySecret
.br
), Canada (ca
), and Australia (au
). RequiresservicePlanId
,apiToken
, andsmsRegion
.This tutorial uses OAuth2 authentication (Option 1). If you need other regions (BR, CA, AU), use API Token authentication instead. Find API Token credentials in the Sinch dashboard under "Service APIs."
What You'll Build: SMS Appointment Reminder System
You'll build a Next.js web application that allows administrative users to schedule SMS appointment reminders through a simple form interface. Users input patient appointment details (name, doctor, date/time, mobile number), and the application uses the Sinch SMS API to automatically send an SMS reminder two hours before each appointment.
Problem Solved: This automated appointment reminder system reduces patient no-shows and eliminates manual reminder calls, improving operational efficiency for medical practices, salons, and service businesses. Learn more about SMS best practices for appointment reminders and 10DLC registration requirements for high-volume messaging.
Technologies Used:
@sinch/sdk-core
): Sends and schedules SMS messages programmatically. Chosen for its direct scheduling capability (send_at
parameter).Architecture:
sms.batches.send
method with thesend_at
parameter.Understanding the Sinch send_at Parameter for Scheduled SMS
The Sinch Batches API provides native SMS scheduling functionality, eliminating the need for external job schedulers or cron jobs. Key parameters (Source: Sinch SMS API Batches documentation, verified January 2025):
send_at
: ISO-8601 formatted datetime string in UTC (format:YYYY-MM-DDThh:mm:ss.SSSZ
). If set in the future, message delivery is delayed until this time. If set in the past, messages send immediately. Must be beforeexpire_at
.expire_at
: ISO-8601 formatted datetime string in UTC. The system stops attempting delivery at this point. Default: 3 days aftersend_at
. Maximum: 3 days aftersend_at
. Must be aftersend_at
.to
: Array of phone numbers in E.164 format. Can contain 1 to 1,000 recipients per batch.body
: Message content. Maximum 2,000 characters. Messages exceeding standard SMS length (160 chars GSM-7, 70 chars Unicode) are automatically split into segments.Outcome: A functional web application that accepts appointment details and schedules SMS reminders via Sinch.
Prerequisites:
FROM_NUMBER
.Recommended Node.js Version: Node.js 20.x or 22.x (LTS versions for 2025). Node.js 18.x enters end-of-life in April 2025 and should not be used for new projects.
Step 1: Next.js Project Setup and Dependencies
Start by creating a new Next.js project and installing the Sinch SDK and time zone management libraries. For developers working with other SMS providers, you might also reference our guides on Twilio SMS integration and MessageBird implementation patterns.
Create Next.js App: Open your terminal and navigate to the directory where you want to create your project. Run:
Follow the prompts (you can accept the defaults). This guide uses TypeScript for better type safety.
Navigate to Project Directory:
Install Dependencies: Install the Sinch SDK and Luxon for date/time manipulation.
@sinch/sdk-core
: The official Sinch Node.js SDK.luxon
: For robust date/time handling and time zone conversions.@types/luxon
: TypeScript definitions for Luxon.Environment Variables: Create a file named
.env.local
in the root of your project. This file stores sensitive credentials and configuration securely. Never commit this file to Git.Obtaining Sinch Credentials:
Project ID
.Key ID
andKey Secret
. Store the Key Secret securely; you won't see it again.FROM_NUMBER
. Ensure it's in E.164 format (e.g.,+12025550181
).us
oreu
). This usually corresponds to where you set up your account.Populate
.env.local
:Security: Protect Your Credentials
Add
.env.local
to your.gitignore
file immediately to prevent committing sensitive credentials:Next.js automatically excludes
.env.local
from Git, but verify your.gitignore
includes this pattern. Never commit API credentials to version control.Explanation:
SINCH_PROJECT_ID
,SINCH_KEY_ID
,SINCH_KEY_SECRET
: Used to authenticate with the Sinch API via the SDK.SINCH_FROM_NUMBER
: The phone number that appears as the sender of the SMS.SINCH_SMS_REGION
: Specifies the Sinch regional endpoint (important for data residency and performance).DEFAULT_COUNTRY_CODE
: A helper for potentially formatting recipient numbers if entered without a country code (implementation varies).Project Structure (Simplified): Next.js provides a standard structure. You'll primarily work within:
pages/
: Contains frontend pages and API routes.pages/index.tsx
: Your main scheduling form page.pages/api/schedule.ts
: Your API route to handle form submission and interact with Sinch.public/
: For static assets (images, CSS if not using modules/tailwind)..env.local
: Environment variables (created above).styles/
: For global styles or CSS modules.Step 2: Creating the Appointment Scheduling Form
Create the user interface for scheduling appointments using basic React state management and standard HTML form elements.
Clear Default Content: Open
pages/index.tsx
and replace its content with the following structure.Implement the Form:
Add Basic Styling (
styles/Home.module.css
): Create or modifystyles/Home.module.css
with some basic styles. This mirrors the intent of the original tutorial's CSS but uses CSS Modules.Explanation:
useState
hooks to manage input values for each form field and track loading/status messages.handleSubmit
triggers on form submission. It prevents the default form action, sets loading state, and makes afetch
request to the backend API route (/api/schedule
).Step 3: Building the SMS Scheduling API Route
This is the core server-side logic where you interact with the Sinch SDK.
Create API Route File: Create the file
pages/api/schedule.ts
.Implement the Handler:
Explanation:
req.body
.DateTime
object. Calculates the reminder time (2 hours prior) and validates that this reminder time is still in the future (at least 5 minutes from now).SinchClient
using environment variables. A runtime check is included for critical variables.localReminderDateTime
to UTC (.toUTC().toISO()
) before passing it to thesend_at
parameter in thesinchClient.sms.batches.send
call. Sinch expectssend_at
in UTC.try...catch
block to handle errors during validation_ date processing_ or the Sinch API call. It logs the error and returns a 500 status with an informative message. It attempts to extract specific error details if available from the Sinch SDK error structure.Step 4: Integrating Sinch SMS API with send_at Parameter
The primary integration happens within the API route (
pages/api/schedule.ts
):SinchClient
is initialized using yourPROJECT_ID
_KEY_ID
_ andKEY_SECRET
from.env.local
.sinchClient.sms.batches.send()
method is the key function call.to
: An array containing the recipient phone number(s) in E.164 format.from
: Your provisioned Sinch number from.env.local
.body
: The text message content.send_at
: The critical parameter for scheduling. Must be an ISO 8601 formatted string in UTC. Luxon's.toUTC().toISO()
provides this correctly.Security of Credentials: By storing keys in
.env.local
and accessing them viaprocess.env
_ Next.js ensures they're only available server-side (in the API route) and not exposed to the client browser. Ensure.env.local
is in your.gitignore
file.Step 5: Implementing Error Handling and Logging
try...catch
block inpages/api/schedule.ts
is the primary error handler.SinchClient
during the API call (e.g._ invalid credentials_ malformed request_ insufficient funds_ invalid 'to' number).console.error
logs on the server side (visible in your terminal or Vercel logs).handleSubmit
function inpages/index.tsx
catches errors during thefetch
call (network issues) or uses the response status (!response.ok
) to identify API-level errors returned from the backend. It displays user-friendly messages.async-retry
) within the API route for transient network errors or specific Sinch error codes that indicate temporary issues. Be cautious not to retry errors related to invalid input or permanently failed states.pino
orwinston
to structure logs_ set different levels (debug_ info_ warn_ error)_ and potentially send logs to external services (e.g._ Datadog_ Logtail).6. Database Schema and Data Layer (Enhancement)
The current implementation doesn't persist appointments. Reminders are scheduled directly with Sinch based on transient form input.
To add persistence (Recommended for Production):
Choose a Database: PostgreSQL_ MySQL_ MongoDB_ etc. Serverless options like Vercel Postgres or Neon are good fits for Next.js.
Install ORM/Client: Prisma (recommended for type safety)_ Drizzle ORM_
node-postgres
_mysql2
_ etc.Define Schema: Create tables/collections (e.g._
Appointments
).Modify API Route:
sinchClient.sms.batches.send
call_ update the appointment record with thesinchBatchId
and perhaps a status indicating the reminder is scheduled.Data Access Layer: Use your chosen ORM/client within the API route to interact with the database (create_ read_ update).
Migrations: Use tools like
prisma migrate dev
to manage schema changes.7. Security Features
google-libphonenumber
or a strict regex to validate E.164 format (as recommended in Section 3).DOMPurify
(if rendering user input) or escaping output are generally good practices. For data used in API calls (like phone numbers)_ ensure the format is strictly controlled.required
attributes andtype
attributes provide basic validation but should not be relied upon for security./api/schedule
endpoint.rate-limiter-flexible
or Vercel's built-in IP rate limiting.SINCH_KEY_SECRET
) out of the codebase and client-side bundles using.env.local
. Perform runtime checks for critical variables in the API route.npm update
oryarn upgrade
) to patch known vulnerabilities.Managing Time Zones for SMS Scheduling
Time zones are critical for scheduling.
Problem: The user enters a date and time in their local time zone. The Sinch API's
send_at
parameter requires UTC. The server running the API route might be in a different time zone altogether.Solution (using Luxon in API Route):
<input type="date">
and<input type="time">
capture local date/time strings based on the user's browser/OS settings.America/New_York
) on the frontend (usingIntl.DateTimeFormat().resolvedOptions().timeZone
) and send it to the backend. Then parse using that zone:DateTime.fromISO(isoString, { zone: userTimeZone })
.DateTime.fromISO(${appointmentDate}T${appointmentTime})
implicitly uses the server's time zone if no zone info is in the string. This works if users are in the same timezone as the server, but is brittle for wider audiences.minus({ hours: 2 })
) on the Luxon object..toUTC().toISO()
before sending thesend_at
value to Sinch.Recommendation: For robust applications, capture and use the user's specific time zone during parsing. While the simplified approach is used here for demonstration, capturing the user's timezone explicitly on the frontend and sending it to the backend is the best practice for production applications. Clearly document the chosen assumption (server's local time for parsing input).
9. Performance Optimizations (Less Critical Here)
For this simple application, performance bottlenecks are unlikely. However, for scaling:
SinchClient
outside the handler function scope (as done in the example) so they persist between invocations (within limits).appointmentTime
).10. Monitoring, Observability, and Analytics
Testing Your SMS Appointment Reminder System
Test your implementation thoroughly before production deployment.
Unit Tests:
Integration Tests:
Manual Testing:
Deploying to Production
Deploy your Next.js application to production.
Vercel (Recommended):
.env.local
).Other Platforms:
Post-Deployment Checklist:
Conclusion
You've built a complete SMS appointment reminder system using Sinch and Next.js. The application schedules SMS messages using the
send_at
parameter, handles time zone conversions with Luxon, and provides a clean user interface for appointment management.Next Steps: