# SMS Campaign Hooks These action hooks fire during SMS campaign lifecycle events, message sending, delivery tracking, and subscriber opt-in/out. All SMS hooks require FluentCRM Pro. ## Provider Registration ### `fluent_crm/register_sms_providers` Fires once while the SMS module boots, after the built-in Twilio and AWS drivers are registered. Instantiate your own driver here — `AbstractSMSDriver`'s constructor registers itself with `SMSDriverManager`, which is what feeds the provider dropdown and settings form. This runs before the module's `isActive()` check, so the settings screen always lists your driver even when SMS is not configured yet. **Parameters** _None._ **Usage:** ```php use FluentCampaign\App\Modules\SMS\Providers\AbstractSMSDriver; class MySMSDriver extends AbstractSMSDriver { public function getSlug(): string { return 'my_sms_service'; } public function getLabel(): string { return 'My SMS Service'; } public function getFields(): array { return [ 'api_key' => ['type' => 'text', 'label' => 'API Key'], 'api_secret' => ['type' => 'password', 'label' => 'API Secret'], ]; } public function send(string $to, string $message, array $settings): array { // Return ['status' => 'success', 'provider_message_id' => '...'] on success, // or ['status' => 'error', 'message' => '...'] on failure — the scheduler // checks $result['status'] === 'success' to mark the message sent. } } add_action('fluent_crm/register_sms_providers', function() { new MySMSDriver(); }); ``` **Source:** `fluentcampaign-pro/app/Modules/SMS/SMSModule.php` --- ### `fluent_crm/register_whatsapp_providers` WhatsApp counterpart of [`fluent_crm/register_sms_providers`](#fluent-crm-register-sms-providers). Fires while the SMS module boots, after the built-in WhatsApp drivers are registered. Instantiate your own driver here — `AbstractWhatsAppDriver`'s constructor registers itself with `WhatsAppDriverManager`, which is what feeds the WhatsApp provider dropdown and settings form. ::: warning WhatsApp channel status The WhatsApp channel is feature-flagged off in current Pro builds — the `do_action()` call in `SMSModule::register()` is commented out and `WhatsAppHelper::isActive()` returns `false` — so this hook does not fire yet. It is documented here as the registration point for third-party drivers once the channel is enabled. ::: **Parameters** _None._ **Usage:** ```php use FluentCampaign\App\Modules\SMS\Providers\AbstractWhatsAppDriver; class MyWhatsAppDriver extends AbstractWhatsAppDriver { public function getSlug(): string { return 'my_whatsapp_service'; } public function getLabel(): string { return 'My WhatsApp Service'; } public function getFields(): array { return [ 'api_key' => ['type' => 'text', 'label' => 'API Key', 'required' => true, 'default' => ''], 'api_secret' => ['type' => 'password', 'label' => 'API Secret', 'required' => true, 'default' => ''], ]; } public function send(string $to, string $message, array $settings): array { // Return ['status' => 'success', 'status_code' => 200, 'message' => '...', // 'response' => $response, 'provider_message_id' => '...'] on success, // or ['status' => 'error', 'message' => '...'] on failure — the scheduler // checks $result['status'] === 'success' to mark the message sent. } } add_action('fluent_crm/register_whatsapp_providers', function() { new MyWhatsAppDriver(); }); ``` **Source:** `fluentcampaign-pro/app/Modules/SMS/SMSModule.php` --- ## Campaign Lifecycle ### `fluent_crm/sms_campaign_created` Fires when a new SMS campaign is created. **Parameters** - `$campaign` SMSCampaign Model **Usage:** ```php add_action('fluent_crm/sms_campaign_created', function($campaign) { // New SMS campaign created }); ``` **Source:** `fluentcampaign-pro/app/Modules/SMS/Http/Controllers/SMSController.php` --- ### `fluent_crm/sms_campaign_updated` Fires when an SMS campaign is updated. **Parameters** - `$campaign` SMSCampaign Model **Usage:** ```php add_action('fluent_crm/sms_campaign_updated', function($campaign) { // SMS campaign was modified }); ``` **Source:** `fluentcampaign-pro/app/Modules/SMS/Http/Controllers/SMSController.php` --- ### `fluent_crm/sms_campaign_status_active` Fires at the start of `SMSController::schedule()`, once the campaign has passed the "must still be a draft" guard but **before** any status change is written. ::: warning The campaign passed to this hook still has `status = 'draft'`. Re-read the model if you need the post-schedule status. ::: **Parameters** - `$smsCampaign` SMSCampaign Model - still in `draft` status at this point **Usage:** ```php add_action('fluent_crm/sms_campaign_status_active', function($smsCampaign) { // SMS campaign activated }); ``` **Source:** `fluentcampaign-pro/app/Modules/SMS/Http/Controllers/SMSController.php` --- ### `fluent_crm/sms_campaign_scheduled` Fires when an SMS campaign is scheduled for future sending. It does **not** fire for a send-immediately campaign — that path schedules the batch-generation job directly instead. **Parameters** - `$smsCampaign` SMSCampaign Model - freshly re-read from the database, so its `status` and `scheduled_at` are the saved values - `$scheduledAt` String - the campaign's `scheduled_at` column, a site-local `Y-m-d H:i:s` datetime (not a Unix timestamp) **Usage:** ```php add_action('fluent_crm/sms_campaign_scheduled', function($smsCampaign, $scheduledAt) { // $scheduledAt is a MySQL datetime string, e.g. '2026-08-12 09:30:00' $timestamp = strtotime($scheduledAt); }, 10, 2); ``` **Source:** `fluentcampaign-pro/app/Modules/SMS/Http/Controllers/SMSController.php` --- ### `fluent_crm/sms_campaign_processing_start` Fires when a `pending-scheduled` campaign flips to `processing` — that is, when its scheduled time is less than six minutes away and the admin screen polls for processing stats. The campaign has already been saved with `status = 'processing'` and `recipients_count = 0` when this runs. **Parameters** - `$campaign` SMSCampaign Model - already saved as `processing` **Usage:** ```php add_action('fluent_crm/sms_campaign_processing_start', function($campaign) { // SMS campaign processing started }); ``` **Source:** `fluentcampaign-pro/app/Modules/SMS/Http/Controllers/SMSController.php` --- ### `fluent_crm/sms_campaign_duplicated` Fires when an SMS campaign is duplicated. **Parameters** - `$newCampaign` SMSCampaign Model - the new copy, created as a `draft` with a `[Duplicate] ` title prefix and the original's labels already attached - `$oldCampaign` SMSCampaign Model - the original **Usage:** ```php add_action('fluent_crm/sms_campaign_duplicated', function($newCampaign, $oldCampaign) { // SMS campaign was duplicated }, 10, 2); ``` **Source:** `fluentcampaign-pro/app/Modules/SMS/Http/Controllers/SMSController.php` --- ### `fluent_crm/sms_campaign_archived` Fires when an SMS campaign is archived — either because a recurring/auto-processing campaign has no future runs left, or by the cleanup cron when a finished campaign has no unsent messages remaining. The campaign row is saved as `status = 'archived'` in all cases, but only the first two call sites re-read the model before firing; on the cron path the model still carries its pre-archive status. **Parameters** - `$smsCampaign` SMSCampaign Model - the row is saved as `archived`, though on the cron path the passed instance may still show the previous status **Usage:** ```php add_action('fluent_crm/sms_campaign_archived', function($smsCampaign) { // SMS campaign archived }); ``` **Source:** `fluentcampaign-pro/app/Modules/SMS/Http/Controllers/SMSController.php`, `fluentcampaign-pro/app/Modules/SMS/SMSScheduler.php` --- ### `fluent_crm/sms_campaign_deleted` Fires after an SMS campaign is permanently deleted, both from the single-delete endpoint and once per campaign from the `delete_campaigns` bulk action. ::: warning The campaign row and its message/meta data are already gone when this fires — only the ID is passed. Capture anything you need on `fluent_crm/sms_campaign_updated` instead. ::: **Parameters** - `$campaignId` INT - deleted campaign ID **Usage:** ```php add_action('fluent_crm/sms_campaign_deleted', function($campaignId) { // SMS campaign deleted }); ``` **Source:** `fluentcampaign-pro/app/Modules/SMS/Http/Controllers/SMSController.php` --- ## Sending & Delivery ### `fluent_crm/sms_sent` Fires after an SMS message is successfully sent, once the message row has been marked sent and the campaign's `sent_count` incremented. **Parameters** - `$smsMessage` SMSMessage Model - the pre-update instance, so its `status` still reflects the value from before the send was recorded - `$result` Array - the driver's response; carries `provider_message_id` when the provider returns one **Usage:** ```php add_action('fluent_crm/sms_sent', function($smsMessage, $result) { // SMS sent successfully // $result contains provider-specific response data }, 10, 2); ``` **Source:** `fluentcampaign-pro/app/Modules/SMS/SMSScheduler.php` --- ### `fluent_crm/sms_failed` Fires after an SMS message fails to send, once the message row has been marked `failed` and the campaign's `failed_count` incremented. **Parameters** - `$smsMessage` SMSMessage Model - the pre-update instance, so its `status` still reflects the value from before the failure was recorded - `$errorMessage` String - error message from the provider; also stored on the message's `notes` column **Usage:** ```php add_action('fluent_crm/sms_failed', function($smsMessage, $errorMessage) { // SMS failed - log or retry error_log('SMS failed: ' . $errorMessage); }, 10, 2); ``` **Source:** `fluentcampaign-pro/app/Modules/SMS/SMSScheduler.php` --- ### `fluent_crm/whatsapp_sent` WhatsApp counterpart of [`fluent_crm/sms_sent`](#fluent-crm-sms-sent) — messages route here when their `channel` is `whatsapp`. Fires after a WhatsApp message is successfully sent, once the message row has been marked sent and the campaign's `sent_count` incremented. Note it fires **in addition to** the generic `fluent_crm/sms_sent` (which runs for every channel), not instead of it — the same applies to `whatsapp_failed` and `sms_failed`. **Parameters** - `$smsMessage` SMSMessage Model - the pre-update instance, so its `status` still reflects the value from before the send was recorded - `$result` Array - the WhatsApp driver's response (`status`, `status_code`, `message`, `response`); carries `provider_message_id` when the provider returns one **Usage:** ```php add_action('fluent_crm/whatsapp_sent', function($smsMessage, $result) { // WhatsApp message sent successfully // $result contains provider-specific response data }, 10, 2); ``` **Source:** `fluentcampaign-pro/app/Modules/SMS/SMSScheduler.php` --- ### `fluent_crm/whatsapp_failed` WhatsApp counterpart of [`fluent_crm/sms_failed`](#fluent-crm-sms-failed). Fires after a WhatsApp message fails to send, once the message row has been marked `failed` and the campaign's `failed_count` incremented. Pre-send guards that mark a message failed without attempting a send — for example a contact whose `whatsapp_status` is not `whatsapp_subscribed` — do **not** fire this hook. **Parameters** - `$smsMessage` SMSMessage Model - the pre-update instance, so its `status` still reflects the value from before the failure was recorded - `$errorMessage` String - the driver response's `message`, or `Unknown error` when it carries none; also stored on the message's `notes` column **Usage:** ```php add_action('fluent_crm/whatsapp_failed', function($smsMessage, $errorMessage) { // WhatsApp send failed - log or retry error_log('WhatsApp failed: ' . $errorMessage); }, 10, 2); ``` **Source:** `fluentcampaign-pro/app/Modules/SMS/SMSScheduler.php` --- ## Opt-in & Opt-out ### `fluent_crm/contact_sms_subscribed` Fires when an inbound message opts a contact in to SMS. The contact is matched by `phone`, so nothing fires for an unknown number. The contact's `sms_status` is already saved as `sms_subscribed`. **Parameters** - `$subscriber` [Subscriber Model](/database/models/subscriber) - already saved with `sms_status = 'sms_subscribed'` - `$data` Array - the inbound webhook context; includes a `provider` key **Usage:** ```php add_action('fluent_crm/contact_sms_subscribed', function($subscriber, $data) { // Contact opted in to SMS }, 10, 2); ``` **Source:** `fluentcampaign-pro/app/Modules/SMS/SMSHelper.php` --- ### `fluent_crm/contact_sms_unsubscribed` Fires when an inbound message opts a contact out of SMS. The contact is matched by `phone`, so nothing fires for an unknown number. The contact's `sms_status` is already saved as `sms_unsubscribed`. **Parameters** - `$subscriber` [Subscriber Model](/database/models/subscriber) - already saved with `sms_status = 'sms_unsubscribed'` - `$data` Array - the inbound webhook context; includes a `provider` key **Usage:** ```php add_action('fluent_crm/contact_sms_unsubscribed', function($subscriber, $data) { // Contact opted out of SMS }, 10, 2); ``` **Source:** `fluentcampaign-pro/app/Modules/SMS/SMSHelper.php` --- ### `fluent_crm/contact_whatsapp_subscribed` WhatsApp counterpart of [`fluent_crm/contact_sms_subscribed`](#fluent-crm-contact-sms-subscribed). Fires when an inbound WhatsApp message — a `start`/`subscribe` keyword arriving on the Twilio WhatsApp or Meta Cloud webhook — opts a contact in to WhatsApp. The contact is matched by `phone`, so nothing fires for an unknown number. The contact's `whatsapp_status` is already saved as `whatsapp_subscribed`. **Parameters** - `$subscriber` [Subscriber Model](/database/models/subscriber) - already saved with `whatsapp_status = 'whatsapp_subscribed'` - `$data` Array - the inbound webhook context; includes a `provider` key (`twilio_whatsapp` or `meta_cloud`) **Usage:** ```php add_action('fluent_crm/contact_whatsapp_subscribed', function($subscriber, $data) { // Contact opted in to WhatsApp }, 10, 2); ``` **Source:** `fluentcampaign-pro/app/Modules/SMS/WhatsAppHelper.php` --- ### `fluent_crm/contact_whatsapp_unsubscribed` WhatsApp counterpart of [`fluent_crm/contact_sms_unsubscribed`](#fluent-crm-contact-sms-unsubscribed). Fires when an inbound WhatsApp message — a `stop`/`cancel`/`unsubscribe` keyword arriving on the Twilio WhatsApp or Meta Cloud webhook — opts a contact out of WhatsApp. The contact is matched by `phone`, so nothing fires for an unknown number. The contact's `whatsapp_status` is already saved as `whatsapp_unsubscribed`. **Parameters** - `$subscriber` [Subscriber Model](/database/models/subscriber) - already saved with `whatsapp_status = 'whatsapp_unsubscribed'` - `$data` Array - the inbound webhook context; includes a `provider` key (`twilio_whatsapp` or `meta_cloud`) **Usage:** ```php add_action('fluent_crm/contact_whatsapp_unsubscribed', function($subscriber, $data) { // Contact opted out of WhatsApp }, 10, 2); ``` **Source:** `fluentcampaign-pro/app/Modules/SMS/WhatsAppHelper.php` --- ## Provider Webhooks ### `fluent_crm_sms_custom_provider_webhook` Generic webhook hook for custom SMS providers. Fires when an incoming webhook is received for a provider that has no built-in handler. The signature check has already passed and the payload is sanitized by the time this runs. ::: warning Built-in providers never reach this hook. `twilio`, `twilio_whatsapp` and `meta_cloud` are routed to their own handlers in `SMSHandler`, so only providers registered through [`fluent_crm/register_sms_providers`](#fluent-crm-register-sms-providers) fire it. ::: **Parameters** - `$bodyData` Array - the sanitized webhook request body - `$provider` String - provider slug taken from the webhook URL **Usage:** ```php add_action('fluent_crm_sms_custom_provider_webhook', function($bodyData, $provider) { if ($provider === 'my_sms_service') { // Handle delivery receipt, status update, etc. } }, 10, 2); ``` **Source:** `fluentcampaign-pro/app/Modules/SMS/SMSReceiver.php` --- ### `fluent_crm_sms_{$provider}_webhook` Provider-specific webhook hook, fired immediately after `fluent_crm_sms_custom_provider_webhook`. The hook name includes the provider slug — for a driver whose `getSlug()` returns `my_sms_service`, the hook is `fluent_crm_sms_my_sms_service_webhook`. ::: warning Like the generic hook above, this only fires for custom providers. There is no `fluent_crm_sms_twilio_webhook` — Twilio, Twilio WhatsApp and Meta Cloud are handled by their built-in `SMSReceiver` methods and never reach this dispatcher. ::: **Parameters** - `$bodyData` Array - the sanitized webhook request body **Usage:** ```php add_action('fluent_crm_sms_my_sms_service_webhook', function($bodyData) { // Handle a delivery receipt or inbound reply from your own provider }); ``` **Source:** `fluentcampaign-pro/app/Modules/SMS/SMSReceiver.php`