Use cases

A Group Trip Planner That Lives in Your iMessage Group Chat

Short answer: give your trip-planning assistant its own iMessage number and put that number in the travelers' group chat. It runs polls for dates and destinations, collects everyone's preferences, posts a summary when the group decides, and sends the itinerary as text and photos, all in the thread the friends already use. On ZeroSettle you can run it with the hosted AI agent (a business context plus an MCP server for availability) or with your own code on a signed webhook. Every member has to have agreed to hear from you, a group holds up to 31 people, and a STOP from any member blocks sends into every group they're in.

Key takeaways

  • Meet the group where it already talks. Group chats are on for every ZeroSettle number: start a group with POST /api/imessage/groups, or have a traveler add the number to the group they already have.
  • Polls settle the arguments. A send with chat_id and poll puts a native Messages poll in the group, and every vote arrives on your webhook as poll.voted.
  • Stay quiet by default. The hosted agent answers in a group only when a message is meant for it, and its triggers never fire into a group. Copy that behavior if you write your own.
  • Have travelers text first. People who texted the number are not new contacts, so a group of them starts within any plan's limits, and you have a clean consent record for each member.
  • Price: Pro is $49/month for one number, Growth is $199/month per number (or $599/month for 3 numbers from a Growth link), Enterprise is custom. Credits pay only for the hosted agent's texts and for videos.

Most group trips are planned in a group chat whether or not anyone meant to. Someone floats a city, three people reply with emoji, two go quiet, and a week later nobody remembers which weekend won. Travel apps ask the whole group to download something and make an account. A planner that sits in the chat itself skips that step.

This post is for travel startups, concierge services and developers building a trip-planning assistant. It covers what the assistant should do in the group, the iMessage features that make it work, the two ways to run it on ZeroSettle, and the consent and limit rules that keep the number healthy.

What does a group trip planner do in iMessage?

A group trip planner is an assistant with its own iMessage number that is a member of the travelers' group chat. It answers when someone asks it something, and otherwise lets the friends talk. Its jobs fall into five groups:

  • Get options on the table. Look up open dates, stays or activities when asked, and post two or three short options instead of a wall of links.
  • Make decisions countable. Turn "which weekend?" and "Lisbon or Porto?" into polls everyone can vote in.
  • Collect preferences. Ask once for budgets, dietary needs, flight constraints and room arrangements, and remember the answers from the thread.
  • Say what was decided. Post a short summary when a vote closes or the group agrees, threaded under the question it settles.
  • Send the plan. Deliver the itinerary as text, with photos of the stay, and answer "what time is checkout?" a week later.

What it should not do is narrate every message or chase people with reminders. A group chat is a shared space. The bar for speaking is higher than in a 1:1 thread; When should an AI speak in a group chat? goes deeper on that judgment.

What does a planning session look like?

Here is a short session in a group of three friends. The planner is a fictional service called Tripkit, built with its own code on the API, so it can send polls as well as text.

  1. Sat 10:12 AM
  2. Priya:
    Tripkit, can you find 3 nights in Lisbon for the 3 of us? Either May 15-18 or May 22-25, under $250 a night total
  3. Tripkit: Found two that fit both weekends: a 3-bedroom in Alfama ($228 a night) and a 2-bedroom plus sofa bed in Principe Real ($195 a night).
  4. Tripkit: 📊 Poll: May 15-18 · May 22-25
  5. Tripkit: Which weekend works?
  6. Marcus:
    15th is my sister's thing, voted 22nd
  7. Jen:
    marcus are you still doing no meat?
  8. Marcus:
    yep, fish is fine though
  9. Sat 11:40 AM
  10. Tripkit: All three votes are in: May 22-25 it is. Next: Alfama or Principe Real? Alfama has a room each, Principe Real saves about $100 total.
  11. Priya:
    alfama, rooms matter. send the plan
  12. Tripkit: Plan: Alfama 3-bedroom, Thu May 22 to Sun May 25. Check-in 3 PM, checkout 11 AM. Dinner Friday: seafood place with veggie options for Marcus. Booking link follows once the hold is confirmed.
Illustrative example with a fictional planner, travelers and listings. Blue bubbles come from the planner's number; gray bubbles are the travelers. The planner stays silent while Jen and Marcus talk to each other, closes the poll with a summary, and remembers the dietary note for the itinerary.

Notice what the planner did not do. It didn't answer Jen's question to Marcus, it didn't post after every vote, and it waited for an explicit "send the plan" before writing the itinerary.

How does the planner get into the group?

The planner gets into a group one of two ways: a traveler adds its number to a group they already have, or your code starts a new group with the travelers in it. Both work the same once the group exists. Group chats are on for every ZeroSettle number; GET /api/imessage/line shows "group_chats": true.

A traveler adds the number. This is the lowest-friction path for a consumer product: the organizer adds your number in Messages, and the group shows up in GET /api/imessage/groups with its chat_id. The catch is consent: the organizer can add people who never agreed to hear from you, and the organizer's say-so doesn't cover them. Before the planner posts into a group it was added to, confirm every member agreed (in your sign-up flow, or by texting the number themselves). If you can't confirm that, take the number out with POST /api/imessage/groups/leave. When it does post, its first message should say who it is, what it will do and how to opt out.

Your code starts the group. This is the better path for a concierge service. The cleanest version: each traveler texts the number first (for example, a "Text JOIN to plan the Lisbon trip" link the organizer shares; JOIN is your own keyword, not a ZeroSettle feature), and once everyone has joined, your code creates the group with them:

bash
curl -s -X POST "$BASE/api/imessage/groups" \
  -H "Authorization: Bearer $ZEROSETTLE_TOKEN" -H "Content-Type: application/json" \
  -d '{"to": ["+13105550111", "+13105550122", "+13105550133"],
       "name": "Lisbon in May",
       "text": "Hi all, Tripkit here. I will run polls for dates and places, keep track of what you decide and send the plan. Ask me anything by name. Reply STOP to leave."}'

The response carries chat.chat_id (for example iMessage;+;chat123456789). Store it as is: it never changes, even when the group is renamed or members come and go. In rare cases the answer is "status": "group_unconfirmed" with "chat": null; the message went out, so don't send it again, or you'll start a second group. The group appears with its real chat_id once someone replies.

Having travelers text first matters for two reasons beyond consent. People who texted the number are not new contacts, so they don't use the plan's daily allowance. And an iPhone shows links from an unknown sender as plain, untappable text; getting customers to text first avoids that for the booking links you'll send later.

Which iMessage features do the planning work?

Polls, inline replies, photos, typing, tapbacks and effects all work in a group. On POST /api/imessage/send, pass chat_id instead of to and everything a 1:1 send takes works in the group; typing and tapbacks have their own endpoints:

Group features a trip planner uses (ZeroSettle API docs, checked November 2, 2026)
JobFeatureHow
Pick dates or a destinationPollpoll with 2 to 12 options and an optional question; every member can vote, and voters lists each member's handle
Answer the right questionInline replyreply_to with the id of a message in the same group threads the answer under it
Show the stay or the viewPhotoimage_b64 (up to 5 MB) with an optional caption in the same bubble
Signal it's working on a lookupTyping bubblePOST /api/imessage/typing with chat_id
Acknowledge without a messageTapbackPOST /api/imessage/reactions with the group message's message_id
Celebrate the bookingEffecteffect, such as confetti or celebration, on a text or photo
Track who's inGroup eventsgroup.event webhooks for renames, members added, removed or leaving, and a new group photo

Two caveats on polls. Travelers on iOS 26 or later vote right in the thread; on older iPhones they see "Sent a poll" and can't vote, so keep a text fallback ("or just reply with the weekend you want"). And a poll's question follows it as a separate text, because iOS draws no question on a poll. The iMessage group chat API guide has every request shape.

Can the hosted AI agent run the planner?

Yes, for the conversational part. ZeroSettle's hosted agent (Managed by AI Agent) answers inside groups on a number it manages: it reads the group's recent messages labeled by who wrote them, knows the group's name and members, and replies into the group only when a message is meant for it or asks something it can help with. It stays silent when people are talking to each other, and tapbacks, poll votes and group events (a rename, someone joining or leaving) don't wake it.

Set it up with a business context written for group trips, and connect an MCP server for live availability:

business_context.txt (excerpt)
You are Tripkit, a trip-planning assistant in a friends' iMessage group chat.
Speak only when someone addresses you by name or asks a planning question.
Keep every answer short: two or three options at most, prices per night for the whole group.
Use the availability tools for dates, stays and prices. Never quote a price you did not look up.
Remember what people tell you: budgets, dietary needs, who shares a room, arrival times.
When the group agrees on something, restate the decision in one sentence.
You can't book or pay. For a booking, say the organizer will get a link from the Tripkit team.
If someone wants no more texts, tell them to reply STOP.
bash
jq -n --rawfile ctx business_context.txt '{managed_by: "zerosettle", business_context: $ctx,
  personality: "casual", first_message_enabled: false, goal_enabled: false, triggers: []}' |
curl -X PUT "$BASE/api/imessage/agent" \
  -H "Authorization: Bearer $ZEROSETTLE_TOKEN" -H "Content-Type: application/json" -d @-

curl -X POST "$BASE/api/imessage/agent/mcp" \
  -H "Authorization: Bearer $ZEROSETTLE_TOKEN" -H "Content-Type: application/json" \
  -d '{"url": "https://mcp.tripkit.example/mcp", "token": "YOUR_SERVER_TOKEN"}'

A few choices in that setup are deliberate:

  • first_message_enabled: false. The default first_message is a two-text opener for an interested sales lead, and it is also the first answer to anyone who texts the number first, such as a traveler texting JOIN. Turned off, the agent writes its opener from your business context. You can instead write your own first_message.
  • triggers: []. Triggers never fire into a group anyway, but the default follow-up would still fire in 1:1 threads, such as that JOIN conversation. A planner usually doesn't need it.
  • personality: "casual". All lowercase and laid back, which suits a friends' group. Use professional for a concierge brand.
  • Read-only tools. ZeroSettle turns on an MCP server's read-only tools (readOnlyHint) and leaves tools that change something off. Anyone who texts the number can ask the agent to use a tool that is on, so keep holds and bookings off unless that's acceptable.
  • Files for the static facts. Upload house rules, a city guide or your cancellation policy as files (PDF, Excel, CSV, Word or text, up to 10) so the agent can answer from them.

The hosted agent answers in texts (up to three per answer); polls and photos come from the API. You can still mix them in. POST /api/imessage/send works on a number the agent manages, and the agent reads your texts in the thread as its own, so a small script can post the poll while the agent handles the conversation. On a number the agent manages, inbound events don't reach your webhook, so the script reads the result from GET /api/imessage/messages/{id}/poll instead of waiting for poll.voted. Each text the agent sends uses one credit, in a group as anywhere else. How the hosted AI agent answers iMessages walks through every setting.

How do you build it with your own code?

Set managed_by to "api", register a webhook, and branch on chat_id: a message with it came from a group, and its handle is the member who sent it. Choose this route when the planner needs to act on votes, place booking holds in your system, or use a specific model.

TypeScript
// After verifying X-ZeroSettle-Signature and answering 200 (see the webhooks guide)
async function onEvent(ev: any) {
  const m = ev.message;
  if (!m.chat_id) return handleDirect(m); // 1:1 thread, e.g. a JOIN text

  if (ev.event === "poll.voted" && m.poll) {
    const group = (await zs("/api/imessage/groups")).groups.find((g: any) => g.chat_id === m.chat_id);
    if (group && m.poll.voter_count >= group.participants.length && !(await alreadyAnnounced(m.target_id))) {
      const top = [...m.poll.options].sort((a: any, b: any) => b.votes - a.votes)[0];
      await zs("/api/imessage/send", {
        chat_id: m.chat_id,
        reply_to: m.target_id, // thread the result under the poll
        text: `All votes are in: ${top.text} it is.`,
      });
    }
    return;
  }

  if (ev.event !== "message.received") return;      // your own sends echo back as message.sent
  if (!isForPlanner(m.text ?? "")) return;            // your rule: addressed by name, or a planning question
  await zs("/api/imessage/typing", { chat_id: m.chat_id, state: "start" });
  const history = await zs(`/api/imessage/messages?chat_id=${encodeURIComponent(m.chat_id)}&before_id=${m.id + 1}&limit=60`); // the 60 latest
  const answer = await planWithModel(history.messages, m); // your model, your keys
  await zs("/api/imessage/send", { chat_id: m.chat_id, text: answer });
}

zs is a thin fetch wrapper with your bearer token, as in Building iMessage agents; alreadyAnnounced is your own record of polls you've closed, so a vote changed later doesn't post the result twice. Without after_id or before_id, GET /api/imessage/messages pages forward from the oldest message, so the code asks for the rows before the new one. Three more details to get right:

  • Start a poll the same way you send a text. {"chat_id": "…", "poll": {"question": "Which weekend works?", "options": ["May 15-18", "May 22-25"]}} to /api/imessage/send. Votes can arrive out of order; the poll on each poll.voted event is the result after that vote, so read totals from it rather than counting events.
  • Ignore group events in your answer logic. Renames and members joining arrive as group.event, never message.received, so a responder that acts only on message.received won't greet every rename.
  • Decide "is this for me?" in code. There is no API flag for it. A name check plus a short classifier call is usually enough; How to build an AI assistant for iMessage group chats shows a fuller version.

How should it summarize decisions and send the itinerary?

It should summarize only when something was actually decided, in one or two sentences, threaded under the question it settles. The thread is the planner's memory: GET /api/imessage/messages?chat_id=… returns the group's messages with each sender's handle, and GET /api/imessage/messages/{id}/poll returns one poll's current results.

Good summaries name the decision and the next open question: "Dates: May 22-25. Still open: where to stay." Bad summaries recap the whole chat. If two people disagree, say so and offer a poll instead of picking a winner.

For the itinerary itself:

  • Keep it in one text where you can. A text holds up to 4,000 characters, which fits days, times and addresses. Split by day only for long trips.
  • Add photos with captions. A photo of the stay with "Alfama, check-in 3 PM" as its caption lands in one bubble. Send it once the group is already talking, the same way you'd hold back a photo in a 1:1 thread until the person has replied.
  • Hold links until people know the number. A member who has never texted the number may see a link as plain text. Members who joined by texting first see tappable links. Why links aren't tappable explains the rule.
  • Answer follow-up questions from the plan. "What time is checkout?" a week later should get "11 AM Sunday" as an inline reply, not the whole itinerary again.

What are the consent, size and STOP rules?

Every member of the group must have agreed to hear from you, a group holds up to 31 people, and a STOP from any member blocks sends into every group they're in. Here are the rules that matter for a trip planner:

Group rules for a trip planner (ZeroSettle API docs, checked November 2, 2026)
RuleWhat it means for the planner
Consent covers every memberA group message reaches everyone. The organizer's say-so is not consent for their friends.
Up to 31 peoplePOST /api/imessage/groups takes 2 to 31 handles in to; more is 422 invalid_request.
New members are new contactsStarting a group, or adding someone, counts each member the number has never exchanged a message with. Pro allows 5 a day per number; Growth 50 a day per number and 3 per 15 minutes. People who texted the number first never count. If they don't all fit, the call is 429 new_contact_limit and nothing is sent.
STOP from any memberSends into every group they're in are refused with 403 recipient_opted_out (error.handle says who) until they're removed or text START.
Hosted agent in groupsAnswers only when a message is meant for it, never fires triggers into a group, and never answers another ZeroSettle number.
CRMsGroup messages are not logged in Close or HubSpot.

The new-contact rule is why the "text JOIN first" flow is worth building. Every new member has to fit in a single call, so a group with six people new to the number can't start on Pro (5 a day) or on Growth (3 per 15 minutes); you'd have to start smaller and add the rest later with POST /api/imessage/groups/update. Travelers who texted first aren't new, so their group starts immediately.

When a member texts STOP, handle the 403 by removing them with POST /api/imessage/groups/update and "remove": ["+1…"], then tell the rest of the group in plain words. Don't try to work around it. If the whole group is done, POST /api/imessage/groups/leave takes the number out; the stored messages stay readable. How STOP works on iMessage covers the keywords, and the TCPA consent guide covers what counts as agreement in the US. This is not legal advice.

A group is not a broadcast

Members of a group see each other's numbers and messages. To send the same update to many travelers who shouldn't see each other, use POST /api/imessage/broadcast, which sends each one their own 1:1 message.

What should you check before launch?

Check that the planner stays quiet, handles STOP and group events, and answers from the thread. Run these in a test group first:

  • Use a group of three or four people on your team, including one iPhone on a version before iOS 26, so you see how a poll looks to someone who can't vote.
  • Have two members chat about something unrelated. The planner should stay silent.
  • Text STOP from one test member and confirm your code removes them and tells the group, instead of retrying.
  • Rename the group and add a member. Your responder should not answer the group.event.
  • Ask the same planning question twice, a day apart. The answer should come from the thread, not from scratch.
  • Decide who on your team handles a booking problem, and make the planner say so plainly.

The same pattern carries over to other groups that plan together: teams, clubs and communities use polls and summaries the same way.

Frequently asked questions

Can the planner join a group chat my friends already have?

Yes. Anyone in the group can add the ZeroSettle number, and a group the number is added to works the same way as one it starts. Everyone already in that group still has to have agreed to hear from the planner; if you can't confirm that, have the number leave the group.

Does everyone in the group have to agree to the texts?

Yes. A group message reaches every member, so consent has to cover each of them, not just the organizer. The simplest record is each traveler texting the number to join before the group starts. This is not legal advice.

How many people can be in a trip group?

A group holds up to 31 people; when your code starts one, the to list takes 2 to 31 handles besides the number itself. Each member the number has never exchanged a message with counts as a new contact that day.

Can the planner run polls in the group?

Yes, through the API: a send with chat_id and poll puts a Messages poll in the group, every member can vote, and each vote arrives as a poll.voted webhook. People on iOS 26 or later vote in the thread; older iPhones see "Sent a poll" and can't vote.

Will the planner flood the group with reminders?

Not the hosted agent: its triggers, such as follow-ups and daily texts, never fire into a group, and it answers only when a message is meant for it. If you run your own code, reminders happen only when your code sends them.

What does a group trip planner cost to run?

Pro is $49 a month for one dedicated number with 5 new contacts a day and 500 free credits once. Growth is $199 a month per number with 50 new contacts a day per number and 1,000 free credits once, or $599 a month for 3 numbers plus $199 for each extra number from a Growth link. Enterprise is custom pricing through sales for high volume and compliance needs. Free credits never refill; credits pay only for texts the hosted agent sends and for videos, and packs are 1,000 for $50 or 5,000 for $200. The subscription has a 30-day money-back guarantee.

Sources

  1. ZeroSettle iMessage API docs: Group chats (start a group, send to a group, polls and replies in groups, group events, limits and compliance). Checked November 2, 2026.
  2. ZeroSettle iMessage API docs: Send a message (polls, replies, effects, links in a first message, broadcast). Checked November 2, 2026.
  3. ZeroSettle iMessage API docs: AI agent (Managed by AI Agent, group chats, triggers, connectors, credits). Checked November 2, 2026.
  4. ZeroSettle iMessage API docs: Errors, limits, and compliance (new contacts, consent, STOP). Checked November 2, 2026.
  5. ZeroSettle iMessage API docs: Your iMessage number (pricing and credits). Checked November 2, 2026.

Written by the ZeroSettle team. ZeroSettle is an iMessage API: a dedicated phone number on iMessage, running on Apple hardware we operate for you in a server facility, that your code or our hosted AI agent texts from and hears back on through a bearer token and signed webhooks.

Product facts on this page come from the ZeroSettle iMessage API docs as of November 2, 2026. Tripkit, its travelers and its listings are fictional; tripkit.example is a placeholder for your own domain.

Put your trip planner in the group chat

Get a dedicated iMessage number, then switch on the hosted agent or point the webhook at your own planner.

Pro is $49 a month for one number with a 30-day money-back guarantee. Growth is $199 a month per number, or $599 a month for 3 from a Growth link. Enterprise pricing is custom, for high volume and compliance needs.