Send iMessage from Customer.io
Keep Customer.io as your system of record and journey builder. A Webhook action sends a 1:1 iMessage through Tuco AI, and every send, reply and failure comes back as a Customer.io event on the right person.
Can Customer.io send iMessage?
Tuco AI adds iMessage to Customer.io: a journey's Webhook action calls the Tuco API to send a real blue-bubble message from a dedicated iPhone line, and Tuco's webhooks write sends, replies and failures back to Customer.io as events.
- Native support: None. Customer.io's built-in channels are email, push, in-app and SMS.
- How it connects: Customer.io Webhook action → POST app.tuco.ai/api/messages. No Zapier or n8n in between.
- Replies: message.reply webhook → webhook-triggered journey → tuco_message_reply event on the person.
- Follow-ups: skipIfReplied: true makes Tuco skip any step once the contact has replied.
- Android: message.fallback tells Customer.io to send SMS or email instead.
- Price: Flat per line, from $149/month. No per-message fees and no A2P 10DLC registration.
How the two systems split the work
Customer.io decides who gets a message and when. Tuco owns the iMessage line, the conversation and the inbox your team replies from.
1 · Customer.io → Tuco
Journey sends the message
An opt-in event starts the journey. A Webhook action posts the person's phone, email, name and assigned rep to Tuco, which texts them inside your send window.
2 · In Tuco
Your team has the conversation
Replies land in the shared inbox with full history. The owner you set gets the notification and can answer from the dashboard or Telegram.
3 · Tuco → Customer.io
Events flow back
Signed webhooks hit a webhook-triggered Customer.io journey, which records each one as an event on the person, so other journeys can exit or branch on it.
Journey 1: send the iMessage from Customer.io
Event-triggered journey: opt-in event → Webhook (first message) → wait 2 days → Webhook (follow-up) → exit. Paste this into the first Webhook action. The Liquid fills in from the Customer.io profile.
POST https://app.tuco.ai/api/messages
Authorization: Bearer tuco_YOUR_API_KEY
Content-Type: application/json
Idempotency-Key: cio-{{customer.id}}-first
{
"message": "{% if customer.first_name != blank %}Hi {{customer.first_name}}{% else %}Hi there{% endif %}, thanks for asking about the program. Any questions I can answer for you here?",
"recipientPhone": "{{customer.phone}}",
"recipientEmail": "{{customer.email}}",
"recipientName": "{{customer.first_name}} {{customer.last_name}}",
"contactOwnerEmail": "{{customer.owner_email}}",
"sendWindowStart": "09:00",
"sendWindowEnd": "20:00",
"timezone": "{{customer.timezone | default: 'America/New_York'}}",
"skipIfReplied": true
}- The first message is plain text. Links aren't clickable and attachments don't go through until the contact replies, so save the link for the follow-up or for your rep.
- recipientEmail is what ties replies back to the person. Tuco texts the phone number and stores the email on its lead, and every webhook carries it.
- The send window holds the message, it doesn't drop it. Outside 9:00 to 20:00 in the contact's timezone, Tuco returns
scheduledand sends when the window opens. - The Idempotency-Key header makes a Customer.io retry safe: the same key returns the original response instead of sending twice.
- Don't leave an empty greeting. Tuco rejects a body like “Hi , …” with
400 MALFORMED_NAME_TEMPLATE, which is why the example falls back to “Hi there”.
The follow-up step
Same request with two extra flags. Tuco skips it if the person already replied, so Customer.io needs no reply branch.
{
"message": "Hi {{customer.first_name}}, just checking in. What questions do you have?",
"respectGap": true,
"skipIfReplied": true,
...same recipient, owner and send-window fields as step 1
}Journey 2: get replies back into Customer.io
Create a webhook-triggered journey, copy its incoming URL, and register it in Tuco under Integrations → Webhooks. Add one Create Event action configured like this:
Event name tuco_{{ trigger.event | replace: '.', '_' }}
Person email = {{trigger.lead.email}}
Attributes
text {% if trigger.event == 'message.reply' %}{{trigger.message}}{% else %}{{trigger.message.message}}{% endif %}
tuco_message_id {{trigger.messageId}}
tuco_lead_id {{trigger.leadId}}
phone {{trigger.lead.phone}}
occurred_at {{trigger.timestamp}}On message.reply the message field is the reply text. On sent, failed and fallback it's the full message object, which is why the mapping reads message.message for those.
| Tuco webhook | Customer.io event | What to do with it |
|---|---|---|
| message.sent | tuco_message_sent | Mark the step as delivered to the line; start a wait-for-reply window. |
| message.reply | tuco_message_reply | Exit nurture journeys, alert the owner, branch on the reply text. Photo and file replies arrive as signed download URLs. |
| message.fallback | tuco_message_fallback | The number has no iMessage. Send the same text by SMS or email from Customer.io instead. |
| message.failed | tuco_message_failed | Technical failure after retries. Alert ops or retry on another channel. |
| conversation.handover | tuco_conversation_handover | The AI responder handed off to a person. Assign the conversation and notify the rep. |
Setup in five steps
- 1
Create a Tuco API key
In the Tuco dashboard open API Keys and create a key that starts with tuco_. Every request below sends it as a Bearer token.
- 2
Build the send journey in Customer.io
Create an event-triggered journey on your opt-in event, for example tax_credit_opt_in or form_submitted. Add a Webhook action that POSTs to https://app.tuco.ai/api/messages with the JSON body on this page. Add a delay and a second Webhook action for the follow-up.
- 3
Build the events journey in Customer.io
Create a webhook-triggered journey and copy its incoming webhook URL. Add a Create Event action that identifies the person by {{trigger.lead.email}} and names the event tuco_{{ trigger.event | replace: '.', '_' }}.
- 4
Register the webhook in Tuco
In Tuco open Integrations, then Webhooks, paste the Customer.io URL and subscribe it to message.sent, message.reply, message.fallback, message.failed and conversation.handover. Use Send test to confirm it arrives.
- 5
Test with your own phone, then start both journeys
Trigger the opt-in event for a test profile that has your own mobile number. Confirm the iMessage lands, reply to it, and check that tuco_message_reply appears on the profile's activity log. Then start both journeys.
What to plan around
Tuco sends real iMessage from real Apple hardware, so it follows Apple's rules. Build the journey with these in mind.
- Opt-in only. A handful of “Report Junk” taps can get a line banned, whatever your volume. Trigger on a real opt-in event, never on an imported cold list.
- Per-line caps. 50 new contacts and 150 total messages per line per day, paced at about 7 new contacts an hour. Use Customer.io's message throttling on the journey, and add lines to scale. See limitations.
- Keep iMessage inside a multi-channel journey. iMessage is the highest-response channel, but Customer.io should still own email and SMS for people iMessage can't reach. See adding iMessage to a multi-channel strategy.
- A line is a working sender, not a forever number. Numbers can't be ported in. If a line is ever banned the first replacement is free, and your history stays in Tuco and Customer.io.
Customer.io + iMessage FAQ
Can Customer.io send iMessage?
Not natively. Customer.io's built-in messaging channels are email, push, in-app and SMS through providers like Twilio. To send real blue-bubble iMessage, a Customer.io journey calls the Tuco AI API from a Webhook action. Tuco sends the message from a dedicated iPhone line and posts replies back to Customer.io as events.
Do I need Zapier or n8n between Customer.io and Tuco?
No. Customer.io's Webhook action can call the Tuco API directly with a Liquid-templated JSON body and an Authorization header, and a webhook-triggered Customer.io journey can receive Tuco's events directly. There is no middleware to host or pay for.
How do replies get back into Customer.io?
Tuco fires a message.reply webhook the moment the contact answers. Point it at a webhook-triggered Customer.io journey with a Create Event action and the reply is recorded as a tuco_message_reply event on the matching person, with the reply text as an attribute. Any journey can then exit, branch or alert on it.
How does Tuco know which Customer.io person a reply belongs to?
Send recipientEmail alongside recipientPhone on the first message. Tuco texts the phone number and stores the email on its lead, and every webhook carries that lead back, so the events journey identifies the person by {{trigger.lead.email}}. If your workspace identifies people by ID instead, store the Customer.io ID somewhere you can match it.
Can a team member answer replies from a shared inbox?
Yes. Every conversation shows up in Tuco's shared inbox with its full history, including media. Set contactOwnerEmail on the send and reply notifications go to that rep instead of the whole team. Handing a conversation to someone else is a change of owner.
Will the follow-up still send if the person already replied?
No. Pass skipIfReplied: true on follow-up steps. If the contact replied at any point, Tuco returns skippedOnly: true and sends nothing, and it checks again right before a queued message goes out. You do not need a reply branch in Customer.io for this.
Can I send links, images or voice notes?
Yes, from the second message on. The first message to a new contact must be plain text: links are not clickable and attachments do not go through until the contact has replied. Put the link or media in the follow-up or in the rep's reply.
What about contacts on Android?
Tuco checks iMessage availability before every send. When a number cannot receive iMessage, Tuco fires message.fallback and the events journey records tuco_message_fallback, so Customer.io can send the same text by SMS or email. Alternatively the line can send peer-to-peer SMS from the same number, with no A2P 10DLC registration.
How many messages can one line send?
By default each line sends to 50 new contacts and 150 messages in total per day, paced at about 7 new contacts per hour. Follow-ups to people already in a conversation do not count toward the new-contact cap. Limits can be raised for opt-in traffic, and you scale by adding lines.
Is it compliant to send iMessage from Customer.io?
Only message people who opted in to texts, keep the send window inside local daytime hours, and stop the moment someone opts out. STOP replies arrive as tuco_message_reply events, so mark the person unsubscribed in Customer.io and every journey exits.
Related
Add iMessage to your Customer.io journeys
A dedicated line, flat monthly pricing, live in about 48 hours.