# Campaign Hooks
These action hooks fire during campaign lifecycle events, email sending, and email template design rendering.
## Campaign Lifecycle
### `fluent_crm/campaign_created`
This action runs after a campaign has been created.
**Parameters**
- `$campaign` [Campaign Model](/database/models/campaign)
**Usage:**
```php
add_action('fluent_crm/campaign_created', function($campaign) {
// Do your stuff here
});
```
**Source:** `app/Http/Controllers/CampaignController.php`, `app/Modules/MCP/Tools/CampaignTools.php`
---
### `fluent_crm/campaign_data_updated`
This action runs after a campaign has been updated.
**Parameters**
- `$campaign` [Campaign Model](/database/models/campaign)
- `$postedData` Array - update data
**Usage:**
```php
add_action('fluent_crm/campaign_data_updated', function($campaign, $postedData) {
// Do your stuff here
}, 10, 2);
```
**Source:** `app/Http/Controllers/CampaignController.php`, `app/Modules/MCP/Tools/CampaignTools.php`
---
### `fluent_crm/update_campaign_compose`
This action fires when the campaign compose/body step is saved.
**Parameters**
- `$data` Array - compose data
- `$campaign` [Campaign Model](/database/models/campaign)
**Usage:**
```php
add_action('fluent_crm/update_campaign_compose', function($data, $campaign) {
// Campaign body/compose step was saved
}, 10, 2);
```
**Source:** `app/Http/Controllers/CampaignController.php`, `app/Hooks/Handlers/FunnelHandler.php`
---
### `fluent_crm/update_campaign_subjects`
This action fires when the campaign subject step is saved.
**Parameters**
- `$data` Array - subject data
- `$campaign` [Campaign Model](/database/models/campaign)
**Usage:**
```php
add_action('fluent_crm/update_campaign_subjects', function($data, $campaign) {
// Campaign subject step was saved
}, 10, 2);
```
**Source:** `app/Http/Controllers/CampaignController.php`
---
### `fluent_crm/campaign_deleted`
This action runs after a campaign has been deleted.
**Parameters**
- `$campaignId` INT - deleted campaign ID
**Usage:**
```php
add_action('fluent_crm/campaign_deleted', function($campaignId) {
// Do your stuff here
});
```
**Source:** `app/Http/Controllers/CampaignController.php`, `app/Modules/MCP/Tools/CampaignTools.php`, `fluentcampaign-pro/app/Http/Controllers/RecurringCampaignController.php`
---
### `fluent_crm/campaign_duplicated`
This action runs after a campaign has been duplicated.
**Parameters**
- `$newCampaign` [Campaign Model](/database/models/campaign) - new Campaign
- `$oldCampaign` [Campaign Model](/database/models/campaign) - original Campaign
**Usage:**
```php
add_action('fluent_crm/campaign_duplicated', function($newCampaign, $oldCampaign) {
// Do your stuff here
}, 10, 2);
```
**Source:** `app/Http/Controllers/CampaignController.php`, `app/Modules/MCP/Tools/CampaignTools.php`
---
### `fluent_crm/campaign_archived`
This action fires when a campaign is archived (either manually or automatically after all emails are sent).
**Parameters**
- `$campaign` [Campaign Model](/database/models/campaign)
**Usage:**
```php
add_action('fluent_crm/campaign_archived', function($campaign) {
// Campaign was archived
});
```
**Source:** `app/Http/Controllers/CampaignController.php`, `app/Hooks/Handlers/Scheduler.php`
---
## Campaign Sending
### `fluent_crm/campaign_recipients_query_updated`
This action runs when recipients are being updated.
**Parameters**
- `$campaign` [Campaign Model](/database/models/campaign)
**Usage:**
```php
add_action('fluent_crm/campaign_recipients_query_updated', function($campaign) {
// Do your stuff here
});
```
**Source:** `app/Http/Controllers/CampaignController.php`
---
### `fluent_crm/campaign_scheduled`
This action runs when a campaign is being scheduled for later.
**Parameters**
- `$campaign` [Campaign Model](/database/models/campaign)
- `$scheduleAt` Date Time (Y-m-d H:i:s format)
**Usage:**
```php
add_action('fluent_crm/campaign_scheduled', function($campaign, $scheduleAt) {
// Do your stuff here
}, 10, 2);
```
**Source:** `app/Http/Controllers/CampaignController.php`, `app/Modules/MCP/Tools/CampaignTools.php`
---
### `fluent_crm/campaign_set_send_now`
This action runs when a campaign is set to send immediately.
**Parameters**
- `$campaign` [Campaign Model](/database/models/campaign)
**Usage:**
```php
add_action('fluent_crm/campaign_set_send_now', function($campaign) {
// Do your stuff here
});
```
**Source:** `app/Http/Controllers/CampaignController.php`, `app/Modules/MCP/Tools/CampaignTools.php`
---
### `fluent_crm/campaign_processing_start`
This action runs when emails of a campaign start being processed.
**Parameters**
- `$campaign` [Campaign Model](/database/models/campaign)
**Usage:**
```php
add_action('fluent_crm/campaign_processing_start', function($campaign) {
// Do your stuff here
});
```
**Source:** `app/Http/Controllers/CampaignController.php`
---
### `fluent_crm/campaign_processing_failed`
This action fires when a scheduled campaign's recipient selection cannot be resolved while its
emails are being materialized — typically a dynamic segment whose provider is no longer registered
(FluentCampaign Pro deactivated, license lapsed) or a deleted segment. Before the hook fires, the
campaign has been moved from `processing` back to `draft`, its half-created email rows have been
deleted, and a human-readable explanation has been stored in the `_processing_error` campaign meta.
::: warning The only programmatic failure signal
Nothing is emailed to the site admin when this happens — the campaign just sits in draft with an
error note in the UI. If you need alerting (Slack, email, monitoring), this hook is the place to
attach it.
:::
**Parameters**
- `$campaign` [Campaign Model](/database/models/campaign) - already reverted to `status = 'draft'`
- `$reason` String - failure reason code; currently always `'recipients_unresolvable'`
**Usage:**
```php
add_action('fluent_crm/campaign_processing_failed', function($campaign, $reason) {
wp_mail(
get_option('admin_email'),
'FluentCRM campaign failed: ' . $campaign->title,
'Reason: ' . $reason . '. The campaign was moved back to draft.'
);
}, 10, 2);
```
**Source:** `app/Services/CampaignProcessor.php`
---
### `fluent_crm/sending_emails_starting`
This action fires at the start of each email sending batch (campaigns, sequences, and automations all use this).
**Parameters**
- `$campaignEmails` Collection - CampaignEmail models being sent in this batch
**Usage:**
```php
add_action('fluent_crm/sending_emails_starting', function($campaignEmails) {
// A batch of emails is about to be sent
});
```
**Source:** `app/Services/Libs/Mailer/BaseHandler.php`
---
### `fluentcrm_sending_emails_done`
This action fires after a batch of emails has been sent.
**Parameters**
- `$campaignEmails` Collection - CampaignEmail models that were sent
**Usage:**
```php
add_action('fluentcrm_sending_emails_done', function($campaignEmails) {
// A batch of emails finished sending
});
```
**Source:** `app/Services/Libs/Mailer/BaseHandler.php`
---
## Email Link Redirect
### `fluentcrm_email_url_click`
This action fires on every tracked email link click, immediately before the visitor is redirected to
the destination URL. By the time it runs, all click bookkeeping has already happened: the click
counter and open flag on the email row are updated, the click metric row is recorded, and
[`fluent_crm/email_url_clicked`](/hooks/actions/contact-activity#fluent-crm-email-url-clicked) has
fired (for attributable clicks).
::: warning Fires even for unattributed clicks
Unlike `fluent_crm/email_url_clicked`, this hook also fires when the click could **not** be tied to
a contact — a missing/invalid `mid`, a failed security-token check, or an anonymized click. In those
cases `$mailId` may be `false` or point to an email whose metrics were not updated. It only requires
that the short URL resolved and a destination URL exists.
Your callback runs after `nocache_headers()` and right before `wp_redirect()` + `exit` — do not
produce output, and keep it fast: every tracked click in every email waits on it.
:::
**Parameters**
- `$redirectUrl` String - the final destination URL the visitor is being redirected to (UTM parameters already appended)
- `$mailId` Integer|false - the `fc_campaign_emails` row ID from the `mid` query parameter, or `false` when absent
- `$urlData` Object - the raw `fc_url_stores` row as a plain `stdClass` (not a [UrlStores Model](/database/models/url-store) instance): `id`, `url` (the original long URL), `short`, plus a transient `url_token` property when the click carried a security hash
**Usage:**
```php
add_action('fluentcrm_email_url_click', function($redirectUrl, $mailId, $urlData) {
// Log the click before the redirect happens
}, 10, 3);
```
**Source:** `app/Hooks/Handlers/RedirectionHandler.php`
---
## Newsletter Archive
These hooks fire when rendering the public newsletter archive pages.
### `fluent_crm/before_newsletter_archive`
Fires before rendering the newsletter archive listing page.
**Parameters**
- `$newsletters` Array - newsletter items
- `$campaigns` Collection - campaign models
**Usage:**
```php
add_action('fluent_crm/before_newsletter_archive', function($newsletters, $campaigns) {
// Add custom content before the archive list
}, 10, 2);
```
**Source:** `fluentcampaign-pro/app/Views/all_newsletters.php`
---
### `fluent_crm/after_newsletter_archive`
Fires after rendering the newsletter archive listing page.
**Parameters**
- `$newsletters` Array - newsletter items
- `$campaigns` Collection - campaign models
**Usage:**
```php
add_action('fluent_crm/after_newsletter_archive', function($newsletters, $campaigns) {
// Add custom content after the archive list
}, 10, 2);
```
**Source:** `fluentcampaign-pro/app/Views/all_newsletters.php`
---
### `fluent_crm/before_newsletter_single`
Fires before rendering a single newsletter page.
**Parameters**
- `$newsletter` Array - newsletter data
- `$campaign` Campaign Model
**Usage:**
```php
add_action('fluent_crm/before_newsletter_single', function($newsletter, $campaign) {
// Add custom content before the newsletter
}, 10, 2);
```
**Source:** `fluentcampaign-pro/app/Views/single_newsletter.php`
---
### `fluent_crm/after_newsletter_single`
Fires after rendering a single newsletter page.
**Parameters**
- `$newsletter` Array - newsletter data
- `$campaign` Campaign Model
**Usage:**
```php
add_action('fluent_crm/after_newsletter_single', function($newsletter, $campaign) {
// Add custom scripts or content after the newsletter
}, 10, 2);
```
**Source:** `fluentcampaign-pro/app/Views/single_newsletter.php`
---
## Email Template Design
### `fluent_crm/email_header`
If you want to add your own custom CSS for a specific email template or all email templates then you can use this hook.
**Parameters**
- `$designSlug` String - Design Name (classic | plain | raw_classic | simple | block_editor)
**Usage:**
```php
/*
* Add Custom CSS for plain design type
*/
add_action('fluent_crm/email_header', function($designName) {
if($designName == 'plain') {
?>