partridgerocks / gmail-client
A gmail api connector
Fund package maintenance!
PartridgeRocks
Requires
- php: ^8.2
- illuminate/contracts: ^10.0||^11.0||^12.0
- saloonphp/saloon: ^3.0
- spatie/laravel-data: ^4.15
- spatie/laravel-package-tools: ^1.16
Requires (Dev)
- larastan/larastan: ^2.9
- laravel/pint: ^1.14
- nunomaduro/collision: ^8.1.1||^7.10.0
- orchestra/testbench: ^10.0||^9.0.0||^8.22.0
- pestphp/pest: ^2.34
- pestphp/pest-plugin-arch: ^2.7
- pestphp/pest-plugin-laravel: ^2.3
- phpstan/extension-installer: ^1.3
- phpstan/phpstan-deprecation-rules: ^1.1
- phpstan/phpstan-phpunit: ^1.3
- dev-master
- v2.0.0
- v1.6.0
- v1.5.0
- v1.4.0
- v1.3.0
- v1.2.0
- v1.1.0
- v1.0.4
- v1.0.3
- v1.0.2
- v1.0.1-alpha
- dev-fix/skipped-tests-memory-optimization
- dev-feature/architectural-refactoring
- dev-feature/architecture-improvements
- dev-feature/centralize-configuration
- dev-feature/phpstan-level-6-upgrade
- dev-feature/implement-exception-tests
- dev-feature/statistics-service-layer
- dev-feature/standardize-exception-handling
- dev-feature/extract-magic-numbers
- dev-feature/service-interfaces
- dev-refactor/extract-services
- dev-feature/null-safe-label-methods
- dev-add-claude-github-actions-1748308480060
- dev-add-claude-github-actions-1748308480062
- dev-feature/contact-parsing-crm-integration
- dev-feature/performance-enhancements
- dev-feature/comprehensive-oauth-improvements
- dev-fix/header-method-invocation
- dev-fix/facade-binding-issue
- dev-docs/improve-documentation
This package is auto-updated.
Last update: 2025-05-29 06:30:09 UTC
README
A Laravel package that integrates with the Gmail API to seamlessly manage emails within your application. Built with Saloon PHP for API interactions and Laravel Data for structured data handling.
๐ Table of Contents
- Features
- Requirements
- Installation
- Google API Setup
- Usage
- Advanced Usage
- Configuration
- Events
- Testing
- Changelog
- Contributing
- Security
- Credits
- License
๐ Features
- OAuth Authentication: Seamless integration with Gmail's OAuth 2.0 flow
- Email Operations:
- Read emails and threads with full content and attachments
- Send emails with HTML content
- Support for CC, BCC, and custom sender addresses
- Automatic contact parsing for CRM integration
- Label Management:
- List, create, update, and delete email labels
- Organize emails with custom label hierarchies
- Performance Optimizations:
- Batch statistics retrieval for multi-account dashboards
- Smart count estimation to prevent timeouts on large mailboxes
- Connection health monitoring with quota tracking
- Lazy loading collections for memory-efficient processing
- Pagination support for large datasets
- Customizable batch sizes for API requests
- Developer Experience:
- Laravel facade for convenient access
- Strongly-typed data objects with Laravel Data
- Full Laravel service container integration
- Comprehensive exception handling
- Null-safe methods for robust applications (dashboards, background processing)
- Command-line testing utilities
- Enterprise Architecture:
- Service layer with interface contracts for enhanced testability
- Builder pattern for fluent client construction
- Repository pattern for data access abstraction
- Factory pattern for centralized service management
- Type-safe configuration objects
- Comprehensive test data builders and mock factories
๐ Requirements
- PHP 8.2 or higher
- Laravel 10.x, 11.x, or 12.x
- Google API credentials
๐ฆ Installation
You can install the package via Composer:
composer require partridgerocks/gmail-client
After installation, publish the configuration file:
php artisan vendor:publish --tag="gmail-client-config"
This will create a config/gmail-client.php
configuration file in your project.
๐ Google API Setup
Before you can use the Gmail Client, you need to set up a project in the Google Developer Console and obtain OAuth 2.0 credentials:
- Go to the Google Developer Console
- Create a new project (or select an existing one)
- Navigate to "APIs & Services" > "Library"
- Search for and enable the "Gmail API"
- Go to "APIs & Services" > "Credentials"
- Click "Configure Consent Screen" and set up your OAuth consent screen:
- Select "External" or "Internal" user type (depending on your needs)
- Fill in the required app information
- Add the required scopes (see below)
- Add test users if needed (for external user type)
- Create OAuth 2.0 credentials:
- Go to "Credentials" and click "Create Credentials" > "OAuth client ID"
- Select "Web application" as the application type
- Add a name for your client
- Add your authorized redirect URIs (this should match your
GMAIL_REDIRECT_URI
config value) - Click "Create"
- Copy the Client ID and Client Secret to your
.env
file:
GMAIL_CLIENT_ID=your-client-id
GMAIL_CLIENT_SECRET=your-client-secret
GMAIL_REDIRECT_URI=https://your-app.com/gmail/auth/callback
GMAIL_FROM_EMAIL=your-email@gmail.com
# Performance optimization settings
GMAIL_SMART_COUNTING=true
GMAIL_COUNT_THRESHOLD=50
GMAIL_CACHE_TTL=300
GMAIL_MAX_CONCURRENT=3
GMAIL_CIRCUIT_BREAKER=true
GMAIL_API_TIMEOUT=30
# Multi-account settings
GMAIL_MAX_ACCOUNTS=5
GMAIL_HEALTH_CHECK_INTERVAL=3600
GMAIL_BULK_OPERATIONS=true
Note: The Gmail API requires specific scopes to access different features. The default configuration includes commonly used scopes, but you can customize them in the config file.
๐ Usage
The package automatically registers a Laravel Facade that provides a convenient way to interact with the Gmail API:
use PartridgeRocks\GmailClient\Facades\GmailClient; // Get recent messages $messages = GmailClient::listMessages(); // Get a specific message $message = GmailClient::getMessage('message-id'); // Send an email $email = GmailClient::sendEmail( 'recipient@example.com', 'Subject line', '<p>Email body in HTML format</p>' );
Authentication
The Gmail Client provides two ways to authenticate with the Gmail API:
1. Manual Authentication Flow
If you want full control over the authentication process:
use PartridgeRocks\GmailClient\Facades\GmailClient; // 1. Get the authorization URL $authUrl = GmailClient::getAuthorizationUrl( config('gmail-client.redirect_uri'), config('gmail-client.scopes'), [ 'access_type' => 'offline', 'prompt' => 'consent' ] ); // 2. Redirect the user to the authorization URL return redirect($authUrl); // 3. In your callback route, exchange the code for tokens public function handleCallback(Request $request) { $code = $request->get('code'); // Exchange code for tokens $tokens = GmailClient::exchangeCode( $code, config('gmail-client.redirect_uri') ); // Store tokens securely for the authenticated user auth()->user()->update([ 'gmail_access_token' => $tokens['access_token'], 'gmail_refresh_token' => $tokens['refresh_token'] ?? null, 'gmail_token_expires_at' => now()->addSeconds($tokens['expires_in']), ]); return redirect()->route('dashboard'); }
2. Using Built-in Routes
For a simpler setup, you can enable the built-in routes:
- Enable route registration in the config file:
// config/gmail-client.php 'register_routes' => true,
- Use the provided routes in your app:
// Generate a link to the Gmail authentication page <a href="{{ route('gmail.auth.redirect') }}">Connect Gmail</a>
The package will handle the OAuth flow and store the tokens in the session by default.
Working with Emails
Once authenticated, you can use the client to interact with Gmail:
List Messages
use PartridgeRocks\GmailClient\Facades\GmailClient; // Authenticate with a stored token GmailClient::authenticate($accessToken); // List recent messages (returns a collection of Email data objects) $messages = GmailClient::listMessages(['maxResults' => 10]); foreach ($messages as $message) { echo "From: {$message->from}\n"; echo "Subject: {$message->subject}\n"; echo "Date: {$message->internalDate->format('Y-m-d H:i:s')}\n"; echo "Body: {$message->body}\n"; } // With query parameters (using Gmail search syntax) $messages = GmailClient::listMessages([ 'q' => 'from:example@gmail.com after:2023/01/01 has:attachment', 'maxResults' => 20 ]);
Get a Specific Message
// Get a specific message by ID $email = GmailClient::getMessage('message-id'); echo "Subject: {$email->subject}\n"; echo "From: {$email->from}\n"; echo "Snippet: {$email->snippet}\n"; echo "Body: {$email->body}\n"; // Access message headers foreach ($email->headers as $name => $value) { echo "{$name}: {$value}\n"; } // Check for specific labels if (in_array('INBOX', $email->labelIds)) { echo "This message is in the inbox\n"; }
Send an Email
// Send a simple email $email = GmailClient::sendEmail( 'recipient@example.com', 'Email subject', '<p>This is the email body in HTML format.</p>' ); // Send with additional options $email = GmailClient::sendEmail( 'recipient@example.com', 'Email with options', '<p>This email includes CC and BCC recipients.</p>', [ 'from' => 'your-email@gmail.com', 'cc' => 'cc@example.com', 'bcc' => 'bcc@example.com', ] ); // The sent email object is returned echo "Email sent with ID: {$email->id}";
Performance Features
Account Statistics (Batch Retrieval)
For multi-account dashboards, use the batch statistics method to minimize API calls:
// Get comprehensive account metrics in 1-2 API calls $stats = GmailClient::getAccountStatistics([ 'unread_limit' => 25, // Show exact count up to 25, then "25+" 'today_limit' => 15, // Today's messages limit 'include_labels' => true, // Include label count 'estimate_large_counts' => true, // Use smart estimation 'background_mode' => false, // Throw exceptions or return partial data ]); // Returns: // [ // 'unread_count' => 23, // or "25+" for large counts // 'today_count' => 8, // Today's messages // 'labels_count' => 42, // Total labels // 'estimated_total' => 15000, // Total mailbox size estimate // 'api_calls_made' => 2, // Actual API calls used // 'last_updated' => '2024-01-01T12:00:00Z', // 'partial_failure' => false, // True if some metrics failed // ] // Performance comparison: // Before: 3-5 API calls per account, 2-5s load time // After: 1-2 API calls per account, <1s load time
Connection Health Monitoring
Monitor connection status and API quota:
$health = GmailClient::getAccountHealth(); // Returns: // [ // 'connected' => true, // 'status' => 'healthy', // healthy, unhealthy, rate_limited, etc. // 'api_quota_remaining' => 250, // Remaining API calls (if available) // 'last_successful_call' => '2024-01-01T12:00:00Z', // 'errors' => [], // Array of error messages // ] // Use for dashboard health indicators if ($health['status'] === 'rate_limited') { // Handle rate limiting gracefully $retryAfter = $health['retry_after'] ?? 60; }
Contact Parsing & CRM Integration
The package automatically parses email addresses and names from Gmail messages, making it easy to integrate with CRM systems:
// Get an email message $email = GmailClient::getMessage('message-id'); // Access parsed contact information $sender = $email->fromContact; echo "Sender: {$sender->name} <{$sender->email}>\n"; echo "Domain: {$sender->domain}\n"; // Access recipient contacts foreach ($email->toContacts as $contact) { echo "To: {$contact->getDisplayName()} - {$contact->email}\n"; } // Get all contacts involved in the email $allContacts = $email->getAllContacts(); // Find contacts from specific domain (useful for CRM) $externalContacts = array_filter( $allContacts, fn($contact) => !$contact->isFromDomain('mycompany.com') ); // Get unique domains for company identification $domains = $email->getContactDomains(); // Returns: ['example.com', 'client.com', 'mycompany.com'] // Check if email involves specific company if ($email->hasContactFromDomain('important-client.com')) { // Handle VIP client email } // Get all contacts from a domain $clientContacts = $email->getContactsFromDomain('acme-corp.com');
Manual Contact Parsing
You can also parse email strings manually:
use PartridgeRocks\GmailClient\Data\Contact; // Parse single email address $contact = Contact::parse('"John Doe" <john@example.com>'); echo $contact->name; // "John Doe" echo $contact->email; // "john@example.com" echo $contact->domain; // "example.com" // Parse multiple addresses $contacts = Contact::parseMultiple('john@example.com, "Jane Doe" <jane@example.com>'); // Access contact properties foreach ($contacts as $contact) { echo "Name: " . ($contact->name ?? 'No name') . "\n"; echo "Email: {$contact->email}\n"; echo "Domain: {$contact->domain}\n"; echo "Local part: {$contact->getLocalPart()}\n"; echo "Display name: {$contact->getDisplayName()}\n"; echo "Formatted: {$contact->format()}\n"; }
CRM Integration Examples
// Find all emails from a specific company $companyEmails = collect($emails)->filter(function ($email) { return $email->hasContactFromDomain('target-company.com'); }); // Extract contact data for CRM import $crmData = []; foreach ($emails as $email) { foreach ($email->getAllContacts() as $contact) { if (!$contact->isFromDomain('mycompany.com')) { $crmData[] = [ 'name' => $contact->name, 'email' => $contact->email, 'company_domain' => $contact->domain, 'first_contact' => $email->internalDate, ]; } } } // Group contacts by company domain $contactsByCompany = collect($emails) ->flatMap(fn($email) => $email->getAllContacts()) ->groupBy('domain') ->map(fn($contacts) => $contacts->unique('email'));
Working with Labels
Gmail uses labels to organize emails. You can create, retrieve, and manage these labels:
// List all labels $labels = GmailClient::listLabels(); foreach ($labels as $label) { echo "Label: {$label->name} (ID: {$label->id})\n"; echo "Type: {$label->type}\n"; if ($label->messagesTotal !== null) { echo "Messages: {$label->messagesTotal} ({$label->messagesUnread} unread)\n"; } } // Get a specific label $label = GmailClient::getLabel('label-id'); // Create a new label $newLabel = GmailClient::createLabel('Important Clients', [ 'labelListVisibility' => 'labelShow', 'messageListVisibility' => 'show', 'color' => [ 'backgroundColor' => '#16a765', 'textColor' => '#ffffff' ] ]); // Update a label (using the LabelResource directly) $updatedLabel = GmailClient::labels()->update($label->id, [ 'name' => 'VIP Clients', 'color' => [ 'backgroundColor' => '#4986e7', 'textColor' => '#ffffff' ] ]); // Delete a label GmailClient::labels()->delete($label->id);
Using Without Facade
If you prefer not to use the facade, you can resolve the client from the container:
use PartridgeRocks\GmailClient\GmailClient; public function index(GmailClient $gmailClient) { $gmailClient->authenticate($accessToken); $messages = $gmailClient->listMessages(); // ... }
Builder Pattern & Service Integration
For advanced usage with dependency injection and configuration:
use PartridgeRocks\GmailClient\Builders\GmailClientBuilder; use PartridgeRocks\GmailClient\Contracts\MessageServiceInterface; // Using builder pattern $client = GmailClientBuilder::create() ->withToken($accessToken) ->withConfig($customConfig) ->build(); // Using service injection class EmailController extends Controller { public function __construct(private MessageServiceInterface $messageService) {} public function dashboard() { $recentMessages = $this->messageService->findRecent(10); return view('dashboard', compact('recentMessages')); } }
Integration with Your User Model
Here's an example of how you might integrate the Gmail client with your User model:
// In your User model public function connectGmail($accessToken, $refreshToken = null, $expiresAt = null) { $this->gmail_access_token = $accessToken; $this->gmail_refresh_token = $refreshToken; $this->gmail_token_expires_at = $expiresAt; $this->save(); } public function getGmailClient() { if (empty($this->gmail_access_token)) { throw new \Exception('Gmail not connected'); } return app(GmailClient::class)->authenticate( $this->gmail_access_token, $this->gmail_refresh_token, $this->gmail_token_expires_at ? new \DateTime($this->gmail_token_expires_at) : null ); } // In your controller public function listEmails() { $gmailClient = auth()->user()->getGmailClient(); return $gmailClient->listMessages(['maxResults' => 20]); }
๐ง Advanced Usage
Pagination Support
The package supports pagination for listing messages and labels:
// Get a paginator for messages $paginator = GmailClient::listMessages(['maxResults' => 25], true); // Get the first page $firstPage = $paginator->getNextPage(); // Check if there are more pages if ($paginator->hasMorePages()) { // Get the next page $secondPage = $paginator->getNextPage(); } // Or get all pages at once (use cautiously with large datasets) $allMessages = $paginator->getAllPages(); // You can also transform the results using the DTO use PartridgeRocks\GmailClient\Data\Responses\EmailDTO; $emails = $paginator->transformUsingDTO(EmailDTO::class);
Memory Efficiency
When working with large Gmail accounts, it's important to avoid loading all messages into memory at once:
// Lazy loading is the most memory-efficient approach for large datasets $messages = GmailClient::listMessages(lazy: true); // Process messages one by one without loading everything into memory foreach ($messages as $message) { processMessage($message); // You can stop iteration at any point if ($someCondition) { break; } } // For even more efficiency, you can get only message IDs without full details $messageIds = GmailClient::listMessages(lazy: true, fullDetails: false); foreach ($messageIds as $messageData) { echo "Message ID: {$messageData['id']}\n"; // Load full details only for specific messages if needed if (needsFullDetails($messageData)) { $fullMessage = GmailClient::getMessage($messageData['id']); } }
Null-Safe Methods for Robust Applications
For applications that need graceful degradation (dashboards, background processing), use the null-safe methods that never throw exceptions:
// โ Safe for dashboards - returns empty collection on any error $labels = GmailClient::safeListLabels(); $messages = GmailClient::safeListMessages(['q' => 'is:unread']); // โ Returns null instead of throwing NotFoundException $message = GmailClient::safeGetMessage('message-id'); // โ Connection health check - never throws exceptions if (GmailClient::isConnected()) { // Safe to proceed with Gmail operations } // โ Account overview with fallback data $summary = GmailClient::getAccountSummary(); // Returns: ['connected' => true, 'labels_count' => 15, 'has_unread' => true, 'errors' => []] // โ Statistics with graceful degradation $stats = GmailClient::safeGetAccountStatistics(); // Returns fallback data if API fails: ['unread_count' => '?', 'partial_failure' => true]
Perfect for:
- Dashboard widgets that should never crash
- Background sync processes
- Health monitoring systems
- Any UI requiring robust error handling
Enhanced Error Handling
The package provides detailed error handling for common Gmail API errors:
use PartridgeRocks\GmailClient\Exceptions\AuthenticationException; use PartridgeRocks\GmailClient\Exceptions\NotFoundException; use PartridgeRocks\GmailClient\Exceptions\RateLimitException; use PartridgeRocks\GmailClient\Exceptions\ValidationException; try { $message = GmailClient::getMessage('non-existent-id'); } catch (NotFoundException $e) { // Handle not found error echo "Message not found: " . $e->getMessage(); } catch (AuthenticationException $e) { // Handle authentication errors echo "Authentication error: " . $e->getMessage(); if ($e->getError()->code === 'token_expired') { // Refresh the token $tokens = GmailClient::refreshToken($refreshToken); } } catch (RateLimitException $e) { // Handle rate limit errors $retryAfter = $e->getRetryAfter(); echo "Rate limit exceeded. Retry after {$retryAfter} seconds."; } catch (ValidationException $e) { // Handle validation errors echo "Validation error: " . $e->getMessage(); }
Refresh a Token
// Refresh an expired token $tokens = GmailClient::refreshToken($refreshToken); // The client is automatically authenticated with the new token // Update the tokens in your storage $user->update([ 'gmail_access_token' => $tokens['access_token'], 'gmail_refresh_token' => $tokens['refresh_token'] ?? $user->gmail_refresh_token, 'gmail_token_expires_at' => now()->addSeconds($tokens['expires_in']), ]);
Command Line Testing
The package includes a command to test your Gmail API connection:
# Get authentication URL php artisan gmail-client:test --authenticate # List recent messages php artisan gmail-client:test --list-messages # List labels php artisan gmail-client:test --list-labels
Custom Email Templates
You can use your own branded email templates:
// config/gmail-client.php 'branded_template' => resource_path('views/emails/branded-template.blade.php'),
โ๏ธ Configuration
The package configuration file (config/gmail-client.php
) provides extensive customization options:
Performance Settings
'performance' => [ 'enable_smart_counting' => true, // Enable smart count estimation 'count_estimation_threshold' => 50, // Show exact count up to this limit 'default_cache_ttl' => 300, // Cache TTL in seconds (future use) 'max_concurrent_requests' => 3, // Max concurrent API requests (future use) 'enable_circuit_breaker' => true, // Enable circuit breaker pattern (future use) 'api_timeout' => 30, // API request timeout in seconds ],
Multi-Account Settings
'multi_account' => [ 'max_accounts_per_user' => 5, // Maximum Gmail accounts per user 'health_check_interval' => 3600, // Health check interval in seconds 'enable_bulk_operations' => true, // Enable bulk operations (future use) ],
Environment Variables
All configuration options can be overridden via environment variables:
Variable | Default | Description |
---|---|---|
GMAIL_SMART_COUNTING |
true |
Enable smart count estimation |
GMAIL_COUNT_THRESHOLD |
50 |
Threshold for showing exact vs estimated counts |
GMAIL_CACHE_TTL |
300 |
Cache time-to-live in seconds |
GMAIL_MAX_CONCURRENT |
3 |
Maximum concurrent requests |
GMAIL_CIRCUIT_BREAKER |
true |
Enable circuit breaker pattern |
GMAIL_API_TIMEOUT |
30 |
API request timeout in seconds |
GMAIL_MAX_ACCOUNTS |
5 |
Maximum accounts per user |
GMAIL_HEALTH_CHECK_INTERVAL |
3600 |
Health check interval in seconds |
GMAIL_BULK_OPERATIONS |
true |
Enable bulk operations |
๐ก Events
The package dispatches events that you can listen for:
GmailAccessTokenRefreshed
: When a token is refreshedGmailMessageSent
: When an email is sent
๐งช Testing
The package includes tests that you can run with PHPUnit:
composer test
For testing in your own application, you can use Saloon's built-in mocking capabilities:
use Saloon\Laravel\Facades\Saloon; use Saloon\Http\Faking\MockResponse; // In your test setup public function setUp(): void { parent::setUp(); // Mock all Gmail API responses Saloon::fake([ '*gmail.googleapis.com*' => MockResponse::make([ 'messages' => [ [ 'id' => 'test-id-123', 'threadId' => 'thread-123', 'snippet' => 'This is a test email', ] ] ], 200), ]); }
๐ Changelog
Please see CHANGELOG for more information on what has changed recently.
๐ค Contributing
Please see CONTRIBUTING for details.
๐ Security Vulnerabilities
Please review our security policy on how to report security vulnerabilities.
๐จโ๐ป Credits
๐ License
The MIT License (MIT). Please see License File for more information.