How to get customers to text you first: sms: links and QR codes
The simplest way to start a business iMessage conversation is to let the customer send the first text: a prefilled sms: link or QR code opens Messages to your number with a short message ready, and they tap Send. On ZeroSettle, anyone who texted the line first is never limited, every link you send back is tappable, and iOS 26's Screen Unknown Senders filter doesn't hide your replies, because you're no longer an unknown sender to them.
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.
Key takeaways
- What it is: a prefilled
sms:link, or a QR code of one, that opens Messages addressed to your ZeroSettle number with a short text typed. The customer taps Send. - Why it matters: a customer who texts first never counts against the new-contact limit, links in your replies are tappable, and Screen Unknown Senders won't hide you.
- The format:
sms:+18055550100&body=Order%20%2320431, with&body=for Apple devices and the text percent-encoded. - Price: Pro is $49/month for one number; Growth is from $599/mo. Conversations customers start are unlimited on both.
- What it does not do: on Android the link opens the SMS app, and ZeroSettle has no SMS fallback. Keep email or chat next to it.
Why should customers text first?
Customers should text first because it switches off the three things that work against a business's first text: ZeroSettle's new-contact limit, iPhone's rule against tapping links from unknown senders, and iOS 26's unknown-sender screening.
| Question | You text first | They text first |
|---|---|---|
| New-contact limit | Counts: 5 a day per number on Pro, 50 on Growth | Never counts, nor does the ongoing conversation |
| A link in your first message | Plain text they can't tap; the API returns warning.code: "link_not_tappable" | Tappable from your first reply |
| iOS 26 Screen Unknown Senders | Can file your text in a separate list, with no notification | Avoided |
| Consent | Generally, a record that they gave the number for this purpose | They asked for this conversation; promotions generally still need a separate yes |
Source: ZeroSettle iMessage API docs. A new contact is a handle the line delivers a text, voice message or photo to first; reactions, typing, profile shares and failed sends never count. The consent row is general information, not legal advice.
Apple's guide to Screen Unknown Senders says filtered senders' messages go to other folders with no notification unless the owner allows one. ZeroSettle's docs add that the setting is on for anyone who had the older unknown-sender filter turned on, so "a cold first text may never be seen." For the link rule, see why links in a first iMessage aren't tappable.
The limit math: if 150 people text a Pro number from a yard sign in a week, none of them touches its 5 new contacts a day. On every plan, the 2-unanswered-messages cap counts from the last thing the customer sent (a text, photo, voice note or tapback), so you can answer twice before they write back; texts sent within 5 minutes of each other count as one.
How does an sms: link work?
An sms: link is a URL that opens Messages addressed to your number with the text already typed, so the customer only taps Send; because your ZeroSettle number is an iMessage line, it goes as iMessage. ZeroSettle's support and sign-in docs use this form:
<a href="sms:+18055550100&body=Order%20%2320431">Text us about order #20431</a>
- Apple reads
&body=. RFC 5724, the IETF standard for these links, uses?body=, which Android reads. iOS and macOS read&body=, and only Apple devices send iMessage. - Percent-encode the text. A raw
#starts a URL fragment and a raw&a new field, so either can cut the prefill short. UseencodeURIComponentor Python'squote. - Navigate inside the tap. Safari drops custom-scheme navigations that aren't the direct result of a tap: use a plain link or
location.hrefin the click handler, notwindow.openafter anawait. In an iOS app, useUIApplication.shared.open.
Where should text-first links and QR codes go?
Put a text-first link wherever a customer already has a question: a button on screen, and a QR code of the same link in print, with the number also printed as text for people who would rather type it. Each row links to a use-case post that runs that entry point end to end.
| Entry point | Prefill | Worked example |
|---|---|---|
| Order status page and shipping emails | Order #20431 | "Where is my order?" for Shopify brands |
| Bags, menus and packaging (QR) | Question about the 12oz Decaf | Text-to-buy |
| Host stand sign (QR) | JOIN party of _, name _ | Restaurant waitlist |
| Website, yard signs and truck wraps | Quote please | Text-a-photo quotes for trades |
| For-sale signs (QR) | I'm interested in 22 Oak St (ref 2207) | Real estate leads |
| Conference badges (QR) | None; the number alone | Event help line |
| Link in bio | Hi Nora! (TikTok) | A creator's text line |
| Cancel flow | Cancel ref 8812 | Text the founder |
| Sign-in button | SIGNIN 4F7K2Q | Sign in with iMessage |
How do you make a QR code that opens Messages?
A text-first QR code is the sms: link drawn as an image, so any library works; ZeroSettle's docs name qrcode on npm and segno in Python. The link inside and the words beside it are what matter.
- One link per placement, so the prefill tells you which sign or wrap the text came from.
- Export SVG, which stays sharp from a business card to a truck door. A higher error-correction level tolerates scuffs.
- Scan the proof with an iPhone before printing: in the Camera app, hold the iPhone so the code appears and tap the link. Messages should open to your number with the prefill typed. Send it and check that it reaches your console or webhook.
- Print what happens: "Scan to text us. Messages opens with your request typed; tap Send." Add the number as text, and an email address for customers without iMessage.
# make_qr.py: one print-ready QR code per placement (pip install segno)
from urllib.parse import quote
import segno
LINE = "+18055550100" # your ZeroSettle number
PLACEMENTS = {
"truck-wrap": "Quote please (truck)",
"yard-sign": "Quote please (yard sign)",
}
for name, text in PLACEMENTS.items():
url = f"sms:{LINE}&body={quote(text)}" # spaces become %20, # becomes %23
qr = segno.make_qr(url, error="q") # level Q tolerates some damage
qr.save(f"{name}.svg", scale=10)
print(name, url)
What should the prefilled message say?
The prefill should be one short reference that tells you who is texting or what they want, such as "Order #20431" or "Quote please". The customer can see and edit it, and it stays in their Messages history.
- Tag the source if you print in several places: "Quote please (truck)" shows in the ZeroSettle console which placement worked.
- Never a secret, token or personal data. ZeroSettle's docs call for "a short, opaque reference (an order number, or a per-user support code), never an email or a secret." Anyone can type an order number.
- Check the reference against the sender. The text arrives with the handle Apple attached, "a stronger identifier than a number the user typed." Share order details only if the handle matches the order's phone number or email.
- Leave out the word "stop". ZeroSettle treats a text with the word STOP in it as an opt-out, so "Can I stop by for a quote?" would opt the customer out with their first message, and every reply would return
403 recipient_opted_outuntil they text START (how STOP and opt-outs work).
What happens when the customer's text arrives?
The customer's text reaches your webhook as a signed message.received event about 1–2 seconds after the line receives it, and shows in the ZeroSettle console inbox. The prefill is the message text:
X-ZeroSettle-Event: message.received
X-ZeroSettle-Signature: t=1757400012,v1=3f0c…
{"event":"message.received","message":{"direction":"in","guid":"5A6F…","handle":"+18055559876","id":44,"status":"received","text":"Order #20431","ts_ms":1757400012000}}
Check the X-ZeroSettle-Signature header before acting on the event: v1 is an HMAC-SHA256, keyed with your webhook secret, of t, a dot and the raw request body, and t must be within 300 seconds of now. A complete receiver is in how to receive iMessages with a webhook.
Then acknowledge the text with a tapback, a 👍 that says "seen" without another text. ZeroSettle supports love, like, dislike, laugh, emphasize and question on any message, theirs or yours, including photos and voice messages; prefix remove_ to retract one. Their reactions arrive as reaction.received.
BASE=https://portal.zerosettle.io
curl -s -X POST -H "Authorization: Bearer $ZEROSETTLE_TOKEN" -H 'Content-Type: application/json' \
-d '{"message_id":44,"reaction":"like"}' $BASE/api/imessage/reactions
Reply with POST /api/imessage/send, and links in the reply are tappable. Or answer from the ZeroSettle console, with no server at all.
- Today 2:14 PM
- Customer: Order #20431
- Customer: too big, can I swap for a medium?👍
- Business: Hi Sam, this is Juniper Outfitters. Mediums are in stock. Start the swap here: juniper.example/swap/20431
- Customer: done, thanks!
- Business: Got it. The medium ships when your return is scanned.❤️
What about Android visitors?
On Android, an sms: link opens the SMS app, and ZeroSettle can't reply there: a ZeroSettle number reaches iMessage addresses only, with no SMS fallback (it is on the roadmap). Keep an email or chat option next to every button and QR code.
Apple's comparison of iMessage, RCS and SMS explains why: iMessage runs between Apple devices, and texts to other phones go as RCS or SMS/MMS. On a web page, the device test from ZeroSettle's sign-in example picks the right option:
<a id="text-us" href="sms:+18055550100&body=Order%20%2320431">Text us about this order</a>
<p id="text-us-qr" hidden><img src="/qr/order-20431.svg" alt="QR code that opens Messages with Order #20431 typed"
width="160" height="160"> Scan with your iPhone, or text +1 805-555-0100.</p>
<p>Not on iMessage? Email <a href="mailto:help@example.com">help@example.com</a>.</p>
<script>
// Apple: the link. Other computers: the QR. Android: email only.
if (!/iPhone|iPad|iPod|Macintosh/.test(navigator.userAgent)) {
document.getElementById("text-us").hidden = true;
if (!/Android/.test(navigator.userAgent)) document.getElementById("text-us-qr").hidden = false;
}
</script>
With JavaScript off, the link and the email line both show. Android reads ?body=, so an Android visitor who taps the link anyway may not see the prefill.
What does text-first cost on ZeroSettle?
When your own server or team answers, text-first conversations cost nothing beyond the plan: ZeroSettle's messaging is unlimited, and customers who text first never use the new-contact allowance.
| Plan | Price | New contacts you can text first, per number per day | Conversations customers start |
|---|---|---|---|
| Pro | $49/month, one number | 5 | Unlimited |
| Growth | From $599/mo, several numbers working as one | 50 | Unlimited |
Both plans have a 30-day money-back guarantee on the subscription. If the hosted AI agent writes the replies, each text it sends uses one AI credit: Pro includes 1,000 a month and Growth 5,000. Compare ZeroSettle's plans, read the customer support docs or get a number. More in iMessage for business guides and how ZeroSettle works.
Frequently asked questions
Can a customer who texted first ever count as a new contact later?
Only if you delete the conversation. DELETE /api/imessage/conversations/{handle} removes every stored message with that handle, and your next text to them counts as a new contact again. Until then, a customer who texted the line first is never limited.
What if the customer deletes the prefilled text?
Whatever they send still arrives with their handle, so a returning customer is matched by their phone number. Ask one short question, such as the order number, only when the handle doesn't tell you who they are.
Can I send promotions to someone who texted first?
Not on the strength of that text, which asks for a reply about one thing. ZeroSettle's docs say not to send automated marketing into a support thread: "It is a different consent, and it is how support lines get reported as spam." In the US, marketing texts generally need written consent; see the TCPA consent guide. This isn't legal advice.
What if someone texts from an iPad or a Mac?
The message can arrive from an iMessage email address instead of a phone number. Treat the handle as an opaque string and match on either.
Can the hosted AI agent answer people who text first?
Yes. On a line group set to Managed by AI Agent (Beta), ZeroSettle's hosted agent answers every inbound text from your business context, using one AI credit per text it sends; your webhook isn't called for those texts.
Sources
- ZeroSettle iMessage API docs: "Send a message" (Links in a first message, Reactions), "Customer support" (Let them start the conversation), "Sign in with iMessage" (From the browser), "Webhooks, step by step" and "Errors, limits, and compliance". Checked October 12, 2026.
- ZeroSettle homepage: plans and the FAQ "Group chats, attachments, Android?" (SMS fallback for non-Apple phones is on the roadmap). Checked October 12, 2026.
- RFC 5724: URI Scheme for Global System for Mobile Communications (GSM) Short Message Service (SMS), IETF, January 2010. Checked October 8, 2026.
- Screen and filter text messages on iPhone (iOS 26), Apple Support. Checked October 8, 2026.
- Scan a QR code with your iPhone or iPad, Apple Support. Checked October 8, 2026.
- What is the difference between iMessage, RCS, and SMS/MMS?, Apple Support. Checked October 8, 2026.