---
title: "How to send appointment reminders by SMS from an AI agent"
description: "Let an AI agent text appointment reminders: consent on record, a registered use case, a fixed template, the local hour, and one idempotency key per reminder."
url: https://www.unitpost.com/blog/agents/ai-agent-sms-appointment-reminders
section: Blog
updated: 2026-11-02
---
# How to send appointment reminders by SMS from an AI agent

Before an AI agent texts a reminder, record the person's consent to appointment texts and register the use case (a 10DLC campaign or toll-free verification). Then keep the agent's wording to the registered samples by filling a fixed template, schedule each text for a sensible hour in the appointment's time zone, send it with one idempotency key per reminder, and route replies back through an sms.received webhook with STOP already handled.

## How to send appointment reminders by SMS from an AI agent

> Let an AI agent text appointment reminders: consent on record, a registered use case, a fixed template, the local hour, and one idempotency key per reminder.

An agent that books appointments can also remind people about them. The text itself is short. What goes wrong is everything around it: a reminder to someone who never agreed to texts, wording that doesn't match what carriers approved, a text at 5:40 in the morning, two copies after a retry, and a STOP reply the model tries to answer.

Each of those has a fix that lives in code, not in the prompt.

### Record consent before the first reminder

The CTIA Messaging Principles (https://api.ctia.org/wp-content/uploads/2023/05/230523-CTIA-Messaging-Principles-and-Best-Practices-FINAL.pdf), the wireless industry's guidelines for business texting, put appointment reminders in the informational category. The person gives you their number and agrees to texts for that purpose, and that express consent comes before the first text. It covers reminders. It doesn't cover promotions.

Unitpost doesn't make that check for you on a reminder. A transactional text to a number you've never stored still goes out, because only marketing needs consent on record. So the agent's send tool has to ask. Record the opt-in where it happens, on your booking form:

- `POST /contacts/{id}/sms-consent` with `action: "opt_in"`, `purpose: "transactional"` and a `source` such as `web_form`.
- `occurred_at` for when they agreed (backdate imported consent to the real moment; a future time is refused), `evidence_url` pointing at your own record, and `disclosure_version` for the checkbox wording they saw.

Then make the send tool read `GET /contacts/{id}/sms-consent` first and refuse when `consented_at` is empty or `status` isn't `active`. The model never decides whether someone agreed. Your tool does.

Unitpost's appointment reminder recipe words the checkbox "Send me Acme SMS appointment reminders.", leaves it unchecked by default, and states how often texts come.

> **Only people who booked:** A reminder goes to someone who made an appointment and agreed to texts about it. An agent never texts a number taken from a web page or a bought list. Unitpost's Acceptable Use Policy prohibits both on every plan.

### Register the use case and keep the agent inside it

US carriers treat every business text as application-to-person traffic, so the number needs a 10DLC campaign or toll-free verification before it sends. Unitpost's appointment reminder recipe registers a transactional program: `ACCOUNT_NOTIFICATION` on 10DLC, `APPOINTMENT` for toll-free, with no embedded links and no embedded phone numbers. Its sample messages read:

```bash
Acme: reminder: your appointment is tomorrow at 10:00. Reply STOP to opt out.
Acme: your appointment is in 1 hour. Reply STOP to opt out.
Acme: your appointment is confirmed. Reply STOP to opt out.
```

What the agent sends has to look like those. Unitpost's SMS Messaging Policy says it directly: what you send must look like the sample messages and use case you registered. A reminder that grows a "book your next cleaning and save 10%" line, or a link the registration said it wouldn't carry, is no longer the program carriers approved.

10DLC rules for AI-generated text messages (/blog/agents/10dlc-ai-generated-texts) covers what drift looks like and what to do when the use case really changes.

### Should the agent use a template or write the text?

Use a template for the reminder. `POST /sms` takes the finished `body` (up to 1,600 characters), so keep the wording in your code and let the agent supply only the fields: the date, the time, perhaps the location. Your code renders the text, so every reminder matches the registered sample, starts with your business name and ends with the opt-out line.

Free text belongs in replies, inside a conversation the person started. Even there, keep the agent on the subject of the appointment.

Two details a model tends to get wrong:

- Opt-out words. CANCEL and END are opt-out keywords on Unitpost numbers, just like STOP. "Reply CANCEL to cancel your appointment" opts the person out of every text you send, reminders included. Ask for a reply that isn't a keyword, such as 1 to confirm and 2 to reschedule.
- Characters. One emoji or curly quote switches a text from GSM-7 to UCS-2, which cuts a segment from 160 characters to 70, and texts are billed per segment. Check each template with the SMS segment calculator (/tools/sms-segment-calculator).

### Send it at a sensible hour in the recipient's time zone

Unitpost holds marketing texts for the recipient's local window, 8:00 to 21:00, narrower where a law or operator code sets it. A reminder is transactional, and transactional texts are never held: a reminder scheduled for 5:40 a.m. goes out at 5:40 a.m. Picking the hour is your job.

Use the appointment's time zone, meaning the clinic's or the shop's. Not the server's, and not a guess from the area code: someone with a number from another state still has the appointment where it is. Unitpost's quiet-hours check maps US area codes to zones and, when a code spans more than one, falls back to the hours that are inside the window in every US zone. That fallback protects marketing sends. It doesn't pick a time for your reminder.

`scheduled_at` makes you commit. It takes ISO 8601 with `Z` or an offset, such as `2026-10-27T08:00:00-07:00`, and a time with no zone is refused with a 422. Compute the instant in your code from the appointment record. Don't ask the model to do offset arithmetic, least of all across a daylight saving change.

### Use one idempotency key per reminder

Agents retry. A timeout, an ambiguous error or a second pass over the same task produces a second call. `POST /sms` takes an `Idempotency-Key` header, and the MCP `sms_send` tool takes `idempotency_key`. The rules:

- Same key and same body within 24 hours: the original message comes back, and no second text goes out.
- Same key with a different body: `409 invalid_idempotent_request`.
- Same key while the first request is still in flight: `409 concurrent_idempotent_requests`.
- A refused send releases the key, so the corrected request can reuse it.

Derive the key from the appointment and the reminder slot, for example `appt_8812-reminder-24h`, never from the attempt. The SDKs generate a key when you pass none, but that only covers the SDK's own network retries. An agent calling the tool again is a new call with a new key.

The 409 on a changed body matters with a model in the loop. If the agent rewords the reminder on a retry, the same key refuses it instead of sending a second, different text. That is the protection working. Don't answer it with a fresh key.

The window is 24 hours, and reminders are often scheduled days ahead. Store the returned `sms_…` id on the appointment and check it before scheduling another. When the appointment moves, change the text you already have: `PATCH /sms/{id}` with a new `scheduled_at` to move it, or `{ "cancel": true }` to drop it (`sms_update` in MCP).

### Route replies back to the agent

US toll-free, 10DLC and Canadian long-code numbers take replies. Alphanumeric Sender IDs don't. Subscribe a webhook to `sms.received` and each reply arrives with `from`, `to` (your number), `body`, `keyword`, `auto_replied`, `looks_like_opt_out`, `in_reply_to` (the reminder they answered, when the carrier says which) and `received_at`.

In the handler, before anything reaches the model:

- Verify the webhook signature on the raw body.
- Drop the event when `keyword` is `OPT_OUT` or `auto_replied` is true. STOP is already recorded and confirmed, and HELP already has its answer.
- When `looks_like_opt_out` is true ("please stop texting me"), send it to a person, not the agent.
- Otherwise load the conversation with `GET /sms?participant=<number>`, hand it to the agent, and answer with `sms.send` and no `from`, so the answer leaves from the number they texted.

A "1" becomes a confirmed appointment in your system. A "2" starts a reschedule. If the person opts out between booking and the reminder, the scheduled text is checked again when it's due, ends `suppressed` without a charge, and `sms.suppressed` fires.

How to handle STOP and HELP when an AI agent sends texts (/blog/agents/ai-agent-sms-stop-help) has the handler code and the opt-out rules in full.

## FAQ

### Do appointment reminders need consent if they aren't marketing?

Yes. CTIA's principles expect express consent for informational texts such as appointment reminders, given when the person provides their number and agrees to texts for that purpose. Unitpost requires consent on record only for marketing, so record the reminder opt-in yourself with POST /contacts/{id}/sms-consent and purpose transactional, and have the agent's send tool check it first.

### Will Unitpost hold a reminder that is scheduled for 6 a.m.?

No. Quiet hours hold marketing texts only, until the recipient's local window opens at 8:00. A transactional reminder goes out at the time you schedule, so compute a reasonable hour in the appointment's time zone in your code and pass scheduled_at with an explicit offset.

### What happens to a scheduled reminder if the person texts STOP first?

It doesn't go out. A scheduled text is checked again when it's due. After an opt-out it ends suppressed, isn't billed, and an sms.suppressed webhook fires with the reason opted_out. STOP covers every text from your workspace, reminders included.

## Related

- [How to handle STOP and HELP when an AI agent sends texts](https://www.unitpost.com/blog/agents/ai-agent-sms-stop-help): Keep STOP and HELP away from the model: handle keywords first, send one confirmation, honor plain-language opt-outs, log consent, check before every send.
- [How to let an AI agent answer text messages](https://www.unitpost.com/blog/agents/ai-agent-two-way-sms): Let an AI agent reply to texts safely: the sms.received webhook, messages it must not answer, opt-out words it shouldn't guess at, and when to hand off.
- [Does AI-written marketing email still need consent?](https://www.unitpost.com/blog/agents/ai-marketing-email-consent): AI-written marketing email still needs consent. The rules in the US, EU, UK and Canada, Gmail and Yahoo bulk sender rules, and where AI agents go wrong.
