Let customers text the founder from your cancel flow, over iMessage
ZeroSettle lets an indie SaaS or subscription app add one optional step to its cancel flow, “Text the founder: we’ll fix it or refund you”, that opens Messages to the company’s own iMessage number with a reference like “Cancel ref 8812” prefilled. The customer texts first, so the conversation is never cold outreach and never counts as a new contact, and the founder answers from the ZeroSettle console or a Slack relay you build. The Cancel button stays right there and works without texting.
Key takeaways
- What it is: an optional “Text the founder” button on the last cancel step. It is an
sms:link to your ZeroSettle number with a short reference prefilled, and the founder replies in the same iMessage thread. - Who it fits: indie SaaS, subscription apps and membership businesses with a self-serve cancel flow and a founder who can reply within minutes.
- Main limit: none for the button, because the customer texts first. Only a save offer your backend sends to an opted-in user you have no conversation with yet counts as a new contact (Pro: 5 a day per number).
- The rule: the Cancel button stays on the same screen and finishes the cancellation without any text.
- Price: Pro is $49/month, starting with one number; Growth is from $599/mo. There are no per-message fees.
- What it does not do: there is no SMS fallback (the link opens the SMS app on Android), and the API reports no read receipts.
Who uses iMessage for cancel-flow saves?
Founder-run SaaS products, subscription apps and membership businesses with a self-serve cancel flow use a “Text the founder” step to hear why a customer is leaving while the problem can still be fixed. 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.
A cancel survey’s checkbox (“Something isn’t working”) arrives after the customer has gone, and nobody can ask what broke. A thread from the cancel screen gets the specific answer while a fix, a pause, a downgrade or a refund is still possible. See when it is not a fit, or other iMessage use cases.
How iMessage for cancel-flow saves works, step by step
The customer starts the conversation, not your server: an optional button opens Messages with a reference prefilled, ZeroSettle posts the text to your webhook, and the founder answers in the same thread.
- An optional “Text the founder: we’ll fix it or refund you” button sits beside a Cancel button that works without texting.
- Your server stores a short reference for the cancel attempt, and the button is an
sms:link to your number with “Cancel ref 8812” prefilled. - The customer taps Send. About 1–2 seconds after the line receives the text, ZeroSettle posts a signed
message.receivedevent to your webhook. - Your webhook ties the reference to the account and posts it to the founder in Slack through a relay you build. The thread is also live in the ZeroSettle console.
- The founder replies from the console or a Slack slash command that calls
POST /api/imessage/send: a quick fix, a pause, a downgrade or a refund, applied in billing first. - A voice note from the founder can follow through the same endpoint as
audio_b64, arriving as Messages’ native audio bubble. - If the customer still wants to leave, the founder cancels at once.
The other path: one save offer to opted-in users
Users who already opted in to texts can get one save offer from your backend instead: “Before you go: 2 months free if you stay. Reply YES.” It is the cancel-flow trigger in ZeroSettle’s retention offers docs, on the same backend that recovers paywall abandonment, with the same rules: one offer per user per week, the two free months created in billing before the text goes out, and no link until they reply. Because your server sends it first, quiet hours apply, and it counts as a new contact if you have never texted each other.
Example conversation: cancel-flow saves
In this thread a customer taps “Text the founder” on the cancel screen of Tidyslot, a fictional scheduling app, and the founder fixes the problem instead of cancelling.
- Today 2:14 PM
- Customer: Cancel ref 8812. The Google Calendar sync keeps breaking👍
- Business: Hey, Ravi here, I built Tidyslot. That’s on us. Did it break after you changed your Google password?
- Customer: yeah last week
- Business: Reconnect it at Settings > Calendar and it’ll stick. I’ve added a free month for the trouble. Still want me to cancel?
- Customer: no that fixed it, thanks❤️
Behind it, the relay put the 👍 on Dana’s first text the moment it arrived, ref 8812 resolved to her account and last week’s sync error, and the free month was applied in billing before Ravi mentioned it.
Setup: a prefilled sms: button and a reference lookup
Setup is three small pieces, following the customer support section of the ZeroSettle docs: the button, a reference lookup in your webhook, and a send call for a voice note.
The button
The button is a plain sms: link beside a Cancel form that works on its own (more in getting customers to text you first).
<a class="btn" href="sms:+18055550100&body=Cancel%20ref%208812">Text the founder: we'll fix it or refund you</a>
<form method="post" action="/billing/cancel"><button>Cancel subscription</button></form>
Keep the reference short, opaque and expiring; never an email, account ID or token, since the customer sees it. On an iPhone the link opens Messages; on Android it opens the SMS app, and ZeroSettle has no SMS fallback, so put an email or chat link beside it for those customers.
The webhook: tie the reference to the account
Start from the docs’ Slack relay, which verifies X-ZeroSettle-Signature, dedupes retries and adds the /imsg reply command (see routing iMessage support to Slack and how ZeroSettle webhooks work). The relay also puts a 👍 tapback on each text so the customer knows it landed. In its message.received branch, send this function’s output to Slack in place of the relay’s own text:
import re
REF = re.compile(r"\bcancel ref (\d{4,6})\b", re.I)
def cancel_context(m: dict) -> str:
text = m.get("text") or ""
found = REF.search(text)
# your DB: try the reference, then fall back to the sender's number
acct = (found and accounts.by_cancel_ref(found.group(1))) or accounts.by_phone(m["handle"])
flag = "number on file" if acct and acct.phone == m["handle"] else "CHECK IDENTITY"
who = acct.name if acct else "Unknown customer"
return f"*Cancel flow* {who} ({flag}):\n> {text}\n_reply with_ `/imsg {m['handle']} your answer`"
A reference is a lookup, not proof of identity: before a refund or plan change, check the flag or confirm by email.
The founder’s voice note
A voice note is one POST /api/imessage/send with audio_b64; this is the docs’ Python helper:
import base64, os, requests
BASE = "https://portal.zerosettle.io"
TOKEN = os.environ["ZEROSETTLE_TOKEN"] # your API token, never in source
def send_voice(to: str, path: str) -> dict:
with open(path, "rb") as f:
audio = base64.b64encode(f.read()).decode()
r = requests.post(f"{BASE}/api/imessage/send",
headers={"Authorization": f"Bearer {TOKEN}"},
json={"to": to, "audio_b64": audio, "audio_format": path.rsplit(".", 1)[-1]},
timeout=120)
return r.json()
send_voice("+18055559876", "founder-note.m4a")
Voice notes go through the API (the console sends texts and photos): up to 6 MB of m4a, mp3, wav, aiff, caf, flac, aac, mp4 or amr, which ZeroSettle converts. The call returns once the line has handed the note to Apple, a few seconds longer than a text. A voice bubble has no caption, so send words as a second message.
Limits and consent for cancel-flow saves
A conversation the customer starts from the cancel screen never counts against ZeroSettle’s new-contact limit, so Pro’s 5-a-day cap matters only for save offers your backend sends first. On consent, the customer chooses to text, and cancelling must never depend on it.
| Message | New contact? | Limit |
|---|---|---|
| Customer texts “Cancel ref 8812” | No | None; inbound is unlimited |
| Founder’s replies, voice note, tapbacks | No | Every plan: at most 2 unanswered messages until the customer replies (sends within 5 minutes count as one; tapbacks never count) |
| Save offer to an opted-in user with no conversation yet | Yes, once | Pro: 5 a day. Growth: 50 a day, 3 per 15 min |
Source: ZeroSettle API docs, “Errors, limits, and compliance”. The day runs 3 AM to 3 AM Eastern.
The limit math
Take a product on Pro with 15 cancel attempts a week. Button texts never count, and a “Before you go” offer to each opted-in canceller you have never texted adds about 2 new contacts a day, under Pro’s 5. A price-change spike of 30 cancels in one day is different: the 6th offer that day returns 429 new_contact_limit with retry_after_s and would wait for the 3 AM Eastern reset. By then the customer has left and quiet hours rule out the text, so drop offers that can’t go out within minutes instead of queueing them overnight.
Never block cancellation behind the conversation
Cancel has to work on its own, with no required text and no hidden or delayed button. In the US, the Restore Online Shoppers’ Confidence Act requires online sellers that bill through a negative option feature to provide “simple mechanisms for a consumer to stop recurring charges”. This is not legal advice.
- Save offers likely need consent: send them only to users who gave you their number for texts, and only between 8 am and 9 pm in their time zone.
- “Stop charging me” is an opt-out. Any text with the word STOP in it, or a clear request like “take me off your list”, blocks every number on your account from reaching that person until they text START (
403 recipient_opted_out), so the founder can’t reply in the thread: cancel and confirm by email. See how STOP works.
When ZeroSettle is not a fit for cancel-flow saves
A “Text the founder” step is the wrong tool when it slows cancelling, when no one can answer, or when customers can’t receive iMessage.
- You’d block cancellation behind the conversation. Required texting turns help into an obstacle.
- Nobody can answer quickly. A customer on the cancel screen waits minutes, not a day; show the button only when someone is on.
- Most users are on Android. There is no SMS fallback; keep email or in-app chat for them.
- You want a fully automated save bot. On lines the hosted AI agent (Beta) manages, inbound texts skip your webhook and your own sends are refused, so put the agent on a separate number or call your own model from the webhook. See hosted-agent win-backs.
- Subscriptions are billed through the App Store. Customers cancel in their Apple subscription settings and request refunds from Apple, not from you, so don’t promise a cancellation or refund the thread can’t deliver.
How much does iMessage for cancel-flow saves cost?
Cancel-flow saves cost the ZeroSettle plan price and nothing per message: Pro is $49 a month, starting with one number, and Growth is from $599/mo for several numbers working as one. Messages are unlimited; only new contacts are capped.
| Plan | Price | Includes |
|---|---|---|
| Pro | $49/month | One number to start, self-serve, no setup fee, 30-day money-back guarantee, 5 new contacts a day per number, unlimited messages with people who message you first |
| Growth | From $599/mo | 3+ numbers working as one, dedicated support, 50 new contacts a day per number; book a demo and ZeroSettle sends a signup link |
See plans or the pricing explainer, read the API docs, or get a number.
Frequently asked questions
Is a “Text the founder” step a dark pattern?
Not when it is optional and Cancel works without it. It becomes one when texting is required, when Cancel is hidden until after the chat, or when the founder’s reply is the only way out.
What if the customer deletes the reference before sending?
The text still arrives, and the webhook falls back to matching the sender’s number against the account’s phone number. If neither matches, the founder asks for the account email in the thread.
Can the founder text customers who cancelled without tapping the button?
Only those who gave you their number for texts from you; a cancellation alone generally isn't consent (this isn't legal advice). A first text to someone who has never texted you counts as a new contact (Pro: 5 a day per number), and an offer waits for 8 am to 9 pm in their time zone. Email is the channel for everyone else.
Does the customer see the founder’s personal number?
No. Every reply goes out from your ZeroSettle number, whether the founder types it in the console or with /imsg in Slack, so the thread stays with the company if the founder hands support to someone else.
Sources
- ZeroSettle iMessage API docs: “Customer support”, “Retention offers”, “Attachments”, “Webhooks, step by step” and “Errors, limits, and compliance”. Checked October 8, 2026.
- 15 U.S. Code § 8403, Negative option marketing on the Internet (Restore Online Shoppers’ Confidence Act), Legal Information Institute. Checked October 8, 2026.
- Cancel a subscription from Apple, Apple Support. Checked October 8, 2026.
- Request a refund for apps or content that you bought from Apple, Apple Support. Checked October 8, 2026.