Build and run an appointment reminder backend with Encore and send SMS through Bird. The example stores appointment and delivery state in PostgreSQL, finds due reminders with a cron-triggered API, moves sends through Pub/Sub, and verifies Bird delivery webhooks against their raw request bodies.
- PostgreSQL tables for appointments, reminders, and deduplicated delivery events
- A five-minute cron job that publishes due reminders to Pub/Sub
- A subscriber that rechecks cancelled, rescheduled, and expired appointments before sending
- Bird's official TypeScript SDK with a stable idempotency key for each reminder
- A raw webhook endpoint using Bird's Standard Webhooks signature verification
- Tests for cancellation and rescheduling decisions, the Bird SMS request, idempotency, and signed webhooks
Install the Encore CLI, then create the app from this example:
encore app create bird-reminders --example=ts/bird-sms-reminders
cd bird-reminders
npm install
encore runEncore starts the API, PostgreSQL, and Pub/Sub locally. Cron jobs do not fire automatically in the local environment, so call the dispatch endpoint directly while testing.
Create an appointment whose reminder is already due:
curl -X POST http://localhost:4000/appointments \
-H 'Content-Type: application/json' \
-d '{
"customerName": "Ada",
"phone": "+15005550006",
"startsAt": "2026-10-02T14:00:00Z",
"remindAt": "2026-10-01T14:00:00Z"
}'Use the returned appointment ID to cancel it, then dispatch due reminders:
curl -X POST http://localhost:4000/appointments/APPOINTMENT_ID/cancel
curl -X POST http://localhost:4000/reminders/dispatch
curl http://localhost:4000/appointments/APPOINTMENT_IDThe appointment should be cancelled, its reminder should be skipped, and its attempt count should remain zero.
Create a Bird API key, configure an SMS sender, and enable the destination country in your Bird SMS settings. A free-text SMS requires both a sender and a recipient; Bird's simulated recipient +15005550006 still requires an eligible US sender and is billed at the normal destination rate.
Store the credentials as Encore secrets:
encore secret set --type local BirdAPIKey
encore secret set --type local BirdSMSSenderCreate a due appointment without cancelling it, then call /reminders/dispatch. The Pub/Sub subscriber sends the SMS through bird.sms.send, marks the reminder as accepted, and stores the returned sms_… message ID; later delivery outcomes arrive through the webhook.
Create a Bird webhook endpoint for the SMS lifecycle events you need, point it at your deployed /webhooks/bird URL, and store the endpoint's whsec_… signing secret:
encore secret set --type local BirdWebhookSecretBird needs a publicly reachable HTTPS endpoint to deliver a real webhook, so local webhook verification is covered by the test suite:
npm test
encore checkThe handler verifies webhook-id, webhook-timestamp, and webhook-signature against the untouched request body. It uses webhook-id as the database key, making Bird's at-least-once webhook delivery safe to replay.