Appointment reminder email: schedule it yourself, send it by API
Updated
A reminder only works if it arrives at the right time and reflects the current state of the booking. Both depend on your code, not your email provider.
When it fires
At fixed offsets before the appointment, commonly 24 hours and 1 hour. The Inboxili transactional API sends immediately when called. There is no send_at field, so you own the scheduling.
Workflow
- When a booking is created, enqueue delayed jobs at the offsets you want, each carrying only the booking ID.
- When the job runs, reload the booking.
- If it is cancelled, rescheduled or in the past, exit without sending.
- Otherwise send the email.
Reloading at send time is the whole trick. It means cancellations need no queue surgery.
Implementation (Node.js with BullMQ)
import { Queue, Worker } from "bullmq";
const connection = { host: process.env.REDIS_HOST };
export const reminders = new Queue("reminders", { connection });
export async function scheduleReminders(booking) {
for (const hoursBefore of [24, 1]) {
const delay = booking.startsAt.getTime() - hoursBefore * 3_600_000 - Date.now();
if (delay > 0) {
await reminders.add("send", { bookingId: booking.id, hoursBefore }, { delay, jobId: `${booking.id}:${hoursBefore}` });
}
}
}
new Worker(
"reminders",
async ({ data }) => {
const booking = await db.bookings.find(data.bookingId);
if (!booking || booking.status !== "confirmed" || booking.startsAt < new Date()) return;
const res = await fetch("https://api.inboxili.com/api/v1/transactional/send", {
method: "POST",
headers: { Authorization: `Bearer ${process.env.INBOXILI_API_KEY}`, "Content-Type": "application/json" },
body: JSON.stringify({
to: booking.email,
from_email: "bookings@yourdomain.com",
from_name: "Acme Clinic",
subject: "Reminder: your appointment on {{date}}",
html_body: "<p>Hi {{name}}, this is a reminder of your appointment on {{date}} at {{time}}. Need to change it? {{manage_url}}</p>",
template_data: {
name: booking.firstName,
date: booking.startsAt.toLocaleDateString("en-GB", { timeZone: booking.timeZone }),
time: booking.startsAt.toLocaleTimeString("en-GB", { timeZone: booking.timeZone, hour: "2-digit", minute: "2-digit" }),
manage_url: `https://app.yourdomain.com/bookings/${booking.id}`,
},
}),
});
if (res.status === 429) throw new Error("rate limited"); // BullMQ retries with backoff
if (!res.ok) throw new Error(`Inboxili ${res.status}`);
},
{ connection, limiter: { max: 100, duration: 60_000 } }
);
The limiter keeps the worker under the API's 120 requests per minute per key.
Sample email
Subject: Reminder: your appointment on 12/10/2026 Hi Ada, this is a reminder of your appointment on 12/10/2026 at 14:30. Need to change it? Manage booking.
Things that go wrong
- Time zones. Store UTC, format in the booking's time zone, and say the zone in the email.
- Reminders for cancelled bookings. Always reload before sending.
- Daylight saving. Compute offsets from the UTC instant, not from local wall-clock time.
- Retry duplicates. A retry after a timeout can send twice. For reminders this is acceptable, for anything financial it is not.
Delivery events
A bounced reminder is useful to the business: phone the customer, since a no-show is costly.
Deliverability notes
Reminders are expected and short. Keep one clear action, a link to manage the booking, and no promotions.
Frequently asked questions
- Does the API schedule emails for later?
- No. POST /transactional/send sends immediately. Schedule the call in your own job queue or cron.
- Should I use automation journeys for reminders?
- Journeys are built for marketing and lifecycle sequences driven by contacts and events in the dashboard. For per-appointment reminders tied to your own database, a scheduled API call is more direct.
Send an appointment reminder
Create a workspace, verify a domain, and make your first API call.