Retail clienteling over iMessage from store numbers, not stylists' phones
ZeroSettle lets a boutique or luxury retailer give each stylist an iMessage number on the store's Growth account, so stylists text new-arrival photos to their clients and the conversation history stays with the store when a stylist leaves. A ❤️ tapback on one of those photos reaches your webhook as a reaction.received event that points at the photo, so your own code can hold the item in your POS. Clients already in a conversation with a stylist's number have no daily cap; only first messages to new clients count against Growth's allowance of 50 a day per number.
Key takeaways
- What it is: one iMessage number per stylist on the store's ZeroSettle account, sending photos and texts from the web console or through a REST API.
- Who it fits: boutiques, luxury retailers, jewelers, bridal shops and furniture showrooms whose top clients use iPhone and opted in to texts.
- Price: Growth, for several numbers working as one, is from $599/mo. A one-stylist shop can use Pro: $49/month for one number.
- Main limits: on Growth, 50 new clients a day per number (3 per rolling 15 minutes) and at most 2 unanswered messages per client. Ongoing conversations have no daily cap.
- What it does not do: no SMS fallback for Android clients, no built-in POS or clienteling-app integration, and no sending a collection to the whole client list at once.
Who uses iMessage for clienteling?
iMessage clienteling suits boutiques, luxury retailers, jewelers, bridal shops and furniture showrooms where stylists or sales associates look after a short list of top clients. Those associates often text clients from personal phones, so when one leaves, the relationship and its history leave too. A showroom that sells one large piece per buyer may be closer to the dealership lead workflow.
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. The stylists' numbers sit on the store's ZeroSettle account rather than on their personal phones, though ZeroSettle's terms keep each number as ZeroSettle's property. Other industries are in the use-case index.
How iMessage clienteling works, step by step
A stylist sends one client a photo of a piece picked for them, from the stylist's number on the store's account, and the reply or ❤️ comes back to the store's webhook and console.
- On Growth, each stylist gets a number (a line, with a stable
line_id) in the store's line group, which shows the store's name and photo, or in a group of their own, such as "Inès at Maison Lune". - Invite stylists and managers as teammates to the console, so anyone can pick up a thread with its full history.
- The stylist sends a new arrival as a captioned photo (up to 5 MB) from the console, or your tool sends it through
POST /api/imessage/sendwith the stylist'sline_idand stores the returnedidnext to the SKU. - The client's ❤️ arrives as
reaction.receivedwithreaction: "love"andtarget_idset to the photo'sid; your handler looks up the SKU and places the hold in your POS. - Clients text back room photos or inspiration shots, which arrive as
message.receivedwithkind: "image".
Where clienteling flows break
- A send without
line_id. On Growth, a new client goes to the line with the most room left today, which may be another stylist's number. - A reaction that is not a ❤️ on a pick. A ❤️ on "See you Saturday!" has no SKU behind it. The API names six reactions (love, like, dislike, laugh, emphasize, question), but Messages on iPhone also lets clients react with any emoji, Memoji or sticker, so pass every other reaction on a pick, and any written "hold it", to the stylist.
- Several inspiration shots in one message. Only the first photo is delivered; ask for one per message.
- A photo or product link in the first text to a new client. Photos to someone who has never replied are refused with
403 attachment_before_reply, and a link shows as plain text the client can't tap until they reply or save the number, so lead with a short text and send the photo and link once they answer.
Example conversation: clienteling
A pick, a heart, a size question and an add-on, on one stylist's number:
- Thu 11:04 AM
- Business: Hi Claire, it's Inès at Maison Lune. This just came in and made me think of you:
- Business: 📷 Photo: silk midi dress in moss❤️
- Customer: do you have a 38?
- Business: I do, holding it for you until Saturday. Want me to pull the matching belt too?
- Customer: yes please!
Setup: line groups per stylist and a tapback-to-reserve handler
Setup is a profile call per stylist group, a photo send that names the stylist's line, and a handler that turns a ❤️ on a pick into a POS hold, using the endpoints in the ZeroSettle iMessage API docs.
BASE=https://portal.zerosettle.io
AUTH="Authorization: Bearer $ZEROSETTLE_TOKEN"
curl -s -H "$AUTH" $BASE/api/imessage/line # each line's id, number and group_id
# name and photo for every line in Inès's group
curl -s -X PUT -H "$AUTH" -H 'Content-Type: application/json' \
-d "{\"name\":\"Inès at Maison Lune\",\"image_b64\":\"$(base64 -i maison-lune.png)\"}" \
"$BASE/api/imessage/profile?group=lg_5e0c9a1f2b7d3e84"
# a captioned pick from Inès's line; keep the returned id with the SKU
curl -s -X POST -H "$AUTH" -H 'Content-Type: application/json' \
-d "{\"to\":\"+15555550142\",\"line_id\":\"ln_8c41e07d92b3f615\",\"image_b64\":\"$(base64 -i moss-midi.jpg)\",\"image_format\":\"jpg\",\"text\":\"This just came in and made me think of you\"}" \
$BASE/api/imessage/send
The receiver verifies X-ZeroSettle-Signature over the raw body and answers 2xx before doing the POS work: ZeroSettle waits 10 seconds for an answer, then retries after 1, 5 and 20 seconds with the same message.id and event, so the handler dedupes on that pair.
# holds.py: run with uvicorn holds:app --port 8100 (picks, pos, notify_stylist are your store code)
import hashlib, hmac, json, os, time
from fastapi import BackgroundTasks, FastAPI, Request, Response
SECRET = os.environ["ZEROSETTLE_WEBHOOK_SECRET"]
app = FastAPI()
seen: set[tuple[int, str]] = set() # use your database in production
def verify(header: str, raw: bytes) -> 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) > 300:
return False
expected = hmac.new(SECRET.encode(), f"{t}.".encode() + raw, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, v1)
def on_reaction(m: dict) -> None:
pick = picks.get(message_id=m["target_id"]) # photo id -> SKU, saved at send time
if pick is None:
return # a reaction on a plain text
if m["reaction"] == "love":
pos.hold(pick.sku, client=m["handle"], days=3)
notify_stylist(pick, m) # every reaction on a pick; the stylist confirms the size
@app.post("/webhook")
async def webhook(request: Request, tasks: BackgroundTasks):
raw = await request.body()
if not verify(request.headers.get("X-ZeroSettle-Signature", ""), raw):
return Response(status_code=401)
event = json.loads(raw)
m = event.get("message")
if event["event"] == "reaction.received" and (m["id"], event["event"]) not in seen:
seen.add((m["id"], event["event"]))
tasks.add_task(on_reaction, m)
return Response(status_code=200)
Register it once with PUT /api/imessage/webhook and keep the signing secret, which is shown only once (webhook steps).
Limits and consent for clienteling
On Growth, each stylist's number can start conversations with 50 new clients a day, at most 3 in any rolling 15 minutes; clients already in a conversation with that number have no daily cap. The rule a stylist meets with existing clients, on either plan, is per client: after 2 unanswered messages, the next send is refused until the client answers.
| Situation | Growth | Pro |
|---|---|---|
| Client in a conversation with this number, or who texted it first | No daily cap | No daily cap |
| New client the stylist texts first (day runs 3 AM to 3 AM Eastern) | 50 a day, 3 per 15 minutes | 5 a day |
| Unanswered messages to one client | 2, then 403 awaiting_reply | |
Source: ZeroSettle iMessage API docs, "Errors, limits, and compliance". Messages sent within 5 minutes of the first count as one, so a greeting and its photo are one. A text, photo, voice note or tapback from the client resets the count, so a ❤️ counts as an answer.
The limit math for one stylist
- New-arrivals day: Inès sends the moss dress to the 18 clients she picked it for, all already in conversations with her number, so no daily cap applies. A send to a client who hasn't answered her last two picks returns
403 awaiting_reply; skip them until they answer. - Saturday at the counter: 12 new clients who each send the first text from a prefilled
sms:link use none of her allowance. If Inès texts them first, 3 go out per 15 minutes, the last about 45 minutes after the first, using 12 of her 50. - A book moved off a personal phone: every client is new to the store number, so 300 clients take six days at 50 a day, each day's 50 spread over about four hours at 3 per 15 minutes, unless they text the new number first. The first text has to say who it is from, because to the client it is an unknown number, and photos wait until the client replies.
Consent comes first
Clients opt in at the counter or on a clienteling card, and the store keeps a consent record for each one. Picks to existing clients are likely still marketing: send them between 8 am and 9 pm in the client's time zone, the window in ZeroSettle's docs and in 47 CFR 64.1200(c)(1) for US telephone solicitations. This isn't legal advice; have counsel review your consent records.
A STOP to any stylist, or a clear request like “take me off your list”, blocks every number on the store's account, and sends to that client return 403 recipient_opted_out. A text with the word STOP in it opts the client out, so "I'll stop by Saturday" does too; texting START opts them back in.
When ZeroSettle is not a fit for clienteling
ZeroSettle, or its Growth plan, is the wrong choice for clienteling in these cases:
- A single shop with one stylist. Growth is more than it needs; Pro is one number for $49/month, with 5 new clients a day.
- Most clients use Android. iMessage only reaches iPhone and other Apple products, and ZeroSettle has no SMS fallback, so it cannot reach those clients.
- You need a native clienteling-app or POS integration. ZeroSettle has none; you build it on the webhook, as the CRM logging guide does.
- You want to send the new collection to the whole client list at once. ZeroSettle's terms forbid high-volume marketing outreach, and new contacts are capped per number; for clients who asked about a size, see back-in-stock alerts.
- You want to own or port the numbers. They remain ZeroSettle's property and are reclaimed when the subscription ends.
How much does iMessage clienteling cost?
Clienteling costs the ZeroSettle plan price, with no per-message fees: messages are unlimited, and only new contacts are capped.
| Plan | Price | Fits |
|---|---|---|
| Pro | $49/month, no setup fee | One stylist, one number to start, 5 new contacts a day, unlimited messages with people who message you first, self-serve |
| Growth | From $599/mo | 3+ numbers working as one, 50 new contacts a day per number, dedicated support, set up after a demo |
Both plans are billed monthly with a 30-day money-back guarantee on the subscription. Details are in the pricing guide and the plans. Book a Growth demo, or get a Pro number.
Frequently asked questions
What happens to a stylist's threads when they leave?
The history stays on the store's account, where teammates you invite to the console pick up each conversation. Rename the stylist's line group for the successor with PUT /api/imessage/profile?group=…; the next message to each client shares the new name and photo from the same number.
Can two stylists text the same client?
Yes, as two separate conversations: your code names the second stylist's line_id, which counts as a new contact for that line, or the client texts that number. Without line_id, ZeroSettle sends from the line the client already talks to.
Can the hosted AI agent answer clients for a stylist?
Not alongside the stylist. On a line group set to Managed by AI Agent (Beta), the hosted agent answers every inbound text, and sends from your code or the console composer are refused with 403 managed_by_zerosettle, so keep stylists' groups Managed by API.
Does a client's receipt number count as consent for picks?
Probably not by itself. ZeroSettle's docs count consent only when the person gave their number for this purpose and you can show the record, and say US marketing texts need written consent. A number taken for a receipt or an alterations pickup was given for that, so ask for a separate opt-in to new-arrival texts. This isn't legal advice; check your program with counsel.
Sources
- ZeroSettle iMessage API docs: "More than one number", "Reactions (tapbacks)", "Attachments", "AI agent", "Webhooks, step by step" and "Errors, limits, and compliance". Checked October 8, 2026.
- ZeroSettle iMessage API Terms of Service, section 6, "Fees and Payment Terms" (numbers remain ZeroSettle's property), and section 13, "Compliance and Client Responsibility" (opt-in consent and prohibited outreach). Last modified September 28, 2026.
- What is the difference between iMessage, RCS, and SMS/MMS?, Apple Support. Checked October 8, 2026.
- React with Tapbacks in Messages on iPhone, Apple Support. Checked October 8, 2026.
- 47 CFR § 64.1200, Delivery restrictions, the FCC's TCPA rules; paragraph (c)(1) sets the 8 a.m. to 9 p.m. window (Legal Information Institute). Checked October 8, 2026.