Appearance
Text Messages
SMS and WhatsApp through Twilio or Vonage: bookings confirmed and reminded, orders and the owner's news texted, every text logged and STOP honoured.
kitloom/text-messages · admins: kitloom/text-messages-filament, kitloom/text-messages-nova
Video lessons
Recorded in the Filament admin; the Nova admin works the same way.
Text messages — SMS and WhatsApp — for the site's own news to the people it serves: a booking received, confirmed and reminded the day before, an order received and completed, a new booking on the staff member's phone, and the owner's notifications on theirs. A text is read in a minute; a reminder text is what cuts no-shows. The site sends through Twilio or Vonage, with an account of yours there that you pay per text; every text is logged, a person who writes STOP is not texted again, and the emails go on as before beside the texts. The admin is kitloom/text-messages-filament or kitloom/text-messages-nova.
SMS works on day one. WhatsApp needs a WhatsApp Business number and templates Meta approved — days of waiting, see WhatsApp.
Twilio
- Open an account at twilio.com. A trial account comes with a little credit and a number; it texts only the phones you verify under Phone Numbers → Verified Caller IDs, and each text begins Sent from your Twilio trial account. Upgrade (add a card) to text anyone.
- Buy a number that can send SMS in your country: Phone Numbers → Buy a number. In Canada and the US a business number must also be registered for texting (Twilio walks you through A2P 10DLC or a toll-free verification); until it is, carriers drop the texts.
- On the Twilio console's front page: the Account SID (AC…) and the Auth token.
- In the admin, Text Messages → Settings: provider Twilio, the Account SID, the auth token, and the number as the SMS sender (+12045550123). Or, if you use a Messaging Service, its SID (MG…): it picks the number itself.
- Save. The page now shows two addresses. Twilio is told the Status URL with every text, so delivery receipts need nothing more. Copy the Inbound URL to Phone Numbers → your number → Messaging → A message comes in, Webhook, HTTP POST: that is how the site hears STOP, START and the replies.
- Send a test at the top of the page, to your own phone.
Twilio signs every call to the site with your auth token; the site answers only the signed ones.
Vonage
- Open an account at vonage.com (the API dashboard). The dashboard's front page shows the API key and the API secret.
- API Settings: under SMS settings, choose Messages API; and copy the Signature secret (it signs the webhooks; signature method SHA-256 HMAC).
- Numbers → Buy numbers: a number for SMS in your country — or, in many countries outside North America, no number at all: a sender name of up to 11 letters (Marginalia).
- Applications → Create a new application, give it a name, switch on Messages, and paste the two addresses from Text Messages → Settings (choose Vonage there first and save): the Inbound URL and the Status URL, both POST. Then Link your number to the application.
- In the admin, Text Messages → Settings: provider Vonage, the API key, the API secret, the signature secret, and the number or the name as the SMS sender. Save, and Send a test.
Without the signature secret the site cannot tell Vonage's calls from anyone's and refuses them: the log stops at Sent and STOP is not heard.
What is texted, and when
Each text goes to the phone the person typed on the booking form or at the checkout, beside the email, in the language they booked or ordered in. Under What is texted in the settings each group is switched on or off; only the modules the site runs are listed.
| Kind | When | To | What it says |
|---|---|---|---|
| Booking received | a booking waits for the salon's word | the client | Marginalia: we got your booking for Haircut, Tue 3 Nov 10:00 (code 7KQ2MN). We will confirm it shortly. |
| Booking confirmed | confirmed — at once, or by the staff | the client | … is confirmed. See you Tue 3 Nov 10:00. Manage: https://…/booking/7KQ2MN/xY3… |
| Booking reminder | the hours before, as Bookings → Settings say, with the reminder email | the client | Reminder: Haircut, Tue 3 Nov 10:00, at Marginalia, 12 Rue Sainte-Catherine. |
| Booking declined, cancelled | as their emails | the client | one line each |
| New booking | a booking made | the staff member's phone (their card in Bookings → Staff) | New booking: Haircut, Tue 3 Nov 10:00, Anna K. Confirm: https://… — or — confirmed. |
| Booking cancelled | a booking cancelled | the staff member | Booking cancelled: Haircut, Tue 3 Nov 10:00, Anna K. |
| Order received | when the shop's own "new order" email goes: at once for a transfer or cash, once paid for a card | the billing phone | Book Nook: thank you, order #1042 ($84.00) is received. |
| Order completed, cancelled, refunded | as their emails | the billing phone | one line each, the amount of a refund |
| The owner's notification | a channel of type SMS or WhatsApp in Notifications → Channels | your phone | the notice's title and lines on one line, the admin's address after |
The links in texts are short ones (/booking/7KQ2MN/xY3…) that open the same pages as the emails' buttons and live as long; the emails keep their own. The shop's texts send nobody anywhere: the account page is where orders are.
Not texted, on purpose: the "time to book again" email (it is marketing — a text would need its own consent), comments, form messages, low stock. You have your channels for those; your clients have their email.
Consent and STOP
What the site texts is the person's own transaction: the booking they made, the order they placed, the reminder of their appointment — what Canada's anti-spam law and the US TCPA treat as consented to by the transaction itself. No offer, no newsletter goes by text in this version.
A person who answers STOP (or STOPALL, UNSUBSCRIBE, CANCEL, END, QUIT, ARRET) is not texted again on that channel — every text to them is logged Said STOP instead — until they answer START. Twilio keeps its own list too, for Canada and the US, and answers STOP itself. To take a number off the list for someone who asked you instead, Messages → Stop texting a number; the same button puts it back.
WhatsApp
A business may write first on WhatsApp only with a template Meta approved, with numbered blanks — 1, 2 — the site fills. So WhatsApp here works kind by kind: a kind with a template goes by WhatsApp, the rest by SMS (or not at all, with SMS off).
A Meta business account and a number. At Twilio: Messaging → Senders → WhatsApp senders → Create, which takes you through Meta's sign-up with your business and a number that is not on WhatsApp yet. At Vonage: External accounts → WhatsApp → Connect, the same sign-up. Meta checks the business; it takes from a day to a few.
The templates, one per kind you want on WhatsApp, category Utility, in each language your clients book in, with the blanks in this order:
Kind {{1}} {{2}} {{3}} {{4}} {{5}} Booking received the client's name the service the time the booking code the site's name Booking confirmed the client's name the service the time the link to manage it the site's name Booking reminder the client's name the service the time the place the site's name Booking declined the client's name the service the time the site's name Booking cancelled the client's name the service the time the site's name New booking (staff) the service the time the client's name the link to confirm it, or "confirmed" the site's name Booking cancelled (staff) the service the time the client's name the site's name Order received the customer's name the order number the total the site's name Order completed, cancelled the customer's name the order number the total the site's name Order refunded the customer's name the order number the amount refunded the site's name The owner's notification the title the details, on one line For example, Booking reminder: "Hi {{1}}, a reminder of your {{2}} on {{3}} at {{4}}. See you soon — {{5}}." At Twilio a template is made in Messaging → Content Template Builder and sent to WhatsApp for approval; its id is the Content SID (HX…). At Vonage it is made in Meta's WhatsApp Manager → Message templates; its id is its name (
booking_reminder), and the site sends the language the client booked in — submit each language under the same name.In the admin, Text Messages → Settings: switch Send WhatsApp on, the WhatsApp number as WhatsApp sender, and paste each approved template's id next to its kind under WhatsApp templates. A kind left empty goes by SMS.
A WhatsApp number is just a phone: the person needs nothing more than WhatsApp on it. A person who answers STOP on WhatsApp is off the WhatsApp list and may still get SMS.
When something goes wrong
Every text is a row under Text Messages → Messages: to whom, what about, by which channel, and how it ended — Queued, Sent, Delivered (the provider's receipt), Not delivered or Failed with the provider's words, No phone number (what was typed is no number of any country), Said STOP. A text is sent from the queue: a provider that is busy gets it again, three tries; one that refuses it gets it no more.
A phone typed without its +code is read as one of the site's country — Country in the settings, else the shop's. Without either, only numbers with a +code are texted: a guessed prefix would text a stranger.
Ten texts in a row that the provider refuses — a wrong key, an empty balance — turn sending off: nothing more goes, bookings and orders go by email only, you get an email about it, and Site Health says so. Fix it, then Send a test: a text that gets through turns sending back on, and so does saving the provider's settings.
Settings
Text Messages → Settings:
- Provider — Twilio, Vonage or none; its Account SID or API key, its auth token or API secret (and Vonage's signature secret) — kept encrypted with the application key, never shown back; an empty field keeps the saved one.
- SMS sender — a number in international form, or a name where the country allows one (not Canada or the US); Twilio's Messaging Service instead, if you have one.
- Webhooks — the two addresses to paste at the provider.
- Send SMS (on), Send WhatsApp (off), the WhatsApp sender.
- Country — two letters, for the phones typed without their +code.
- What is texted — bookings to the client, bookings to the staff member, orders to the customer; each on.
- WhatsApp templates — an id per kind.
- Keep the words, days (90) — older texts lose their words and the template's blanks once a day; the rows stay for counts.
Beyond the screens
- Notifications (kitloom/notifications): two more kinds of channel, SMS and WhatsApp, to your own phone, offered once texts are set up; and the topic Text messages failing (on by default), danger once sending has turned itself off.
- Site Health (kitloom/health): "Text messages" warns while the last text failed and fails while sending is off.
- AI agents (kitloom/ai):
text_messages_list— "Did Anna get her reminder?" —text_messages_send_testandtext_messages_opt_out, for whoever manages the texts. The keys stay in the admin. - Export and Erase Personal Data (kitloom/privacy): the texts of the person's phone — found through the texts sent with their address — are exported; erased, they lose their words and the number. A STOP is kept: forgetting it would let the site text them again.
What is inside
| What | Where |
|---|---|
| The log | table text_messages (the site's prefix in front), model Kitloom\TextMessages\Models\TextMessage |
| STOP | text_opt_outs — a phone and a channel |
| Settings | options text_messages_*; the secrets encrypted, the failures counted there |
| Webhooks | /wp-json/text-messages/v1/status/{twilio,vonage} and /inbound/{…}, signed by the provider |
| The console | text-messages:prune (--days=), daily on the scheduler |
| Permission | manage_text_messages, administrators |
| Configuration | config/kitloom-text-messages.php — the queue, the timeout, the webhooks' throttle, the tags |
The module binds wp-schema's Kitloom\WpSchema\Messaging\TextMessenger: bookings, the shop and notifications text through that contract and the TextChannel of Laravel's notifications, and never name this module. Twilio and Vonage are reached on Laravel's HTTP client, one call each, no SDK; a third provider is a class implementing Providers\Provider. Phones are made E.164 with libphonenumber.
What the application overrides
- The model:
ModelRegistry::use(TextMessage::KEY, App\Models\TextMessage::class). - The queue, the timeout, the webhooks, the admin sections and the tags for agents, notifications, health and privacy:
php artisan vendor:publish --tag=kitloom-text-messages-config.
Tests
From the application that installs the package: php vendor/bin/phpunit --testsuite TextMessages. Kitloom\TextMessages\Testing\FakesProviders sets a provider up and answers for it on the HTTP client; a module that texts tests with wp-schema's FakesTexts instead.