Practical Hook Examples
This page provides comprehensive, production-ready examples for common use cases. All examples are tested against the actual Mumara Campaigns codebase.
CRM Integration Examples
Sync Contacts to HubSpot
<?php
// File: includes/hooks/hubspot-sync.php
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;
/**
* Sync new contacts to HubSpot CRM
*/
add_hook('AddContact', 5, function($vars) {
try {
$subscriber = $vars['subscriber'];
$fields = $vars['fields'];
$hubspotData = [
'properties' => [
'email' => $subscriber->email,
'firstname' => $fields['first_name'] ?? '',
'lastname' => $fields['last_name'] ?? '',
'phone' => $fields['phone'] ?? '',
'company' => $fields['company'] ?? '',
'mumara_list_id' => $subscriber->list_id,
'mumara_subscriber_id' => $subscriber->id,
]
];
$response = Http::withHeaders([
'Authorization' => 'Bearer ' . config('services.hubspot.token'),
'Content-Type' => 'application/json',
])->post('https://api.hubapi.com/crm/v3/objects/contacts', $hubspotData);
if ($response->successful()) {
Log::info('HubSpot sync successful', [
'subscriber_id' => $subscriber->id,
'hubspot_id' => $response->json('id')
]);
} else {
Log::error('HubSpot sync failed', [
'subscriber_id' => $subscriber->id,
'error' => $response->body()
]);
}
} catch (\Exception $e) {
Log::error('HubSpot sync exception: ' . $e->getMessage());
}
});
/**
* Update HubSpot when contact is edited
*/
add_hook('EditContact', 5, function($vars) {
try {
$subscriber = $vars['subscriber'];
$fields = $vars['fields'];
// Search for existing contact in HubSpot
$searchResponse = Http::withHeaders([
'Authorization' => 'Bearer ' . config('services.hubspot.token'),
])->post('https://api.hubapi.com/crm/v3/objects/contacts/search', [
'filterGroups' => [[
'filters' => [[
'propertyName' => 'email',
'operator' => 'EQ',
'value' => $subscriber->email
]]
]]
]);
if ($searchResponse->successful() && $searchResponse->json('total') > 0) {
$hubspotId = $searchResponse->json('results.0.id');
Http::withHeaders([
'Authorization' => 'Bearer ' . config('services.hubspot.token'),
])->patch("https://api.hubapi.com/crm/v3/objects/contacts/{$hubspotId}", [
'properties' => [
'firstname' => $fields['first_name'] ?? '',
'lastname' => $fields['last_name'] ?? '',
'phone' => $fields['phone'] ?? '',
]
]);
}
} catch (\Exception $e) {
Log::error('HubSpot update exception: ' . $e->getMessage());
}
});
/**
* Remove from HubSpot when contact is deleted
*/
add_hook('DeleteContact', 5, function($vars) {
try {
$email = $vars->email;
// Search and archive contact in HubSpot
$searchResponse = Http::withHeaders([
'Authorization' => 'Bearer ' . config('services.hubspot.token'),
])->post('https://api.hubapi.com/crm/v3/objects/contacts/search', [
'filterGroups' => [[
'filters' => [[
'propertyName' => 'email',
'operator' => 'EQ',
'value' => $email
]]
]]
]);
if ($searchResponse->successful() && $searchResponse->json('total') > 0) {
$hubspotId = $searchResponse->json('results.0.id');
Http::withHeaders([
'Authorization' => 'Bearer ' . config('services.hubspot.token'),
])->delete("https://api.hubapi.com/crm/v3/objects/contacts/{$hubspotId}");
Log::info('HubSpot contact archived', ['email' => $email]);
}
} catch (\Exception $e) {
Log::error('HubSpot delete exception: ' . $e->getMessage());
}
});
Sync Contacts to Salesforce
<?php
// File: includes/hooks/salesforce-sync.php
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Log;
/**
* Get Salesforce access token with caching
*/
function getSalesforceToken() {
return Cache::remember('salesforce_token', 3500, function() {
$response = Http::asForm()->post(config('services.salesforce.auth_url') . '/services/oauth2/token', [
'grant_type' => 'client_credentials',
'client_id' => config('services.salesforce.client_id'),
'client_secret' => config('services.salesforce.client_secret'),
]);
return $response->json('access_token');
});
}
/**
* Sync new contacts to Salesforce as Leads
*/
add_hook('AddContact', 5, function($vars) {
try {
$subscriber = $vars['subscriber'];
$fields = $vars['fields'];
$token = getSalesforceToken();
$instanceUrl = config('services.salesforce.instance_url');
$leadData = [
'Email' => $subscriber->email,
'FirstName' => $fields['first_name'] ?? 'Unknown',
'LastName' => $fields['last_name'] ?? 'Contact',
'Company' => $fields['company'] ?? 'Not Provided',
'Phone' => $fields['phone'] ?? '',
'LeadSource' => 'Mumara Campaigns',
'Mumara_List_ID__c' => (string)$subscriber->list_id,
'Mumara_Subscriber_ID__c' => (string)$subscriber->id,
];
$response = Http::withHeaders([
'Authorization' => 'Bearer ' . $token,
'Content-Type' => 'application/json',
])->post("{$instanceUrl}/services/data/v57.0/sobjects/Lead", $leadData);
if ($response->successful()) {
Log::info('Salesforce Lead created', [
'subscriber_id' => $subscriber->id,
'salesforce_id' => $response->json('id')
]);
}
} catch (\Exception $e) {
Log::error('Salesforce sync exception: ' . $e->getMessage());
}
});
Notification Examples
Slack Notifications for Campaign Events
<?php
// File: includes/hooks/slack-notifications.php
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;
/**
* Send Slack notification when campaign starts
*/
add_hook('StartBroadcast', 1, function($vars) {
try {
$scheduleId = $vars->id;
$campaignId = $vars->campaign_id;
$totalRecipients = $vars->total_subscribers;
$campaign = \App\Models\Campaign::find($campaignId);
$campaignName = $campaign->name ?? 'Unknown Campaign';
sendSlackNotification([
'text' => ":rocket: Campaign Started",
'attachments' => [[
'color' => '#36a64f',
'fields' => [
['title' => 'Campaign', 'value' => $campaignName, 'short' => true],
['title' => 'Recipients', 'value' => number_format($totalRecipients), 'short' => true],
['title' => 'Schedule ID', 'value' => $scheduleId, 'short' => true],
['title' => 'Started At', 'value' => now()->format('M j, Y g:i A'), 'short' => true],
]
]]
]);
} catch (\Exception $e) {
Log::error('Slack notification failed: ' . $e->getMessage());
}
});
/**
* Send Slack notification when campaign completes
*/
add_hook('CompleteBroadcast', 1, function($vars) {
try {
$scheduleId = $vars['schedule_id'];
$campaignId = $vars['campaign_id'];
$totalSent = $vars['total_sent'] ?? 0;
$campaign = \App\Models\Campaign::find($campaignId);
$campaignName = $campaign->name ?? 'Unknown Campaign';
// Get campaign statistics
$schedule = \App\Models\CampaignSchedule::find($scheduleId);
$bounced = $schedule->bounced ?? 0;
$opened = $schedule->opened ?? 0;
sendSlackNotification([
'text' => ":white_check_mark: Campaign Completed",
'attachments' => [[
'color' => '#2eb886',
'fields' => [
['title' => 'Campaign', 'value' => $campaignName, 'short' => true],
['title' => 'Total Sent', 'value' => number_format($totalSent), 'short' => true],
['title' => 'Bounced', 'value' => number_format($bounced), 'short' => true],
['title' => 'Opened', 'value' => number_format($opened), 'short' => true],
]
]]
]);
} catch (\Exception $e) {
Log::error('Slack notification failed: ' . $e->getMessage());
}
});
/**
* Alert when campaign is paused by system
*/
add_hook('SystemPauseBroadcast', 1, function($vars) {
try {
$campaignId = $vars['campaign_id'];
$reason = $vars['reason'] ?? 'Unknown reason';
$campaign = \App\Models\Campaign::find($campaignId);
$campaignName = $campaign->name ?? 'Unknown Campaign';
sendSlackNotification([
'text' => ":warning: Campaign Auto-Paused",
'attachments' => [[
'color' => '#ff0000',
'fields' => [
['title' => 'Campaign', 'value' => $campaignName, 'short' => true],
['title' => 'Reason', 'value' => $reason, 'short' => true],
['title' => 'Action Required', 'value' => 'Please review campaign immediately', 'short' => false],
]
]]
]);
} catch (\Exception $e) {
Log::error('Slack alert failed: ' . $e->getMessage());
}
});
/**
* Helper function to send Slack notifications
*/
function sendSlackNotification(array $payload) {
$webhookUrl = config('services.slack.webhook_url');
if (!$webhookUrl) {
return;
}
Http::post($webhookUrl, $payload);
}
Email Notifications for Domain Events
<?php
// File: includes/hooks/domain-notifications.php
use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Facades\Log;
/**
* Notify admin when domain verification fails
*/
add_hook('SendingDomainDnsFailed', 1, function($vars) {
try {
$domain = $vars->domain ?? $vars['domain'] ?? 'Unknown';
$userId = $vars->user_id ?? $vars['user_id'] ?? null;
$user = $userId ? \App\Models\User::find($userId) : null;
$adminEmail = config('mail.admin_email');
if ($adminEmail) {
Mail::raw(
"DNS verification failed for domain: {$domain}\n" .
"User: " . ($user ? $user->email : 'Unknown') . "\n" .
"Time: " . now()->format('M j, Y g:i A'),
function($message) use ($adminEmail, $domain) {
$message->to($adminEmail)
->subject("DNS Verification Failed: {$domain}");
}
);
}
} catch (\Exception $e) {
Log::error('Domain notification failed: ' . $e->getMessage());
}
});
/**
* Notify user when domain is verified
*/
add_hook('SendingDomainVerified', 1, function($vars) {
try {
$domain = $vars->domain ?? $vars['domain'] ?? 'Unknown';
$userId = $vars->user_id ?? $vars['user_id'] ?? null;
$user = $userId ? \App\Models\User::find($userId) : null;
if ($user && $user->email) {
Mail::raw(
"Great news! Your sending domain {$domain} has been verified.\n\n" .
"You can now start sending emails using this domain.\n\n" .
"Best regards,\nMumara Campaigns",
function($message) use ($user, $domain) {
$message->to($user->email)
->subject("Domain Verified: {$domain}");
}
);
}
} catch (\Exception $e) {
Log::error('Domain verification notification failed: ' . $e->getMessage());
}
});
Webhook Examples
Send Webhooks on Contact Events
<?php
// File: includes/hooks/contact-webhooks.php
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\DB;
/**
* Send webhook when contact is added
*/
add_hook('AddContact', 10, function($vars) {
try {
$subscriber = $vars['subscriber'];
$fields = $vars['fields'];
// Get configured webhooks for this list
$webhooks = DB::table('webhooks')
->where('list_id', $subscriber->list_id)
->where('event', 'contact_added')
->where('status', 'active')
->get();
foreach ($webhooks as $webhook) {
$payload = [
'event' => 'contact_added',
'timestamp' => now()->toIso8601String(),
'data' => [
'subscriber_id' => $subscriber->id,
'email' => $subscriber->email,
'list_id' => $subscriber->list_id,
'fields' => $fields,
'created_at' => $subscriber->created_at,
]
];
// Add signature for security
$signature = hash_hmac('sha256', json_encode($payload), $webhook->secret);
Http::withHeaders([
'Content-Type' => 'application/json',
'X-Mumara-Signature' => $signature,
'X-Mumara-Event' => 'contact_added',
])->timeout(10)->post($webhook->url, $payload);
}
} catch (\Exception $e) {
Log::error('Webhook delivery failed: ' . $e->getMessage());
}
});
/**
* Send webhook when email is opened
*/
add_hook('EmailOpened', 5, function($vars) {
try {
$subscriberId = $vars['subscriber_id'];
$scheduleId = $vars['schedule_id'];
$broadcastId = $vars['broadcast_id'];
$subscriber = \App\Models\Subscriber::find($subscriberId);
if (!$subscriber) {
return;
}
$webhooks = DB::table('webhooks')
->where('list_id', $subscriber->list_id)
->where('event', 'email_opened')
->where('status', 'active')
->get();
foreach ($webhooks as $webhook) {
$payload = [
'event' => 'email_opened',
'timestamp' => now()->toIso8601String(),
'data' => [
'subscriber_id' => $subscriberId,
'email' => $subscriber->email,
'campaign_id' => $broadcastId,
'schedule_id' => $scheduleId,
]
];
$signature = hash_hmac('sha256', json_encode($payload), $webhook->secret);
Http::withHeaders([
'X-Mumara-Signature' => $signature,
'X-Mumara-Event' => 'email_opened',
])->timeout(10)->post($webhook->url, $payload);
}
} catch (\Exception $e) {
Log::error('Open webhook failed: ' . $e->getMessage());
}
});
/**
* Send webhook when link is clicked
*/
add_hook('LinkClicked', 5, function($vars) {
try {
$subscriberId = $vars['subscriber_id'];
$listId = $vars['list_id'];
$url = $vars['url'];
$broadcastId = $vars['broadcast_id'];
$subscriber = \App\Models\Subscriber::find($subscriberId);
$webhooks = DB::table('webhooks')
->where('list_id', $listId)
->where('event', 'link_clicked')
->where('status', 'active')
->get();
foreach ($webhooks as $webhook) {
$payload = [
'event' => 'link_clicked',
'timestamp' => now()->toIso8601String(),
'data' => [
'subscriber_id' => $subscriberId,
'email' => $subscriber->email ?? '',
'campaign_id' => $broadcastId,
'clicked_url' => $url,
]
];
$signature = hash_hmac('sha256', json_encode($payload), $webhook->secret);
Http::withHeaders([
'X-Mumara-Signature' => $signature,
'X-Mumara-Event' => 'link_clicked',
])->timeout(10)->post($webhook->url, $payload);
}
} catch (\Exception $e) {
Log::error('Click webhook failed: ' . $e->getMessage());
}
});
Analytics & Tracking Examples
Custom Analytics Integration
<?php
// File: includes/hooks/analytics.php
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;
/**
* Track email opens in custom analytics
*/
add_hook('EmailOpened', 1, function($vars) {
try {
$subscriberId = $vars['subscriber_id'];
$scheduleId = $vars['schedule_id'];
$broadcastId = $vars['broadcast_id'];
// Send to analytics service
Http::post(config('services.analytics.endpoint') . '/events', [
'event' => 'email_open',
'properties' => [
'subscriber_id' => $subscriberId,
'campaign_id' => $broadcastId,
'schedule_id' => $scheduleId,
'timestamp' => now()->toIso8601String(),
]
]);
} catch (\Exception $e) {
Log::error('Analytics tracking failed: ' . $e->getMessage());
}
});
/**
* Track link clicks with UTM parameters
*/
add_hook('LinkClicked', 1, function($vars) {
try {
$url = $vars['url'];
$subscriberId = $vars['subscriber_id'];
$broadcastId = $vars['broadcast_id'];
// Parse URL for analytics
$parsedUrl = parse_url($url);
parse_str($parsedUrl['query'] ?? '', $queryParams);
Http::post(config('services.analytics.endpoint') . '/events', [
'event' => 'link_click',
'properties' => [
'subscriber_id' => $subscriberId,
'campaign_id' => $broadcastId,
'url' => $url,
'domain' => $parsedUrl['host'] ?? '',
'utm_source' => $queryParams['utm_source'] ?? '',
'utm_medium' => $queryParams['utm_medium'] ?? '',
'utm_campaign' => $queryParams['utm_campaign'] ?? '',
'timestamp' => now()->toIso8601String(),
]
]);
} catch (\Exception $e) {
Log::error('Click analytics failed: ' . $e->getMessage());
}
});
/**
* Track campaign completion metrics
*/
add_hook('CompleteBroadcast', 1, function($vars) {
try {
$scheduleId = $vars['schedule_id'];
$campaignId = $vars['campaign_id'];
$schedule = \App\Models\CampaignSchedule::find($scheduleId);
if (!$schedule) {
return;
}
Http::post(config('services.analytics.endpoint') . '/campaigns', [
'campaign_id' => $campaignId,
'schedule_id' => $scheduleId,
'metrics' => [
'total_sent' => $schedule->total_sent ?? 0,
'total_delivered' => $schedule->delivered ?? 0,
'total_bounced' => $schedule->bounced ?? 0,
'total_opened' => $schedule->opened ?? 0,
'total_clicked' => $schedule->clicked ?? 0,
'total_unsubscribed' => $schedule->unsubscribed ?? 0,
'duration_seconds' => $schedule->created_at->diffInSeconds($schedule->completed_at),
],
'completed_at' => now()->toIso8601String(),
]);
} catch (\Exception $e) {
Log::error('Campaign analytics failed: ' . $e->getMessage());
}
});
Google Analytics Integration
<?php
// File: includes/hooks/google-analytics.php
/**
* Add Google Analytics tracking to all pages
*/
add_hook('HeadEnd', 1, function($vars) {
$trackingId = config('services.google.analytics_id');
if (!$trackingId) {
return '';
}
return <<<HTML
<!-- Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id={$trackingId}"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', '{$trackingId}', {
'custom_map': {
'dimension1': 'user_id',
'dimension2': 'user_role'
}
});
</script>
HTML;
});
/**
* Track user events
*/
add_hook('BodyEnd', 5, function($vars) {
$user = $vars['user'] ?? auth()->user();
if (!$user) {
return '';
}
$userId = $user->id;
$userRole = $user->role ?? 'user';
return <<<HTML
<script>
gtag('set', 'user_properties', {
'user_id': '{$userId}',
'user_role': '{$userRole}'
});
</script>
HTML;
});
UI Customization Examples
Custom Dashboard Widgets
<?php
// File: includes/hooks/dashboard-widgets.php
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Auth;
/**
* Add custom statistics widget to dashboard
*/
add_hook('addPageHtml', 1, function($vars) {
$route = $vars['route'] ?? '';
if ($route !== 'dashboard') {
return '';
}
$user = Auth::user();
// Get custom stats
$stats = [
'total_sent_today' => DB::table('campaign_schedule_logs')
->where('user_id', $user->id)
->whereDate('created_at', today())
->count(),
'opens_today' => DB::table('email_opens')
->whereDate('created_at', today())
->whereIn('campaign_schedule_id', function($query) use ($user) {
$query->select('id')
->from('campaign_schedules')
->where('user_id', $user->id);
})
->count(),
'clicks_today' => DB::table('email_clicks')
->whereDate('created_at', today())
->whereIn('campaign_schedule_id', function($query) use ($user) {
$query->select('id')
->from('campaign_schedules')
->where('user_id', $user->id);
})
->count(),
];
return <<<HTML
<div class="col-xl-4">
<div class="card card-custom gutter-b">
<div class="card-header">
<div class="card-title">
<h3 class="card-label">Today's Activity</h3>
</div>
</div>
<div class="card-body">
<div class="d-flex align-items-center justify-content-between mb-4">
<span class="text-muted font-weight-bold">Emails Sent</span>
<span class="font-weight-bolder text-primary">{$stats['total_sent_today']}</span>
</div>
<div class="d-flex align-items-center justify-content-between mb-4">
<span class="text-muted font-weight-bold">Opens</span>
<span class="font-weight-bolder text-success">{$stats['opens_today']}</span>
</div>
<div class="d-flex align-items-center justify-content-between">
<span class="text-muted font-weight-bold">Clicks</span>
<span class="font-weight-bolder text-info">{$stats['clicks_today']}</span>
</div>
</div>
</div>
</div>
HTML;
});
Custom Navigation Menu Items
<?php
// File: includes/hooks/custom-menu.php
use Illuminate\Support\Facades\Auth;
/**
* Add custom menu items to sidebar
*/
add_hook('PrimaryMenu', 5, function($vars) {
$user = Auth::user();
if (!$user) {
return '';
}
// Only show to admins
if (!$user->isAdmin()) {
return '';
}
return <<<HTML
<li class="kt-menu__item" aria-haspopup="true">
<a href="/admin/reports" class="kt-menu__link">
<span class="kt-menu__link-icon">
<i class="flaticon-graph"></i>
</span>
<span class="kt-menu__link-text">Custom Reports</span>
</a>
</li>
<li class="kt-menu__item kt-menu__item--submenu" aria-haspopup="true" data-ktmenu-submenu-toggle="hover">
<a href="javascript:;" class="kt-menu__link kt-menu__toggle">
<span class="kt-menu__link-icon">
<i class="flaticon-settings"></i>
</span>
<span class="kt-menu__link-text">Advanced Tools</span>
<i class="kt-menu__ver-arrow la la-angle-right"></i>
</a>
<div class="kt-menu__submenu">
<span class="kt-menu__arrow"></span>
<ul class="kt-menu__subnav">
<li class="kt-menu__item" aria-haspopup="true">
<a href="/admin/tools/bulk-actions" class="kt-menu__link">
<i class="kt-menu__link-bullet kt-menu__link-bullet--dot"><span></span></i>
<span class="kt-menu__link-text">Bulk Actions</span>
</a>
</li>
<li class="kt-menu__item" aria-haspopup="true">
<a href="/admin/tools/data-cleanup" class="kt-menu__link">
<i class="kt-menu__link-bullet kt-menu__link-bullet--dot"><span></span></i>
<span class="kt-menu__link-text">Data Cleanup</span>
</a>
</li>
</ul>
</div>
</li>
HTML;
});
Custom Alert Bar Messages
<?php
// File: includes/hooks/alert-messages.php
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Cache;
/**
* Show system maintenance warning
*/
add_hook('AlertBar', 1, function($vars) {
$maintenanceDate = config('app.scheduled_maintenance');
if (!$maintenanceDate) {
return '';
}
$maintenanceTime = \Carbon\Carbon::parse($maintenanceDate);
if ($maintenanceTime->isPast()) {
return '';
}
$formattedTime = $maintenanceTime->format('M j, Y \a\t g:i A');
return <<<HTML
<div class="alert alert-warning alert-dismissible fade show mb-3" role="alert">
<strong><i class="fas fa-exclamation-triangle mr-2"></i>Scheduled Maintenance:</strong>
The system will be briefly unavailable on {$formattedTime} for scheduled maintenance.
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
</div>
HTML;
});
/**
* Show sending limit warning
*/
add_hook('alerts', 1, function($vars) {
$user = Auth::user();
if (!$user) {
return '';
}
// Check if user is approaching sending limit
$dailyLimit = $user->daily_email_limit ?? 10000;
$sentToday = Cache::remember("sent_today_{$user->id}", 300, function() use ($user) {
return \App\Models\CampaignScheduleLog::where('user_id', $user->id)
->whereDate('created_at', today())
->count();
});
$percentUsed = ($sentToday / $dailyLimit) * 100;
if ($percentUsed < 80) {
return '';
}
$remaining = $dailyLimit - $sentToday;
$alertClass = $percentUsed >= 95 ? 'alert-danger' : 'alert-warning';
return <<<HTML
<div class="alert {$alertClass} mb-3">
<strong>Sending Limit Warning:</strong>
You have used {$percentUsed}% of your daily sending limit.
{$remaining} emails remaining for today.
</div>
HTML;
});
Data Validation Examples
Block Disposable Email Addresses
<?php
// File: includes/hooks/email-validation.php
use Illuminate\Support\Facades\Log;
/**
* Block disposable email addresses
*/
add_hook('AddContact', 1, function($vars) {
$subscriber = $vars['subscriber'];
$email = $subscriber->email;
// List of known disposable email domains
$disposableDomains = [
'tempmail.com',
'throwaway.com',
'fakeinbox.com',
'guerrillamail.com',
'mailinator.com',
'10minutemail.com',
'temp-mail.org',
'disposablemail.com',
'trashmail.com',
'yopmail.com',
];
$domain = strtolower(substr(strrchr($email, "@"), 1));
if (in_array($domain, $disposableDomains)) {
Log::warning("Blocked disposable email: {$email}");
// Delete the subscriber that was just created
$subscriber->delete();
throw new \Exception('Disposable email addresses are not allowed');
}
});
/**
* Validate email format and MX records
*/
add_hook('AddContact', 2, function($vars) {
$subscriber = $vars['subscriber'];
$email = $subscriber->email;
$domain = substr(strrchr($email, "@"), 1);
// Check MX records
if (!checkdnsrr($domain, 'MX')) {
Log::warning("Invalid email domain (no MX): {$email}");
// Mark as invalid but don't delete
$subscriber->update(['status' => 'invalid']);
}
});
Validate Custom Field Data
<?php
// File: includes/hooks/field-validation.php
use Illuminate\Support\Facades\Log;
/**
* Validate phone number format
*/
add_hook('AddContact', 1, function($vars) {
$fields = $vars['fields'];
if (isset($fields['phone']) && !empty($fields['phone'])) {
$phone = preg_replace('/[^0-9+]/', '', $fields['phone']);
// Basic phone validation (adjust pattern as needed)
if (!preg_match('/^\+?[1-9]\d{6,14}$/', $phone)) {
Log::warning("Invalid phone format: {$fields['phone']}");
// Update to cleaned version
$vars['subscriber']->update(['phone' => $phone]);
}
}
});
/**
* Normalize country names
*/
add_hook('AddContact', 2, function($vars) {
$fields = $vars['fields'];
$subscriber = $vars['subscriber'];
if (isset($fields['country']) && !empty($fields['country'])) {
$countryMap = [
'usa' => 'United States',
'us' => 'United States',
'uk' => 'United Kingdom',
'uae' => 'United Arab Emirates',
];
$normalized = $countryMap[strtolower($fields['country'])] ?? $fields['country'];
if ($normalized !== $fields['country']) {
// Update with normalized value
\App\Models\SubscriberField::where('subscriber_id', $subscriber->id)
->where('field_name', 'country')
->update(['field_value' => $normalized]);
}
}
});
Automation Examples
Auto-Tag Contacts Based on Behavior
<?php
// File: includes/hooks/auto-tagging.php
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
/**
* Tag contacts who open emails
*/
add_hook('EmailOpened', 5, function($vars) {
try {
$subscriberId = $vars['subscriber_id'];
// Count total opens
$openCount = DB::table('email_opens')
->where('subscriber_id', $subscriberId)
->count();
// Apply tags based on engagement level
$tagId = null;
if ($openCount >= 10) {
$tagId = getOrCreateTag('highly-engaged');
} elseif ($openCount >= 5) {
$tagId = getOrCreateTag('engaged');
} elseif ($openCount >= 1) {
$tagId = getOrCreateTag('active');
}
if ($tagId) {
DB::table('subscriber_tags')->updateOrInsert(
['subscriber_id' => $subscriberId, 'tag_id' => $tagId],
['updated_at' => now()]
);
}
} catch (\Exception $e) {
Log::error('Auto-tagging failed: ' . $e->getMessage());
}
});
/**
* Tag contacts who click links
*/
add_hook('LinkClicked', 5, function($vars) {
try {
$subscriberId = $vars['subscriber_id'];
$url = $vars['url'];
// Tag based on link categories
if (str_contains($url, '/pricing') || str_contains($url, '/buy')) {
$tagId = getOrCreateTag('interested-buyer');
DB::table('subscriber_tags')->updateOrInsert(
['subscriber_id' => $subscriberId, 'tag_id' => $tagId],
['updated_at' => now()]
);
}
if (str_contains($url, '/demo') || str_contains($url, '/trial')) {
$tagId = getOrCreateTag('demo-interested');
DB::table('subscriber_tags')->updateOrInsert(
['subscriber_id' => $subscriberId, 'tag_id' => $tagId],
['updated_at' => now()]
);
}
} catch (\Exception $e) {
Log::error('Click tagging failed: ' . $e->getMessage());
}
});
/**
* Helper to get or create tag
*/
function getOrCreateTag($tagName) {
$tag = DB::table('tags')->where('name', $tagName)->first();
if ($tag) {
return $tag->id;
}
return DB::table('tags')->insertGetId([
'name' => $tagName,
'created_at' => now(),
'updated_at' => now(),
]);
}
Trigger Workflows on Events
<?php
// File: includes/hooks/workflow-triggers.php
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
/**
* Start welcome sequence when contact is added
*/
add_hook('AutomationAddContact', 5, function($vars) {
try {
$subscriberId = $vars['subscriber_id'];
$listId = $vars['list_id'];
// Find welcome drip campaign for this list
$welcomeDrip = DB::table('autoresponders')
->where('list_id', $listId)
->where('type', 'welcome')
->where('status', 'active')
->first();
if ($welcomeDrip) {
// Add subscriber to drip campaign
DB::table('drip_subscribers')->insert([
'autoresponder_id' => $welcomeDrip->id,
'subscriber_id' => $subscriberId,
'status' => 'active',
'started_at' => now(),
'created_at' => now(),
]);
Log::info("Started welcome sequence for subscriber {$subscriberId}");
}
} catch (\Exception $e) {
Log::error('Welcome sequence failed: ' . $e->getMessage());
}
});
/**
* Trigger re-engagement campaign for inactive contacts
*/
add_hook('AfterCron', 10, function($vars) {
try {
// Find contacts who haven't opened in 30 days
$inactiveContacts = DB::table('subscribers')
->select('subscribers.id', 'subscribers.list_id')
->leftJoin('email_opens', function($join) {
$join->on('subscribers.id', '=', 'email_opens.subscriber_id')
->where('email_opens.created_at', '>', now()->subDays(30));
})
->whereNull('email_opens.id')
->where('subscribers.status', 'active')
->where('subscribers.created_at', '<', now()->subDays(30))
->limit(100)
->get();
foreach ($inactiveContacts as $contact) {
// Find re-engagement campaign
$reEngagementDrip = DB::table('autoresponders')
->where('list_id', $contact->list_id)
->where('type', 're-engagement')
->where('status', 'active')
->first();
if ($reEngagementDrip) {
DB::table('drip_subscribers')->updateOrInsert(
[
'autoresponder_id' => $reEngagementDrip->id,
'subscriber_id' => $contact->id,
],
[
'status' => 'active',
'started_at' => now(),
'updated_at' => now(),
]
);
}
}
} catch (\Exception $e) {
Log::error('Re-engagement automation failed: ' . $e->getMessage());
}
});
Security & Compliance Examples
GDPR Compliance Logging
<?php
// File: includes/hooks/gdpr-compliance.php
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
/**
* Log all contact data changes for GDPR compliance
*/
add_hook('AddContact', 1, function($vars) {
$subscriber = $vars['subscriber'];
DB::table('gdpr_audit_log')->insert([
'action' => 'contact_created',
'subscriber_id' => $subscriber->id,
'email' => $subscriber->email,
'ip_address' => request()->ip(),
'user_agent' => request()->userAgent(),
'data' => json_encode([
'list_id' => $subscriber->list_id,
'source' => 'api', // or 'web_form', 'import', etc.
]),
'created_at' => now(),
]);
});
add_hook('EditContact', 1, function($vars) {
$subscriber = $vars['subscriber'];
$fields = $vars['fields'];
DB::table('gdpr_audit_log')->insert([
'action' => 'contact_updated',
'subscriber_id' => $subscriber->id,
'email' => $subscriber->email,
'ip_address' => request()->ip(),
'user_agent' => request()->userAgent(),
'data' => json_encode(['updated_fields' => array_keys($fields)]),
'created_at' => now(),
]);
});
add_hook('DeleteContact', 1, function($vars) {
DB::table('gdpr_audit_log')->insert([
'action' => 'contact_deleted',
'subscriber_id' => $vars->id,
'email' => $vars->email,
'ip_address' => request()->ip(),
'user_agent' => request()->userAgent(),
'data' => json_encode(['reason' => 'user_request']),
'created_at' => now(),
]);
});
/**
* Log unsubscribe events
*/
add_hook('TimelineUnsubEmail', 1, function($vars) {
DB::table('gdpr_audit_log')->insert([
'action' => 'unsubscribe',
'subscriber_id' => $vars['subscriber_id'],
'email' => '',
'ip_address' => request()->ip(),
'user_agent' => request()->userAgent(),
'data' => json_encode([
'campaign_id' => $vars['broadcast_id'],
'list_id' => $vars['list_id'],
]),
'created_at' => now(),
]);
});
Rate Limiting
<?php
// File: includes/hooks/rate-limiting.php
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Log;
/**
* Rate limit contact additions per IP
*/
add_hook('AddContact', 1, function($vars) {
$ip = request()->ip();
$cacheKey = "contact_add_rate_{$ip}";
$count = Cache::get($cacheKey, 0);
if ($count >= 100) { // Max 100 contacts per hour per IP
Log::warning("Rate limit exceeded for IP: {$ip}");
throw new \Exception('Rate limit exceeded. Please try again later.');
}
Cache::put($cacheKey, $count + 1, 3600);
});
/**
* Rate limit web form submissions
*/
add_hook('AddWebForm', 1, function($vars) {
$userId = auth()->id();
$cacheKey = "webform_create_rate_{$userId}";
$count = Cache::get($cacheKey, 0);
if ($count >= 10) { // Max 10 forms per hour
throw new \Exception('You have reached the maximum number of forms you can create per hour.');
}
Cache::put($cacheKey, $count + 1, 3600);
});
Debugging & Logging Examples
Comprehensive Hook Logging
<?php
// File: includes/hooks/debug-logging.php
use Illuminate\Support\Facades\Log;
/**
* Log all contact operations for debugging
*/
if (config('app.debug')) {
add_hook('AddContact', 99, function($vars) {
Log::debug('AddContact hook fired', [
'subscriber_id' => $vars['subscriber']->id ?? null,
'email' => $vars['subscriber']->email ?? null,
'fields_count' => count($vars['fields'] ?? []),
]);
});
add_hook('EditContact', 99, function($vars) {
Log::debug('EditContact hook fired', [
'subscriber_id' => $vars['subscriber']->id ?? null,
'fields' => $vars['fields'] ?? [],
]);
});
add_hook('DeleteContact', 99, function($vars) {
Log::debug('DeleteContact hook fired', [
'subscriber_id' => $vars->id ?? null,
'email' => $vars->email ?? null,
]);
});
}
/**
* Log campaign lifecycle for monitoring
*/
add_hook('StartBroadcast', 99, function($vars) {
Log::info('Campaign started', [
'schedule_id' => $vars->id,
'campaign_id' => $vars->campaign_id,
'total_subscribers' => $vars->total_subscribers,
]);
});
add_hook('CompleteBroadcast', 99, function($vars) {
Log::info('Campaign completed', [
'schedule_id' => $vars['schedule_id'],
'campaign_id' => $vars['campaign_id'],
'total_sent' => $vars['total_sent'] ?? 0,
]);
});
add_hook('SystemPauseBroadcast', 99, function($vars) {
Log::warning('Campaign auto-paused', [
'campaign_id' => $vars['campaign_id'],
'reason' => $vars['reason'] ?? 'unknown',
]);
});
Performance Monitoring
<?php
// File: includes/hooks/performance-monitoring.php
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Cache;
/**
* Track hook execution times
*/
$hookTimings = [];
add_hook('AddContact', 0, function($vars) use (&$hookTimings) {
$hookTimings['AddContact_start'] = microtime(true);
});
add_hook('AddContact', 999, function($vars) use (&$hookTimings) {
$start = $hookTimings['AddContact_start'] ?? microtime(true);
$duration = (microtime(true) - $start) * 1000;
if ($duration > 100) { // Log if takes more than 100ms
Log::warning('Slow hook execution: AddContact', [
'duration_ms' => round($duration, 2),
'subscriber_id' => $vars['subscriber']->id ?? null,
]);
}
// Track average times
$key = 'hook_timing_AddContact';
$stats = Cache::get($key, ['total' => 0, 'count' => 0]);
$stats['total'] += $duration;
$stats['count']++;
Cache::put($key, $stats, 3600);
});