Use cases

iMessage appointment reminders that clients confirm with a tapback

ZeroSettle lets a salon, studio or home-service business send appointment reminders as iMessages from its own number and have clients confirm with a 👍 tapback, which reaches your webhook as reaction.received about 1–2 seconds after it reaches your number. Your booking system decides when to send, because ZeroSettle doesn't schedule messages. On the $49-a-month Pro plan, clients who texted you first and ongoing threads are never limited; first reminders to new clients count against 5 new contacts a day per number.

Key takeaways

  • What it is: reminders sent from your business's own number on real iMessage, running on Apple hardware we operate for you in a server facility, through ZeroSettle's REST API and signed webhooks.
  • How a client confirms: a 👍 on the reminder arrives as reaction.received with target_id set to that reminder's id, so one tap confirms one specific appointment.
  • Price: Pro is $49/month for one number; Growth, for several numbers working as one, is from $599/mo. Messaging is unlimited, with no per-message fees.
  • Main limit: on Pro, first messages to 5 new contacts a day per number (50 on Growth). Clients who texted first never count as new contacts; on every plan, two unanswered messages to one client pause sends until they reply.
  • What it does not do: no SMS fallback for Android clients, no scheduled sends, and no protected health information without a signed BAA.

Who uses iMessage for appointment reminders?

Businesses that book one-to-one appointments and lose money to no-shows: salons, barbers, spas, fitness and yoga studios, tutors, pet groomers and home-service companies. ZeroSettle's terms don't allow protected health information without a signed Business Associate Agreement, so HIPAA-covered healthcare providers should check with counsel before sending patient reminders (see when ZeroSettle is not a fit).

ZeroSettle is an iMessage API: a real phone number on real iMessage, running on Apple hardware we operate for you, that your code texts customers from and hears back on through a bearer token and signed webhooks. Reminders are one of the scheduling jobs among ZeroSettle's use cases.

The tapback replaces "Reply C to confirm". The client double-taps the reminder and picks 👍, with nothing to type, and Apple attaches each Tapback to a specific message, so the tap tells your code which reminder was confirmed. Messages also lets people react with any emoji or sticker, but ZeroSettle's reaction types are the six classic tapbacks (love, like, dislike, laugh, emphasize and question), so the reminder asks for 👍 by name.

How iMessage for appointment reminders works, step by step

A nightly job sends each reminder, the client's 👍 confirms it through your webhook, and text replies go to a person; ZeroSettle carries the messages and the booking logic stays in your system.

  1. Get the client to text first. A button on the booking confirmation page opens an sms: link to your number with "Remind me: Lena K" prefilled; a front-desk QR code does the same with "Remind me:" for in-person bookings. Your handler records that text as the client's opt-in. A client who sends it never counts as a new contact, can tap links you send later, and won't have your reminder screened into Unknown Senders on iOS 26. See getting customers to text you first, or the same QR pattern on a restaurant waitlist.
  2. Send from a nightly job. ZeroSettle doesn't schedule sends, so each evening your booking system takes every appointment in the next 48 hours with no reminder yet, soonest first, and calls POST /api/imessage/send with the staff name, date, time, service and "Tap 👍 to confirm or reply to reschedule." Most go out 24–48 hours ahead. Save the returned id on the appointment.
  3. The 👍 confirms. It reaches your signed webhook as reaction.received about 1–2 seconds after it reaches the line, with message.target_id set to the reminder's id. Your handler marks that appointment confirmed through the booking API.
  4. Text replies go to the front desk. "Can we do Friday?" arrives as message.received; staff answer from the ZeroSettle console and move the booking.
  5. Online bookers can skip step 1. A client who ticked a text-consent box when booking can get the first reminder without texting first. It counts once against the new-contact allowance (Pro: 5 a day per number), any link in it isn't tappable, and Screen Unknown Senders can file it in a separate list with no notification.

Matching on target_id beats parsing a typed code: a client with two bookings gets two reminders, and a 👍 on one confirms only that one. The API reports that a reminder left the line (message.sent), never that it was read, so a 👍 or a reply is your only sign the client saw it. What an iMessage API can send has more on tapbacks.

Example conversation: appointment reminders

Two threads from one week's job: Lena confirms with a single tap, and Omar reschedules by text.

  1. Fri 4:12 PM
  2. Client: Remind me: Lena K
  3. Sun 5:30 PM
  4. Business: Hi Lena, it's Studio Ana. You're booked with Ana on Tue Oct 13 at 9:30 AM for a cut and gloss. Tap 👍 to confirm or reply to reschedule.👍
Illustrative example with a fictional studio. Blue bubbles come from the studio's number; gray bubbles are the client's. Lena texted first from the link on her booking confirmation, so her reminder never counted as a new contact.
  1. Tue 5:30 PM
  2. Business: Hi Omar, it's Studio Ana. You're booked with Jade on Thu Oct 15 at 3:00 PM for a cut. Tap 👍 to confirm or reply to reschedule.
  3. Client: can we do friday instead
  4. Business: Friday has 10:15 or 1:45. Which works?
  5. Client: 1:45
  6. Business: Moved to Fri 1:45 with Jade. See you then!
Illustrative example. Omar's reply arrived as message.received, and the front desk answered from the console.

Omar's freed Thursday slot is what a cancellation waitlist over iMessage fills, and after Lena's visit the same thread can carry a review request.

Setup: a nightly reminder job and a tapback handler

The integration is two small programs: a job that sends each reminder and saves its id, and a handler that verifies ZeroSettle's signature and matches each 👍 to that id. Both follow the docs' send and webhook sections; "your booking API" marks your own code.

python
# remind.py: run each evening from cron, e.g.  30 17 * * *  python3 remind.py
import os, time
import requests

BASE = "https://portal.zerosettle.io"
AUTH = {"Authorization": f"Bearer {os.environ['ZEROSETTLE_TOKEN']}"}

def send(to: str, text: str) -> dict:
    for _ in range(5):
        r = requests.post(f"{BASE}/api/imessage/send", json={"to": to, "text": text},
                          headers=AUTH, timeout=90)
        body = r.json()
        if r.status_code == 200:
            return body
        err = body["error"]
        if err["code"] == "line_busy":
            time.sleep(2)                       # one send at a time per line
        elif err["code"] == "new_contact_limit" and err.get("window") == "15m":
            time.sleep(err["retry_after_s"])    # 3 new contacts per 15 min on Growth
        elif err["code"] in ("phone_offline", "phone_timeout"):
            time.sleep(30)                      # the line is briefly unreachable
        else:
            break
    return body

for appt in unreminded_appointments(within_hours=48):   # your booking API, soonest first
    res = send(appt.phone, f"Hi {appt.first_name}, it's Studio Ana. You're booked with {appt.staff} "
                           f"on {appt.starts:%a %b %-d at %-I:%M %p} for {appt.service}. "
                           "Tap 👍 to confirm or reply to reschedule.")
    if res.get("ok"):
        save_reminder_id(appt, res["id"])                # the 👍 comes back with this target_id
    elif res["error"]["code"] != "new_contact_limit":   # a full day waits for tomorrow's run
        email_reminder(appt)                             # opted out, awaiting a reply, failed, ...
python
# confirmations.py: run with  uvicorn confirmations:app --port 8100
import hashlib, hmac, json, os, time
from fastapi import BackgroundTasks, FastAPI, Request, Response

app = FastAPI()
SECRET = os.environ["ZEROSETTLE_WEBHOOK_SECRET"]
seen: set[tuple[int, str]] = set()          # use your database in production

def verify(header: str, raw: bytes, tolerance: int = 300) -> bool:
    try:
        parts = dict(p.split("=", 1) for p in header.split(","))
        t, v1 = int(parts["t"]), parts["v1"]
    except Exception:
        return False
    if abs(time.time() - t) > tolerance:
        return False
    expected = hmac.new(SECRET.encode(), f"{t}.".encode() + raw, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, v1)

def handle(event: str, m: dict) -> None:
    if event == "message.received":
        if m["text"].lower().startswith("remind me"):
            record_opt_in(m["handle"], m["ts_ms"])     # your booking API: consent on record
        else:
            notify_front_desk(m)                       # "can we do friday instead"
        return
    appt = appointment_by_reminder_id(m["target_id"])  # your booking API
    if appt and m.get("reaction") in ("like", "love"):
        appt.confirm()                                 # 👍 or ❤️ on the reminder
    elif appt:
        notify_front_desk(m)                           # 👎, ❓, a removed 👍

@app.post("/webhook")
async def webhook(req: Request, tasks: BackgroundTasks):
    raw = await req.body()
    if not verify(req.headers.get("X-ZeroSettle-Signature", ""), raw):
        return Response(status_code=401)
    ev = json.loads(raw)
    m = ev.get("message")
    if m is None or ev["event"] not in ("reaction.received", "message.received"):
        return {"ok": True}                    # your own sends echo back as message.sent
    if (m["id"], ev["event"]) in seen:
        return {"ok": True}                    # retries carry the same id and event
    seen.add((m["id"], ev["event"]))
    tasks.add_task(handle, ev["event"], m)
    return {"ok": True}

Register the handler with PUT /api/imessage/webhook and store the secret it returns, which is shown once. ZeroSettle retries a non-2xx or a reply slower than 10 seconds after 1, 5 and 20 seconds, hence the background task and the dedupe (webhooks explained). A line sends one message at a time, so at 2–5 seconds per send, 40 reminders to existing threads take roughly 80 to 200 seconds.

Limits and consent for appointment reminders

Clients who texted your number first never count against ZeroSettle's new-contact limits; only a first reminder to a client who hasn't is a new contact, at 5 a day per Pro number. On every plan, two unanswered reminders to one client pause sends until they reply (a 👍 counts as a reply), so a client who never answers gets the email fallback in the job above.

ZeroSettle limits that touch appointment reminders, per number (API docs, checked October 12, 2026)
SituationProGrowth
Client who texted firstNever a new contactNever a new contact
First reminders to online bookers per day (3 AM to 3 AM Eastern)550
First reminders per rolling 15 minutes—3
Unanswered reminders to one client2, then 403 awaiting_reply2, then 403 awaiting_reply

Limit math for a busy Wednesday

Say Wednesday has 35 daytime appointments with online bookers who have never texted the studio. On Pro, Monday's 5:30 PM run reaches 5; the other 30 get 429 new_contact_limit for the day, and Tuesday's run reaches only 5 more. Pro fits a studio whose clients mostly text first from the confirmation link. If weeks like that are normal, push more clients to the text-first link or move to Growth: each Growth number allows 50 first reminders a day and 3 per 15 minutes, and new contacts go to whichever number has room, so three numbers send those 35 in under an hour.

  • Consent: the client texted the line, or ticked a booking-form checkbox and you stored the timestamp. Your first message says who you are ("it's Studio Ana").
  • Quiet hours: a reminder about a booked appointment is generally treated as transactional, while rebooking nudges ("six weeks since your last cut") are generally marketing, which in the US usually needs prior express written consent. ZeroSettle's docs ask that nothing promotional go out before 8 am or after 9 pm in the client's time zone. This isn't legal advice; check your own situation with counsel.
  • STOP: a text with the word STOP in it, or a clear request like "take me off your list", opts the client out on every number on the account, and later sends return 403 recipient_opted_out, so the job emails them instead (how STOP works).

When ZeroSettle is not a fit for appointment reminders

ZeroSettle is the wrong tool for appointment reminders in four cases.

  • HIPAA-covered providers (dentists, clinics, therapists): ZeroSettle's terms prohibit protected health information without a signed Business Associate Agreement with ZeroSettle. Whether a given reminder carries PHI is a question for your counsel; this isn't legal advice.
  • Clients on Android need reminders too: there's no SMS fallback, so keep your booking app's SMS or email reminders for them.
  • You want ZeroSettle to schedule sends: it doesn't; your booking system or a cron job calls the API.
  • More than 5 first-time reminders a day from one Pro number: use the text-first QR route or Growth.

How much does iMessage for appointment reminders cost?

Appointment reminders on ZeroSettle cost the plan price with no per-message fees; messaging is unlimited on both plans.

ZeroSettle plans for appointment reminders (zerosettle.io, checked October 12, 2026)
PlanPriceFits
Pro$49/monthOne number, 5 new contacts a day, unlimited messages with people who message you first; no setup fee, 30-day money-back guarantee
GrowthFrom $599/moSeveral numbers working as one; through "Book a demo"

Code you run yourself uses no AI credits. Details: ZeroSettle pricing.

Frequently asked questions

Does a 👍 tapback count as a reply for the unanswered rule?

Yes. Anything the client sends back, including a tapback, resets the two-unanswered-messages count, on Pro and Growth alike.

Can I put a reschedule link in the reminder?

Yes, but it is tappable only once the client has texted your number or saved it as a contact. Before that, an iPhone shows the link as plain text, and the API still sends it with a link_not_tappable warning. Clients who texted first from the confirmation link or the QR code can tap links already (why first-message links aren't tappable).

What happens if a client removes their 👍?

The removal arrives as another reaction.received on the same target_id, with reaction set to remove_like. The handler above sends it to the front desk rather than silently un-confirming.

Will clients see my studio's name or just a number?

Set a name (up to 64 characters) and a photo once, in the console or with PUT /api/imessage/profile, and ZeroSettle shares that card with your first message to each client. They see "Studio Ana shared a name and photo with you" and can save it, which also makes links you send them tappable.

Sources

  1. ZeroSettle iMessage API docs: "Your iMessage number" (pricing, more than one number), "Send a message" (including "Reactions (tapbacks)", "Links in a first message" and "Name and photo"), "Customer support" (sms: links), "Webhooks, step by step" and "Errors, limits, and compliance". Checked October 12, 2026.
  2. ZeroSettle plans, zerosettle.io (Pro at $49/mo with no setup fee and a 30-day money-back guarantee; Growth from $599/mo through "Book a demo"). Checked October 8, 2026.
  3. ZeroSettle iMessage API Terms of Service, section 13 (opt-in consent, protected health information and BAAs). Last modified September 28, 2026; checked October 8, 2026.
  4. React with Tapbacks in Messages on iPhone, iPhone User Guide, Apple Support. Checked October 8, 2026.
  5. Screen and filter text messages on iPhone, iPhone User Guide for iOS 26, Apple Support. Checked October 8, 2026.

Written by the ZeroSettle team. ZeroSettle is an iMessage API: a real phone number on real iMessage, running on Apple hardware we operate for you, that your code texts customers from and hears back on through a bearer token and signed webhooks.

Drafted with AI assistance from ZeroSettle's API docs and iMessage API Terms and Apple's iPhone User Guide, as of October 8, 2026.

Text your customers from your own iMessage number

ZeroSettle gives your business its own phone number on real iMessage, running on Apple hardware we operate for you in a server facility. Your code sends with one API call and hears every reply on a signed webhook.

Pro is $49 a month with a 30-day money-back guarantee. Growth, for several numbers working as one, is from $599/mo.