partridgerocks/gmail-client

A gmail api connector

v1.0.2 2025-05-16 04:43 UTC

README

Latest Version on Packagist GitHub Tests Action Status GitHub Code Style Action Status Total Downloads PHP Version Support Laravel Version Support

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

  • 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
  • Label Management:
    • List, create, update, and delete email labels
    • Organize emails with custom label hierarchies
  • Performance Optimizations:
    • 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
    • Command-line testing utilities

๐Ÿ“‹ 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:

  1. Go to the Google Developer Console
  2. Create a new project (or select an existing one)
  3. Navigate to "APIs & Services" > "Library"
  4. Search for and enable the "Gmail API"
  5. Go to "APIs & Services" > "Credentials"
  6. 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)
  7. 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"
  8. 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

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:

  1. Enable route registration in the config file:
// config/gmail-client.php
'register_routes' => true,
  1. 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}";

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();
    
    // ...
}

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']);
    }
}

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'),

๐Ÿ“ก Events

The package dispatches events that you can listen for:

  • GmailAccessTokenRefreshed: When a token is refreshed
  • GmailMessageSent: 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.