A comprehensive Laravel mail client package for managing email accounts, messages, and folders with support for IMAP, SMTP, Gmail, Outlook, and various email providers.
- Features
- Requirements
- Quick Start
- Configuration
- Basic Usage
- Advanced Features
- Models & Architecture
- Testing
- API Reference
- Troubleshooting
- Contributing
- Support
- π Multi-Provider Support - IMAP, SMTP, Gmail API, Outlook/Exchange
- π§ Complete Email Management - Send, receive, organize, and track emails
- π Folder Hierarchy - Nested folder structures with full CRUD operations
- π Email Templates - Predefined and reusable email templates
- β° Scheduled Emails - Queue and schedule emails for future delivery
- π Link Tracking - Track email link clicks and analytics
- π Sync Management - Intelligent email synchronization
- π ULID Support - Modern, sortable unique identifiers
- π§ͺ 100% Test Coverage - 173 tests, 566 assertions, rock-solid reliability
| Component | Version |
|---|---|
| PHP | 8.4+ |
| Laravel | 12.0+ |
| Database | MySQL 8.0+ / PostgreSQL 13+ / SQLite 3.35+ |
composer require turahe/mailclientphp artisan vendor:publish --provider="Turahe\MailClient\MailClientServiceProvider"
php artisan migrateuse Turahe\MailClient\Models\EmailAccount;
use Turahe\MailClient\Enums\ConnectionType;
$account = EmailAccount::create([
'email' => '[email protected]',
'password' => 'your_password',
'connection_type' => ConnectionType::IMAP,
'imap_server' => 'imap.example.com',
'imap_port' => 993,
'smtp_server' => 'smtp.example.com',
'smtp_port' => 587,
]);$client = $account->createClient();
$message = $client->compose()
->to('[email protected]')
->subject('Hello World!')
->html('<h1>Hello from Laravel Mail Client!</h1>')
->send();# Gmail OAuth
GMAIL_CLIENT_ID=your_gmail_client_id
GMAIL_CLIENT_SECRET=your_gmail_client_secret
GMAIL_REDIRECT_URL=your_callback_url
# Outlook OAuth
OUTLOOK_CLIENT_ID=your_outlook_client_id
OUTLOOK_CLIENT_SECRET=your_outlook_client_secret
OUTLOOK_REDIRECT_URL=your_callback_url// config/mail-client.php
return [
'default_connection_type' => 'imap',
'sync_batch_size' => 50,
'max_attachment_size' => 25 * 1024 * 1024, // 25MB
'allowed_attachment_types' => ['pdf', 'doc', 'docx', 'jpg', 'png'],
];π₯ Email Account Management
$account = EmailAccount::create([
'email' => '[email protected]',
'password' => 'secure_password',
'connection_type' => ConnectionType::IMAP,
'imap_server' => 'imap.example.com',
'imap_port' => 993,
'imap_encryption' => 'ssl',
'smtp_server' => 'smtp.example.com',
'smtp_port' => 587,
'smtp_encryption' => 'tls',
]);$gmailAccount = EmailAccount::create([
'email' => '[email protected]',
'connection_type' => ConnectionType::GMAIL,
'access_token' => $accessToken,
'refresh_token' => $refreshToken,
]);$outlookAccount = EmailAccount::create([
'email' => '[email protected]',
'connection_type' => ConnectionType::OUTLOOK,
'access_token' => $accessToken,
'refresh_token' => $refreshToken,
]);// Test connection
if ($account->testConnection()) {
echo "β
Connection successful!";
}
// Sync management
$account->enableSync();
$account->disableSync();
// Get statistics
$stats = $account->getStats();
echo "π§ Total: {$stats['total_messages']}";
echo "π΅ Unread: {$stats['unread_messages']}";π Folder Management
// Create main folder
$folder = EmailAccountFolder::create([
'email_account_id' => $account->id,
'name' => 'Important',
'display_name' => 'Important Messages',
'syncable' => true,
]);
// Create subfolder
$subFolder = EmailAccountFolder::create([
'email_account_id' => $account->id,
'parent_id' => $folder->id,
'name' => 'Urgent',
'display_name' => 'Urgent Items',
]);
// Browse hierarchy
$rootFolders = $account->folders()->whereNull('parent_id')->get();
foreach ($rootFolders as $folder) {
echo "π {$folder->name}\n";
foreach ($folder->children as $child) {
echo " ββ π {$child->name}\n";
}
}π¨ Sending Emails
$client = $account->createClient();
// Simple email
$message = $client->compose()
->to('[email protected]')
->subject('Meeting Tomorrow')
->text('Don\'t forget our meeting at 2 PM tomorrow.')
->send();
// Rich HTML email
$message = $client->compose()
->to('[email protected]')
->cc('[email protected]')
->bcc('[email protected]')
->subject('Project Update')
->html('
<h2>Project Status Update</h2>
<p>The project is <strong>on track</strong> for completion.</p>
<ul>
<li>β
Phase 1: Complete</li>
<li>π Phase 2: In Progress</li>
<li>β³ Phase 3: Planned</li>
</ul>
')
->attach('/path/to/report.pdf')
->send();$template = PredefinedMailTemplate::find(1);
$message = $client->compose()
->to('[email protected]')
->fromTemplate($template)
->variables([
'customer_name' => 'John Doe',
'order_number' => 'ORD-12345',
'delivery_date' => '2025-01-15'
])
->send();π¬ Managing Messages
// Get recent unread messages
$messages = $account->messages()
->unread()
->orderBy('date', 'desc')
->take(10)
->get();
foreach ($messages as $message) {
echo "π§ {$message->subject}\n";
echo "π€ From: {$message->from_address}\n";
echo "π
Date: {$message->date}\n\n";
}$message = EmailAccountMessage::find($messageId);
// Status operations
$message->markAsRead();
$message->markAsUnread();
// Organization
$message->moveToFolder($importantFolder);
$message->addToFolders([$folder1, $folder2]);
// Lifecycle management
$message->archive();
$message->trash();
$message->restore();
$message->purge(); // Permanent delete// Get message content
$htmlBody = $message->getHtmlBody();
$textBody = $message->getTextBody();
// Handle attachments
foreach ($message->attachments as $attachment) {
echo "π {$attachment->name} ({$attachment->size} bytes)\n";
// Download attachment
$content = $attachment->getContent();
file_put_contents("/downloads/{$attachment->name}", $content);
}π Email Templates
$template = PredefinedMailTemplate::create([
'name' => 'Order Confirmation',
'subject' => 'Order {{order_number}} Confirmed',
'html_body' => '
<h1>Order Confirmed! π</h1>
<p>Hello {{customer_name}},</p>
<p>Your order <strong>#{{order_number}}</strong> has been confirmed.</p>
<p>Expected delivery: {{delivery_date}}</p>
',
'text_body' => 'Hello {{customer_name}}, your order #{{order_number}} is confirmed. Delivery: {{delivery_date}}',
'is_shared' => true,
]);$processedTemplate = $template->process([
'customer_name' => 'Sarah Johnson',
'order_number' => 'ORD-789',
'delivery_date' => 'January 20, 2025'
]);
// Result:
// Subject: "Order ORD-789 Confirmed"
// Body: "Hello Sarah Johnson, your order #ORD-789 is confirmed..."β° Scheduled Emails
$scheduledEmail = ScheduledEmail::create([
'email_account_id' => $account->id,
'to' => '[email protected]',
'subject' => 'Weekly Newsletter',
'html_body' => '<h1>This Week in Tech</h1>...',
'scheduled_at' => Carbon::now()->addWeek(),
'data' => ['newsletter_id' => 456],
]);// In your scheduled job (e.g., daily)
$dueEmails = ScheduledEmail::dueForSend()->get();
foreach ($dueEmails as $email) {
try {
$email->send();
echo "β
Sent: {$email->subject}\n";
} catch (Exception $e) {
echo "β Failed: {$e->getMessage()}\n";
}
}π Link Tracking
// Links in emails are automatically tracked
$message = $client->compose()
->to('[email protected]')
->subject('Check out our new features!')
->html('
<p>Visit our <a href="https://example.com/features">new features page</a></p>
<p>Read the <a href="https://example.com/blog/update">latest blog post</a></p>
')
->send();$message = EmailAccountMessage::find($messageId);
// Get all clicks
$clicks = $message->linkClicks;
foreach ($clicks as $click) {
echo "π URL: {$click->url}\n";
echo "π
Clicked: {$click->clicked_at}\n";
echo "π IP: {$click->ip_address}\n";
echo "π» Browser: {$click->user_agent}\n\n";
}
// Statistics
$totalClicks = $message->linkClicks()->count();
$uniqueClicks = $message->linkClicks()->distinct('ip_address')->count();
echo "π Total clicks: {$totalClicks} | Unique: {$uniqueClicks}";π Bulk Operations
// Get messages to process
$messages = $account->messages()
->where('subject', 'like', '%newsletter%')
->get();
// Bulk operations
$account->moveMessagesToFolder($messages, $newsletterFolder);
$account->markMessagesAsRead($messages);
$account->deleteMessages($messages);use Turahe\MailClient\Services\EmailAccountMessageSyncService;
$syncService = app(EmailAccountMessageSyncService::class);
// Full account sync
$syncService->syncAccount($account);
// Incremental sync (faster)
$lastSync = $account->last_synced_at;
$syncService->syncAccountIncremental($account, $lastSync);
// Sync specific folder only
$syncService->syncFolder($folder);| Model | Purpose | Key Features |
|---|---|---|
| EmailAccount | Email account management | Multi-provider support, OAuth, sync settings |
| EmailAccountFolder | Folder organization | Hierarchical structure, sync control |
| EmailAccountMessage | Message storage | Rich content, attachments, metadata |
| EmailAccountMessageAddress | Email addresses | From, To, CC, BCC tracking |
| EmailAccountMessageHeader | Email headers | Technical metadata storage |
| PredefinedMailTemplate | Email templates | Variable substitution, sharing |
| ScheduledEmail | Email scheduling | Queue management, retry logic |
| MessageLinksClick | Link analytics | Click tracking, user behavior |
// EmailAccount (1:N)
$account->folders; // All folders
$account->messages; // All messages
$account->scheduledEmails; // Scheduled emails
// EmailAccountMessage (N:M)
$message->folders; // Associated folders
$message->addresses; // Email addresses
$message->headers; // Technical headers
$message->linkClicks; // Click analytics
// EmailAccountFolder (Tree)
$folder->parent; // Parent folder
$folder->children; // Child folders
$folder->messages; // Folder messages# All tests (173 tests, 566 assertions)
vendor/bin/phpunit
# Specific test suites
vendor/bin/phpunit --testsuite=Unit # Unit tests only
vendor/bin/phpunit --testsuite=Feature # Feature tests only
# With coverage report
vendor/bin/phpunit --coverage-html coverageuse Turahe\MailClient\Tests\Factories\EmailAccountFactory;
// Create test account
$account = EmailAccountFactory::new()->create([
'email' => '[email protected]'
]);
// Create account with related data
$account = EmailAccountFactory::new()
->withMessages(10) // 10 messages
->withFolders(5) // 5 folders
->create();
// Create specific message
$message = EmailAccountMessageFactory::new()
->forAccount($account)
->create([
'subject' => 'Test Message',
'from_address' => '[email protected]'
]);EmailAccount Methods
// Connection management
$account->testConnection(): bool
$account->createClient(): Client
// Sync control
$account->enableSync(): void
$account->disableSync(): void
$account->isSyncDisabled(): bool
// Statistics
$account->getStats(): array
$account->getUnreadCount(): int
// Token management (OAuth accounts)
$account->refreshAccessToken(): void
$account->isTokenExpired(): boolEmailAccountMessage Methods
// Status management
$message->markAsRead(): void
$message->markAsUnread(): void
$message->isRead(): bool
// Organization
$message->moveToFolder(EmailAccountFolder $folder): void
$message->addToFolders(array $folders): void
$message->removeFromFolder(EmailAccountFolder $folder): void
// Lifecycle
$message->archive(): void
$message->trash(): void
$message->restore(): void
$message->purge(): void
// Content access
$message->getHtmlBody(): string
$message->getTextBody(): string
$message->hasAttachments(): boolPredefinedMailTemplate Methods
// Template processing
$template->process(array $variables): array
$template->getProcessedSubject(array $variables): string
$template->getProcessedBody(array $variables): string
// Sharing
$template->makeShared(): void
$template->makePrivate(): void
$template->isShared(): boolConnection Issues
Problem: Connection timeout errors
// Solution: Increase timeout settings
'imap_timeout' => 60, // seconds
'smtp_timeout' => 30, // secondsProblem: SSL certificate errors
// Solution: Disable SSL verification (development only)
'imap_options' => [
'ssl' => [
'verify_peer' => false,
'verify_peer_name' => false,
]
]Memory Issues
Problem: Memory exhaustion with large attachments
// Solution 1: Increase memory limit
ini_set('memory_limit', '512M');
// Solution 2: Use streaming
$attachment->streamToFile('/path/to/destination');
// Solution 3: Process in chunks
$account->messages()->chunk(50, function ($messages) {
// Process 50 messages at a time
});OAuth Token Issues
Problem: Expired access tokens
// Solution: Automatic token refresh
if ($account->isTokenExpired()) {
$account->refreshAccessToken();
}
// Or handle in exception
try {
$client->getMessages();
} catch (UnauthorizedException $e) {
$account->refreshAccessToken();
$client->getMessages(); // Retry
}We welcome contributions! Here's how to get started:
# Clone and setup
git clone https://github.com/turahe/mail-client.git
cd mail-client
composer install
# Prepare testing
cp phpunit.xml.dist phpunit.xml
vendor/bin/phpunit
# Start coding!- Fork the repository
- Create feature branch (
git checkout -b feature/amazing-feature) - Write tests for your changes
- Ensure all tests pass (
vendor/bin/phpunit) - Commit your changes (
git commit -m 'Add amazing feature') - Push to branch (
git push origin feature/amazing-feature) - Open a Pull Request
- β PHP 8.4+ type declarations
- β PSR-12 coding standards
- β 100% test coverage for new features
- β PHPStan level 8 compliance
- β Clear documentation for public methods
- π§ Email: [email protected]
- π Issues: GitHub Issues
- π Wiki: Documentation
- π¬ Discussions: GitHub Discussions
This package is open-sourced software licensed under the MIT license.
See CHANGELOG.md for version history and updates.
Built with β€οΈ for the Laravel community