A comprehensive Laravel application for creating, managing, and tracking email marketing campaigns with advanced analytics
- About
- Features
- Tech Stack
- Getting Started
- Project Structure
- Database Schema
- Email Tracking
- Queue System
- Challenges & Solutions
- Testing
- Performance
- Roadmap
A professional email marketing platform built with Laravel 12 that enables businesses to create, send, and track email campaigns efficiently. Features real-time tracking of email opens and link clicks, template management, subscriber lists, and comprehensive campaign analytics.
Email marketing is crucial for business growth, but existing solutions can be expensive and overly complex. This project demonstrates:
- Backend Architecture - Clean MVC structure with service layer patterns
- Queue Management - Efficient background job processing for bulk email sending
- Database Design - Normalized schema with proper relationships and soft deletes
- Email Tracking - Custom implementation of pixel tracking and link redirects
- Real-time Analytics - Campaign performance metrics and subscriber engagement
- 📧 Create Email Campaigns - Rich text editor with template support
- 📅 Schedule Campaigns - Send immediately or schedule for later
- 📊 Campaign Analytics - Track opens, clicks, and engagement rates
- 🗂️ Template System - Reusable email templates with variable placeholders
- 🔄 Soft Delete - Restore accidentally deleted campaigns
- 👥 Email Lists - Organize subscribers into targeted lists
- ➕ Add Subscribers - Individual or bulk subscriber import
- 📋 List Management - View and manage subscriber details
- 🗑️ Subscriber Removal - Clean list management
- 👁️ Open Tracking - 1x1 pixel tracking for email opens
- 🔗 Click Tracking - Track link clicks within emails
- 📈 Real-time Metrics - Live campaign performance dashboard
- 📊 Engagement Analytics - Detailed subscriber engagement data
- 🔐 Authentication - Laravel Breeze with email verification
- ⚡ Queue System - Background job processing for email sending
- 🎨 Responsive UI - Tailwind CSS for mobile-friendly interface
- 🧪 Testing - Pest PHP test suite
Backend:
- PHP 8.2
- Laravel 12.0 Framework
- Laravel Breeze (Authentication)
- Laravel Queue (Background Jobs)
- Pest PHP (Testing)
Frontend:
- Blade Templates
- Tailwind CSS 3.0
- Alpine.js (via Breeze)
- Vite (Asset Bundling)
Database:
- MySQL 8.0 / PostgreSQL
- Eloquent ORM
- Database Migrations
Development Tools:
- Laravel Debugbar
- LaraDumps
- Laravel Pint (Code Style)
- Composer
Infrastructure:
- Queue Workers
- Email Service (SMTP/Mailgun/etc)
- File Storage
- PHP >= 8.2
- Composer >= 2.0
- Node.js >= 18
- MySQL >= 8.0 or PostgreSQL >= 12
- Mail Server (SMTP credentials)
- Clone the repository
git clone https://github.com/Luan-Neumann-Dev/email-blast.git
cd email-campaign-manager- Install PHP dependencies
composer install- Install Node dependencies
npm install- Environment setup
cp .env.example .env
php artisan key:generate- Configure database and mail in
.env
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=email_campaigns
DB_USERNAME=root
DB_PASSWORD=
MAIL_MAILER=smtp
MAIL_HOST=smtp.mailtrap.io
MAIL_PORT=2525
MAIL_USERNAME=your_username
MAIL_PASSWORD=your_password
MAIL_FROM_ADDRESS=noreply@example.com- Run migrations
php artisan migrate --seed- Build frontend assets
npm run build- Start the development server
# Quick start (runs all services concurrently)
composer dev
# Or manually start each service
php artisan serve
php artisan queue:listen
npm run dev- Access the application
http://localhost:8000
Or use the automated setup:
composer setupemail-campaign-manager/
├── app/
│ ├── Http/
│ │ ├── Controllers/
│ │ │ ├── CampaignController.php # Campaign CRUD
│ │ │ ├── EmailListController.php # Email list management
│ │ │ ├── SubscriberController.php # Subscriber management
│ │ │ ├── TemplateController.php # Template management
│ │ │ └── TrackingController.php # Open/click tracking
│ │ ├── Middleware/
│ │ │ └── CampaignCreateSessionControl.php
│ │ └── Requests/ # Form validation
│ │
│ ├── Jobs/
│ │ ├── SendEmailsCampaignJob.php # Queue campaign
│ │ └── SendEmailCampaignJob.php # Send individual email
│ │
│ ├── Mail/
│ │ └── EmailCampaign.php # Mailable class
│ │
│ └── Models/
│ ├── Campaign.php # Campaign model
│ ├── CampaignMail.php # Tracking model
│ ├── EmailList.php # Email list model
│ ├── Subscriber.php # Subscriber model
│ └── Template.php # Template model
│
├── database/
│ ├── migrations/ # Database schema
│ └── seeders/ # Test data
│
├── resources/
│ └── views/ # Blade templates
│
├── routes/
│ ├── web.php # Web routes
│ └── auth.php # Auth routes
│
└── tests/ # Pest PHP tests
- id (primary key)
- name (string)
- subject (string)
- email_list_id (foreign key)
- template_id (foreign key)
- track_click (boolean)
- track_open (boolean)
- body (text)
- send_at (datetime, nullable)
- created_at, updated_at, deleted_at- id (primary key)
- campaign_id (foreign key)
- subscriber_id (foreign key)
- sent_at (datetime, nullable)
- openings (unsigned small int)
- clicks (unsigned small int)
- created_at, updated_at- id (primary key)
- name (string)
- user_id (foreign key)
- created_at, updated_at- id (primary key)
- email (string, unique per list)
- name (string)
- email_list_id (foreign key)
- created_at, updated_at- id (primary key)
- name (string)
- content (text)
- user_id (foreign key)
- created_at, updated_atUser (1) ──→ (N) EmailList
EmailList (1) ──→ (N) Subscriber
EmailList (1) ──→ (N) Campaign
Template (1) ──→ (N) Campaign
Campaign (1) ──→ (N) CampaignMail
Subscriber (1) ──→ (N) CampaignMail
- When an email is sent, a unique 1x1 transparent pixel is embedded:
<img src="https://yourapp.com/t/{campaign_mail_id}/o" width="1" height="1" />- When the recipient opens the email, the pixel loads
- The tracking route increments the
openingscounter - Returns a transparent 1x1 PNG image
- All links in the email are rewritten to go through the tracking system:
<a href="https://yourapp.com/t/{campaign_mail_id}/c?url={original_url}">- When clicked, the route:
- Increments the
clickscounter - Redirects user to the original URL
- Increments the
// Open tracking
GET /t/{campaignMail}/o
// Click tracking
GET /t/{campaignMail}/c?url={destination}Queues individual emails for all subscribers in a campaign:
public function handle(): void
{
foreach ($this->campaign->emailList->subscribers as $subscriber) {
SendEmailCampaignJob::dispatch($this->campaign, $subscriber);
}
}Sends individual email to a subscriber:
- Creates tracking record in
campaign_mails - Sends email with tracking pixels/links
- Updates
sent_attimestamp
# Development
php artisan queue:listen
# Production (supervisord recommended)
php artisan queue:work --tries=3 --timeout=60Problem: Sending thousands of emails synchronously would timeout and block the application.
Solution: Implemented Laravel Queue system with two-level job dispatching:
// Level 1: Queue the campaign
SendEmailsCampaignJob::dispatch($campaign);
// Level 2: Each subscriber gets their own job
SendEmailCampaignJob::dispatch($campaign, $subscriber);Result:
- Non-blocking email sending
- Automatic retry on failure
- Scalable to thousands of subscribers
- Failed jobs tracked in
failed_jobstable
Problem: Tracking opens and clicks without external services.
Solution: Custom tracking implementation:
- Opens: 1x1 transparent tracking pixel
- Clicks: URL rewriting through tracking controller
- Unique IDs: Each email gets unique
campaign_mail_id
Technologies:
- PHP Image Generation (GD library)
- URL parsing and rewriting
- Database incrementing with
increment()
Result:
- 100% open rate accuracy (when images enabled)
- All click tracking without JavaScript
- Privacy-respecting (no external trackers)
Problem: Campaign creation has multiple steps (details, template, list, schedule).
Solution: Created custom middleware CampaignCreateSessionControl:
- Stores form data in session between steps
- Validates required data before each step
- Clears session after campaign creation
Result:
- Smooth multi-step user experience
- Data persistence across steps
- Clean validation
# Run all tests
php artisan test
# Or using composer script
composer test
# Run specific test file
php artisan test tests/Feature/CampaignTest.php
# Run with coverage
php artisan test --coveragetests/
├── Feature/
│ ├── CampaignTest.php
│ ├── EmailListTest.php
│ └── TrackingTest.php
└── Unit/
├── JobTest.php
└── MailTest.php
- Average Email Send Time: ~200ms per email
- Queue Processing: 100+ emails/minute
- Database Queries: Optimized with eager loading
- Page Load Time: < 500ms (with database cache)
- Eager loading for N+1 query prevention
- Database indexing on foreign keys
- Queue chunking for bulk operations
- Compiled Blade templates
- A/B Testing - Test subject lines and content variations
- Advanced Analytics - Heat maps, time-based engagement
- Subscriber Segmentation - Tag-based filtering
- Email Templates Gallery - Pre-built responsive templates
- Webhook Support - Integration with external services
- CSV Import/Export - Bulk subscriber management
- Campaign Duplication - Clone successful campaigns
- Bounce Handling - Track and manage bounced emails
- Unsubscribe Management - One-click unsubscribe links
- API - RESTful API for integrations
- Dark Mode - UI theme toggle
- Multi-language Support - i18n implementation
- CSRF protection on all forms
- SQL injection prevention via Eloquent ORM
- XSS protection in Blade templates
- Email validation and sanitization
- Rate limiting on tracking endpoints
- Signed routes for email verification
Contributions are welcome! Please feel free to submit a Pull Request.
- Fork the Project
- Create your Feature Branch (
git checkout -b feature/AmazingFeature) - Commit your Changes (
git commit -m 'Add some AmazingFeature') - Push to the Branch (
git push origin feature/AmazingFeature) - Open a Pull Request
Luan Henrique Neumann
- LinkedIn: LuanNeumannDev
- GitHub: @Luan-Neumann-Dev
- Email: luan.neumann.dev@gmail.com
- Laravel Framework for the robust foundation
- Tailwind CSS for the beautiful UI
- Laravel Breeze for authentication scaffolding
- Pest PHP for elegant testing syntax
⭐ If this project helped you, please consider giving it a star!
Made with ❤️ and ☕ by Luan Henrique Neumann