PHP code example of r0bdiabl0 / laravel-email-tracker
1. Go to this page and download the library: Download r0bdiabl0/laravel-email-tracker library. Choose the download type require.
2. Extract the ZIP file and open the index.php.
3. Add this code to the index.php.
<?php
require_once('vendor/autoload.php');
/* Start to develop here. Best regards https://php-download.com/ */
r0bdiabl0 / laravel-email-tracker example snippets
// config/mail.php
'mailers' => [
// Resend API transport (recommended for full tracking)
'resend' => [
'transport' => 'resend',
'key' => env('RESEND_API_KEY'),
],
// Postal API transport (recommended for full tracking)
'postal' => [
'transport' => 'postal',
'url' => env('POSTAL_URL'),
'key' => env('POSTAL_API_KEY'),
],
// SES, Mailgun, Postmark use Laravel/Symfony built-in transports
// SendGrid uses SMTP
],
use R0bdiabl0\EmailTracker\Facades\EmailTracker;
// Uses the default provider (from EMAIL_TRACKER_DEFAULT_PROVIDER)
EmailTracker::enableAllTracking()
->to('[email protected]')
->send(new WelcomeMail($user));
// Override to use a specific provider for this send
// Automatically switches to the Resend transport
EmailTracker::provider('resend')
->enableAllTracking()
->to('[email protected]')
->send(new WelcomeMail($user));
// Use Mailgun for transactional emails
EmailTracker::provider('mailgun')
->enableAllTracking()
->to('[email protected]')
->send(new OrderConfirmation($order));
use R0bdiabl0\EmailTracker\Facades\EmailTracker;
// Enable all tracking (opens, links, bounces, complaints, deliveries)
// Note: This does NOT enable unsubscribe headers - use enableUnsubscribeHeaders() separately
EmailTracker::enableAllTracking()
->to('[email protected]')
->send(new WelcomeMail($user));
// With unsubscribe headers for bulk/marketing emails
EmailTracker::enableAllTracking()
->enableUnsubscribeHeaders() // Add RFC 8058 List-Unsubscribe headers
->to('[email protected]')
->send(new MarketingMail($user));
// With batch grouping for campaigns
EmailTracker::enableAllTracking()
->setBatch('welcome-campaign-2024')
->to('[email protected]')
->send(new WelcomeMail($user));
// Enable specific tracking only
EmailTracker::enableOpenTracking()
->enableLinkTracking()
->to('[email protected]')
->send(new WelcomeMail($user));
// Specify provider explicitly
EmailTracker::provider('resend')
->enableAllTracking()
->to('[email protected]')
->send(new WelcomeMail($user));
use Illuminate\Mail\Mailable;
use R0bdiabl0\EmailTracker\Traits\TracksWithEmail;
class WelcomeMail extends Mailable
{
use TracksWithEmail;
public function build()
{
return $this->view('emails.welcome');
}
}
// Static methods for quick sending
WelcomeMail::sendTracked('[email protected]', batch: 'welcome');
WelcomeMail::queueTracked(['[email protected]'], batch: 'welcome', queue: 'emails');
use Illuminate\Notifications\Notification;
use R0bdiabl0\EmailTracker\Notifications\EmailTrackerChannel;
class WelcomeNotification extends Notification
{
public function via($notifiable): array
{
return [EmailTrackerChannel::class];
}
public function toEmailTracker($notifiable): Mailable
{
return new WelcomeMail($notifiable);
}
}
use R0bdiabl0\EmailTracker\Contracts\SentEmailContract;
use R0bdiabl0\EmailTracker\Contracts\UnsubscribeUrlGenerator;
// In a service provider's register() method:
$this->app->bind(UnsubscribeUrlGenerator::class, function () {
return new class implements UnsubscribeUrlGenerator
{
public function generate(SentEmailContract $email): string
{
// Return whatever persistent URL your application uses.
return route('my.unsubscribe', ['token' => /* your token for $email */]);
}
};
});
use R0bdiabl0\EmailTracker\Events\EmailUnsubscribeEvent;
// In EventServiceProvider
protected $listen = [
EmailUnsubscribeEvent::class => [
\App\Listeners\HandleUnsubscribe::class,
],
];
namespace App\Listeners;
use R0bdiabl0\EmailTracker\Events\EmailUnsubscribeEvent;
class HandleUnsubscribe
{
public function handle(EmailUnsubscribeEvent $event): void
{
$email = $event->email;
$messageId = $event->messageId;
$sentEmail = $event->sentEmail; // May be null
// Update user preferences
User::where('email', $email)
->update(['marketing_emails' => false]);
// Or remove from specific mailing list based on batch
if ($sentEmail && $sentEmail->batch) {
MailingListSubscription::where('email', $email)
->where('list', $sentEmail->batch->name)
->delete();
}
Log::info("User unsubscribed", ['email' => $email]);
}
}
// bootstrap/app.php (Laravel 11+)
->withMiddleware(function (Middleware $middleware) {
$middleware->validateCsrfTokens(except: [
'email-tracker/webhook/*', // Adjust if using custom EMAIL_TRACKER_ROUTE_PREFIX
'email-tracker/unsubscribe',
]);
})
// In your event listener
if (RateLimiter::tooManyAttempts('unsubscribe:' . $event->email, 5)) {
Log::warning('Unsubscribe rate limit exceeded', ['email' => $event->email]);
return;
}
RateLimiter::hit('unsubscribe:' . $event->email, 3600);
use R0bdiabl0\EmailTracker\Events\EmailSentEvent;
use R0bdiabl0\EmailTracker\Events\EmailDeliveryEvent;
use R0bdiabl0\EmailTracker\Events\EmailBounceEvent;
use R0bdiabl0\EmailTracker\Events\EmailComplaintEvent;
use R0bdiabl0\EmailTracker\Events\EmailOpenEvent;
use R0bdiabl0\EmailTracker\Events\EmailLinkClickEvent;
use R0bdiabl0\EmailTracker\Events\EmailUnsubscribeEvent;
protected $listen = [
EmailBounceEvent::class => [
\App\Listeners\HandleEmailBounce::class,
],
EmailComplaintEvent::class => [
\App\Listeners\HandleEmailComplaint::class,
],
EmailUnsubscribeEvent::class => [
\App\Listeners\HandleUnsubscribe::class,
],
];
namespace App\Listeners;
use R0bdiabl0\EmailTracker\Events\EmailBounceEvent;
class HandleEmailBounce
{
public function handle(EmailBounceEvent $event): void
{
$bounce = $event->emailBounce;
$email = $bounce->email;
$type = $bounce->type; // 'Permanent' or 'Transient'
$metadata = $bounce->metadata; // Raw webhook payload
if ($type === 'Permanent') {
// Mark user as having invalid email
User::where('email', $email)->update(['email_valid' => false]);
}
// Access provider-specific diagnostic information from metadata
// For SES: $metadata['bounce']['bouncedRecipients'][0]['diagnosticCode']
// For Mailgun: $metadata['event-data']['delivery-status']['message']
// For SendGrid: $metadata['reason']
$diagnosticCode = $this->extractDiagnosticCode($metadata, $bounce->provider);
// Log for monitoring
Log::warning("Email bounced", [
'email' => $email,
'type' => $type,
'provider' => $bounce->provider,
'diagnostic_code' => $diagnosticCode,
]);
}
private function extractDiagnosticCode(?array $metadata, string $provider): ?string
{
if (!$metadata) {
return null;
}
return match ($provider) {
'ses' => $metadata['bounce']['bouncedRecipients'][0]['diagnosticCode'] ?? null,
'mailgun' => $metadata['event-data']['delivery-status']['message'] ?? null,
'sendgrid' => $metadata['reason'] ?? null,
'postmark' => $metadata['Description'] ?? null,
default => null,
};
}
}
use R0bdiabl0\EmailTracker\Services\EmailValidator;
// Check if email should be blocked
if (EmailValidator::shouldBlock('[email protected]')) {
return; // Don't send
}
// Get specific counts
$bounceCount = EmailValidator::getBounceCount('[email protected]');
$hasComplaint = EmailValidator::hasComplaint('[email protected]');
// Filter a list of emails
$validEmails = EmailValidator::filterBlockedEmails($emailList);
// In your event listener - metadata is ALWAYS available
public function handle(EmailBounceEvent $event): void
{
$metadata = $event->emailBounce->metadata; // Available regardless of store_metadata config
$diagnosticCode = $metadata['bounce']['bouncedRecipients'][0]['diagnosticCode'] ?? null;
// Process in real-time...
}
// Delete bounce/complaint records older than 90 days
EmailBounce::where('bounced_at', '<', now()->subDays(90))->delete();
EmailComplaint::where('complained_at', '<', now()->subDays(90))->delete();
use R0bdiabl0\EmailTracker\Models\SentEmail;
use R0bdiabl0\EmailTracker\Models\Batch;
// Get all bounced emails
$bounced = SentEmail::bounced()->get();
// Get all emails that received complaints
$complained = SentEmail::complained()->get();
// Get emails by provider
$sesEmails = SentEmail::forProvider('ses')->get();
// Get delivered emails
$delivered = SentEmail::delivered()->get();
// Get emails for a specific address
$userEmails = SentEmail::forEmail('[email protected]')->get();
// Get batch with all emails
$batch = Batch::where('name', 'campaign-2024')->with('sentEmails')->first();
// Check if specific email bounced
$email = SentEmail::where('message_id', $messageId)->first();
if ($email->wasBounced()) {
// Handle bounce
}
// Still works!
SesMail::enableAllTracking()->to($email)->send($mailable);
use R0bdiabl0\EmailTrackerFilament\EmailTrackerFilamentPlugin;
public function panel(Panel $panel): Panel
{
return $panel
->plugins([
EmailTrackerFilamentPlugin::make(),
]);
}
namespace App\EmailProviders;
use Carbon\Carbon;
use Illuminate\Http\Request;
use R0bdiabl0\EmailTracker\DataTransferObjects\EmailEventData;
use R0bdiabl0\EmailTracker\Enums\EmailEventType;
use R0bdiabl0\EmailTracker\Providers\AbstractProvider;
use Symfony\Component\HttpFoundation\Response;
class CustomSmtpProvider extends AbstractProvider
{
/**
* Unique provider name (used in routes and database).
*/
public function getName(): string
{
return 'custom-smtp';
}
/**
* Handle incoming webhook from your email provider.
*/
public function handleWebhook(Request $request, ?string $event = null): Response
{
$this->logRawPayload($request);
// Validate webhook signature
if (! $this->validateSignature($request)) {
return response()->json(['error' => 'Invalid signature'], 403);
}
$payload = $request->all();
$eventType = $payload['event_type'] ?? 'unknown';
// Parse into standardized format
$data = $this->parsePayload($payload);
// Route to appropriate handler based on event type
// The base class helpers expect EmailEventData objects
return match ($eventType) {
'bounce' => $this->processBounceEvent($data),
'complaint' => $this->processComplaintEvent($data),
'delivered' => $this->processDeliveryEvent($data),
default => response()->json(['success' => true]),
};
}
/**
* Parse webhook payload into standardized EmailEventData.
*/
public function parsePayload(array $payload): EmailEventData
{
return new EmailEventData(
messageId: $payload['message_id'] ?? '',
email: $payload['recipient'] ?? '',
provider: $this->getName(),
eventType: $this->mapEventType($payload['event_type'] ?? ''),
timestamp: isset($payload['timestamp'])
? Carbon::parse($payload['timestamp'])
: null,
bounceType: $payload['bounce_type'] ?? null,
metadata: $payload,
);
}
/**
* Validate webhook signature/authenticity.
*/
public function validateSignature(Request $request): bool
{
$secret = $this->getConfig('webhook_secret');
if (! $secret) {
return true; // Skip validation if no secret configured
}
$signature = $request->header('X-Custom-Signature');
$expectedSignature = hash_hmac('sha256', $request->getContent(), $secret);
return hash_equals($expectedSignature, $signature ?? '');
}
/**
* Map provider event types to EmailEventType enum.
*/
protected function mapEventType(string $event): EmailEventType
{
return match ($event) {
'bounce' => EmailEventType::Bounced,
'complaint' => EmailEventType::Complained,
'delivered' => EmailEventType::Delivered,
'opened' => EmailEventType::Opened,
'clicked' => EmailEventType::Clicked,
default => EmailEventType::Sent,
};
}
}
use R0bdiabl0\EmailTracker\Facades\EmailTracker;
use App\EmailProviders\CustomSmtpProvider;
public function boot(): void
{
EmailTracker::registerProvider('custom-smtp', CustomSmtpProvider::class);
}
namespace App\Models;
use R0bdiabl0\EmailTracker\Models\SentEmail as BaseSentEmail;
class TrackedEmail extends BaseSentEmail
{
// Add custom methods or relationships
public function user()
{
return $this->belongsTo(User::class, 'email', 'email');
}
}
bash
php artisan email-tracker:install
Loading please wait ...
Before you can download the PHP files, the dependencies should be resolved. This can take some minutes. Please be patient.