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.
This guide provides a complete walkthrough for building a web application using Next.js and Node.js that allows users to schedule SMS appointment reminders via the Sinch SMS API. We will cover everything from project setup to deployment and verification.
Project Overview and Goals
We aim to build a simple web application where an administrative user can input patient appointment details (patient name, doctor name, appointment date/time, patient mobile number). The application will then use the Sinch SMS API to automatically schedule and send an SMS reminder to the patient two hours before their appointment.
@sinch/sdk-core
): Enables sending and scheduling SMS messages programmatically. Chosen for its direct scheduling capability (send_at
parameter).sms.batches.send
method with thesend_at
parameter.FROM_NUMBER
.1. Setting up the Project
Let's initialize our Next.js project and install the necessary dependencies.
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). We're using TypeScript for better type safety.
Navigate to Project Directory:
Install Dependencies: We need 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; it won't be shown again.FROM_NUMBER
. Ensure it's in E.164 format (e.g.,+12025550181
).us
oreu
). This usually corresponds to where your account was set up.Populate
.env.local
: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 will appear as the sender of the SMS.SINCH_SMS_REGION
: Specifies the Sinch regional endpoint to use (important for data residency and performance).DEFAULT_COUNTRY_CODE
: A helper for potentially formatting recipient numbers if they are entered without a country code (implementation varies).Project Structure (Simplified): Next.js provides a standard structure. We'll primarily work within:
pages/
: Contains frontend pages and API routes.pages/index.tsx
: Our main scheduling form page.pages/api/schedule.ts
: Our 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.2. Building the Scheduling Frontend (
pages/index.tsx
)Let's create the user interface for scheduling appointments. We'll use 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.useState
hooks to manage the input values for each form field and to track loading/status messages.handleSubmit
is triggered on form submission. It prevents the default form action, sets loading state, and makes afetch
request to our backend API route (/api/schedule
).3. Implementing the Scheduling API (
pages/api/schedule.ts
)This is the core server-side logic where we interact with the Sinch SDK.
Create API Route File: Create the file
pages/api/schedule.ts
.Implement the Handler:
req.body
.DateTime
object. It calculates the reminder time (2 hours prior) and validates that this reminder time is still in the future (at least 5 minutes from now_ accommodating potential delays and preventing past scheduling).SinchClient
using environment variables. A note about the non-null assertion operator (!
) and the recommendation for runtime checks in production is added. An example 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. This is essential as 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.4. Integrating with Sinch (Covered in API Route)
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 are only available server-side (in the API route) and not exposed to the client browser. Ensure.env.local
is added to your.gitignore
file.5. Error Handling and Logging (Basic Implementation)
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
is used for logging 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. However_ 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 does not persist appointments. Reminders are scheduled directly with Sinch based on transient form input.
To add persistence (Recommended for Production):
node-postgres
_mysql2
_ etc.Appointments
).sinchClient.sms.batches.send
call_ update the appointment record with thesinchBatchId
and perhaps a status indicating the reminder is scheduled.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.8. Handling Special Cases (Time Zones)
Time zones are critical for scheduling.
send_at
parameter requires UTC. The server running the API route might be in a different time zone altogether.<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 okay 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.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
DLRs
) if configured.