Developer’s Tutorial: How to Send Emails in Laravel using Gmail SMTP and API

On April 25, 2024
26min read
Dzenana Kajtaz Technical Content Writer @Mailtrap
Veljko Ristić Content Manager @ Mailtrap

If using Laravel to send email through Gmail is something you plan on doing within your current/future Laravel project, then you’ve come to the right place.

In this article, I show you how it’s done step by step.

To skip the setup and jump straight to sending with Gmail SMTP, [click here], and to skip to sending with Gmail API, [click here].

Setting up Laravel email service before sending emails

Note: The methods described are compatible with Laravel 8, Laravel 9, and Laravel 10. If you’re using earlier versions, further code modifications might be necessary.

Building your application

If you do not currently have a Laravel application, you can use the following steps to create one.

First, using the terminal, create a new Laravel project by running either laravel new app_email_in_laravel or the composer create-project laravel/app_email_in_laravel command (of course, within the commands, you should replace “app_email_in_laravel” with the name of your application).

Next, utilize the cd app_email_in_laravel command to switch to a directory named app_email_in_laravel (make sure this matches your actual directory name). 

This step is essential in executing the Laravel command that creates mailable classes (more details in the next section).

Lastly, to view your Laravel application in a web browser, execute the php artisan serve command.

Configuration

In the Laravel framework, you can configure email services in the config/mail.php file. This file contains a mailers configuration array with sample configuration entries for various mail drivers/transports supported by Laravel.

The default configuration value in this file determines the default mailer when sending an email from your Laravel application.

Thanks to the multiple mailers that can be configured in the config/mail.php file, you can utilize different email-sending services for different types of emails.

Generating and writing mailables

Various types of emails in Laravel are represented by a class called “mailable”, which is stored in the app/Mail directory. 

This directory is not present by default in your application, but it gets generated automatically when you create your first mailable class using this Artisan CLI command:

php artisan make:mail MailableName

After creating a mailable class, you can view its contents and configure the class using the following methods:

  • Envelope – Returns an instance of the Illuminate\Mail\Mailables\Envelope object, which allows you to define the subject and recipients of the email.
  • Content – Returns an instance of the Illuminate\Mail\Mailables\Content object, which lets you define the Blade template used to generate the content of the email message.
  • Attachments – Returns an array of attachments that can be added to the email.

Sender configuration

To specify the sender – the “from” email address and name – you have two options: you can use the Envelope object of the message or set a global “from” address.

  • Specifying the sender in the Envelope object:
use Illuminate\Mail\Mailables\Address;
use Illuminate\Mail\Mailables\Envelope;
 
/**
* Get the message envelope.
*
* @return \Illuminate\Mail\Mailables\Envelope
*/
public function envelope()
{
   return new Envelope(
       from: new Address('example@example.com', 'Test Sender'),
       subject: 'Test Email',
   );
}
  • Specifying the sender in config/mail.php using a global “from” address:
'from' => ['address' => 'example@example.com', 'name' => 'App Name']

Note: If you want to use the same “from” address for all the emails sent by your application, it is recommended to use the global “from” address method. This approach is convenient as it eliminates the need to call the from method in each of your mailable classes, and it serves as the default “from” address if no other address is specified.

How to send emails using Laravel and Gmail SMTP?

The initial step in sending emails using Gmail SMTP is adding your Gmail SMTP configuration to the .env file of your application.

MAIL_MAILER=smtp
MAIL_HOST=smtp.gmail.com
MAIL_PORT=465
MAIL_USERNAME=mygoogle@gmail.com 
MAIL_PASSWORD=*************
MAIL_ENCRYPTION=tls
MAIL_FROM_ADDRESS=mygoogle@gmail.com
MAIL_FROM_NAME="${APP_NAME}"

After the SMTP setup is completed, it’s time to generate one of those mailable classes we mentioned earlier using the following command.

php artisan make:mail MyTestEmail

Upon executing the command in the Artisan CLI, a new class named “MyTestEmail” will be generated in the app/Mail directory.

If you wish, you can inspect the class code below:

<?php

namespace App\Mail;

use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Mail\Mailable;
use Illuminate\Mail\Mailables\Content;
use Illuminate\Mail\Mailables\Envelope;
use Illuminate\Queue\SerializesModels;

class MyTestEmail extends Mailable
{
    use Queueable, SerializesModels;

    /**
     * Create a new message instance.
     *
     * @return void
     */
    public function __construct()
    {
        //
    }

    /**
     * Get the message envelope.
     *
     * @return \Illuminate\Mail\Mailables\Envelope
     */

    public function envelope()
    {
        return new Envelope(
            subject: 'My Test Email',
        );
    }
 /**
     * Get the message content definition.
     *
     * @return \Illuminate\Mail\Mailables\Content
     */

    public function content()
    {
        return new Content(
            view: 'view.name',
        );
    }

    /**
     * Get the attachments for the message.
     *
     * @return array
     */
    public function attachments()
    {
        return [];
    }
}

As evident from the code above, the content() method will return a view. Therefore, you need to navigate to the resources/views directory, create a new folder, and within it, a blade.php file.

When the file has been generated, you can add content to it.

// resources/views/mail/test-email.blade.php
Hey, 
Can your Laravel app send emails yet? 😉 
Funny Coder

Then, return to the content() method and update the name of the view being returned by it with the name of the newly-created file.

If you wish to add dynamic content, you can use the with attribute to include the recipient’s name in your email template/blade.php file.

<?php

namespace App\Mail;

use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Mail\Mailable;
use Illuminate\Mail\Mailables\Content;
use Illuminate\Mail\Mailables\Envelope;
use Illuminate\Queue\SerializesModels;

class MyTestEmail extends Mailable
{
    use Queueable, SerializesModels;

    /**
     * Create a new message instance.
     *
     * @return void
     */
    public function __construct(private $name)
    {
        //
    }

    /**
     * Get the message envelope.
     *
     * @return \Illuminate\Mail\Mailables\Envelope
     */
    public function envelope()
    {
        return new Envelope(
            subject: 'My Test Email',
        );
    }

    /**
     * Get the message content definition.
     *
     * @return \Illuminate\Mail\Mailables\Content
     */
    public function content()
    {
        return new Content(
            view: 'mail.test-email',
            with: ['name' => $this->name],
        );
    }
}

You will also need to make a minor modification in the test-email.blade.php view file as well and make it accept the $name variable as a parameter.

// resources/views/mail/test-email.blade.php

Hey {{$name}}, 
Can your Laravel app send emails yet? 😉 
Mailtrap

As the final step, add a route in the routes/web.php file using the following code:

<?php

use Illuminate\Support\Facades\Route;
use App\Mail\MyTestEmail;
use Illuminate\Support\Facades\Mail;

Route::get('/testroute', function() {
    $name = "Funny Coder";

    // The email sending is done using the to method on the Mail facade
    Mail::to('testreceiver@gmail.com')->send(new MyTestEmail($name));
});

To test the functionality, you can run the php artisan serve command and then access your web browser. In the browser, paste the URL of the route you created.

Once you have done this, if everything is working properly, an email should be sent to the “to” address that you specified.

For learning how to send an email in HTML format, an email with an attachment, or an email with multiple recipients, check out our How to Send Emails in Laravel: A Complete SMTP & API Guide.

Send email to multiple recipients

To send emails to multiple recipients, via SMTP mail server, you can pass an array of email addresses to the to() method. This is a quick and simple method, but note that the recipients will see each other.

Route::get('/sendToMultiple', function() {
    $emails = ['first@example.com', 'second@example.com', 'third@example.com'];
    $name = "Funny Coder"; // Assuming you want to send the same content to all

    Mail::to($emails)->send(new MyTestEmail($name));
});

In the snippet above, $emails is an array of email addresses to which the email will be sent. The MyTestEmail is the mailable class that constructs the email, and as mentioned, all the recipients are listed under to() method. 

If you want to manage different visibility levels, you can use cc() and bcc(). Here’s the snippet.

Route::get('/sendWithCCandBCC', function() {
    $mainRecipients = ['main1@example.com', 'main2@example.com'];
    $ccRecipients = ['cc1@example.com', 'cc2@example.com'];
    $bccRecipients = ['secret1@example.com', 'secret2@example.com'];
    $name = "Funny Coder"; // Dynamic content

    Mail::to($mainRecipients)
        ->cc($ccRecipients)
        ->bcc($bccRecipients)
        ->send(new MyTestEmail($name));
});

The $mainRecipients are the primary recipients who will see each other. The $ccRecipients can also see each other and know about the main recipients. Finally, the $bccRecipients recipients are hidden from all other recipients.

Send email with attachments

Adding attachments is relatively simple, and it starts by modifying the mailable class. I’ll be covering that here, but note that there are other methods as well. 

  1. First, modify the MyTestEmail class to handle attachment data.
namespace App\Mail;

use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Mail\Mailable;
use Illuminate\Mail\Mailables\Content;
use Illuminate\Mail\Mailables\Envelope;
use Illuminate\Queue\SerializesModels;
Use Illuminate\Mail\Mailables\Attachment;

class MyTestEmail extends Mailable
{
    use Queueable, SerializesModels;

    public $name;
    public $attachment;

    public function __construct($name, $attachment = null)
    {
        $this->name = $name;
        $this->attachment = $attachment;
    }

    public function envelope()
    {
        return new Envelope(
            subject: 'My Test Email',
        );
    }

    public function content()
    {
        return new Content(
            view: 'mail.test-email',
            with: [
                'name' => $this->name
            ],
        );
    }

    public function attachments()
    {
        if ($this->attachment) {
            return [$this->attachment];
            return [
                Attachment::fromPath($this->attachment),
            ];
        }

        return [];
    }
}
  1. Attach the files when you instantiate the mailable class to send the email.
use Illuminate\Support\Facades\Route;
use App\Mail\MyTestEmail;
use Illuminate\Support\Facades\Mail;

Route::get('/sendWithAttachment', function() {
    $name = "Funny Coder";
    $filePath = 'path/to/your/file.pdf';  // Ensure the path is correct

    // The email sending is done using the to method on the Mail facade
    Mail::to('testreceiver@gmail.com')->send(new MyTestEmail($name, $filePath));
});

The $filePath variable should be set to the path of the file you want to attach to the email. Make sure the file exists and is accessible by your Laravel application. Then, the attachments method in the Mailable class checks if an attachment is present and includes it in the email.

There are a couple of things to be aware of when sending attachments:

  • File permissions: Ensure that your Laravel application has the necessary permissions to read the file you wish to attach.
  • File size: Be aware of the file size limits for attachments when using Gmail as your mail provider. Gmail has a limit of 25 MB per email; if your attachments exceed this size, you may need to send them using a file sharing service or compress them.

Send HTML email

I’ll be using the Mailable class to send an HTML email. Check the exemplary flow below. 

  1. I’ll start with an HTML Blade template that will serve as the email body. You should create or modify a Blade file in the ‘resources/views/mail’ directory. For example, I’ll call it ‘html-email.blade.php’.
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Email</title>
    <style>
        body {
            font-family: 'Arial', sans-serif;
            margin: 0;
            padding: 0;
            color: #333;
        }
        .content {
            padding: 20px;
            background-color: #f4f4f4;
        }
    </style>
</head>
<body>
    <div class="content">
        <h1>Hello, {{$name}}!</h1>
        <p>This is a test HTML email sent from a Laravel application.</p>
        <p style="color: blue;">Enjoy coding!</p>
    </div>
</body>
</html>
  1. I’ll adjust the MyTestEmail class to be able to use the HTML template. The critical thing here is to ensure the content() method points to the correct view.
public function content()
{
    return new Content(
        view: 'mail.html-email',
        with: [
            'name' => $this->name
        ],
    );
}
  1. When you send the email using the route you’ve set up, it will render the ‘html-email.blade.php’ as an HTML email. Here’s how the sending function might look:
Route::get('/sendHtmlEmail', function() {
    $name = "Funny Coder";  // Assuming this is the dynamic content

    Mail::to('testreceiver@gmail.com')->send(new MyTestEmail($name));
});

You can write standard HTML and inline CSS directly in your Blade view file. Avoid external stylesheets because not all email clients support them. Also, make sure to pass any dynamic data required by using the with attribute in the content() method of your Mailable class.

Pro Tip

You can write CSS in the <style> tag in the header, but, for better email client compatibility and immediate rendering, consider inlining your CSS styles. This way, your email won’t be stripped off the styles if the client removes the <head> and <body> tags from incoming HTML. 

There are tools and Laravel packages available that can automate this process. For instance, the popular option is Laravel’s ‘fedeisas/laravel-mail-css-inliner’ package.

Queued email sending

Queueing email sending in Laravel is an efficient way to handle email delivery without delaying the response time of your web application. This is particularly useful for applications that need to send a high volume of emails or when the timing of the email delivery is not critical to the immediate user action.

Here’s how to set it up. 

  1. Ensure your Laravel environment is configured to use a queue driver. Laravel supports several drivers such as Redis, Database, SQS, etc. You can set this in your ‘.env’ file, and I’ll be using Redis for this example.
QUEUE_CONNECTION=redis
  1. Install ‘predis’ composer module in your laravel project by running composer require predis/predis
    • Note, if you do all of that above and still see the error, try adding this to your Laravel .env configuration: REDIS_CLIENT=predis
  2. Assuming you already have a mailable class like ‘MyTestEmail’ from my earlier examples, it has to implement the ‘ShouldQueue’ interface to allow for queuing. Here’s how I modified the ‘MyTestEmail’ class:
namespace App\Mail;

use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Mail\Mailable;
use Illuminate\Mail\Mailables\Content;
use Illuminate\Mail\Mailables\Envelope;
use Illuminate\Queue\SerializesModels;

class MyTestEmail extends Mailable implements ShouldQueue
{
    use Queueable, SerializesModels;

    public $name;

    public function __construct($name)
    {
        $this->name = $name;
    }

    public function envelope()
    {
        return new Envelope(subject: 'My Test Email');
    }

    public function content()
    {
        return new Content(view: 'mail.test-email', with: ['name' => $this->name]);
    }
}
  1. To send the email to the queue, you need to modify the controller or the route method. 
use Illuminate\Support\Facades\Route;
use App\Mail\MyTestEmail;
use Illuminate\Support\Facades\Mail;

Route::get('/queueEmail', function() {
    $name = "Funny Coder";
    Mail::to('testreceiver@gmail.com')->queue(new MyTestEmail($name));
    print(“Email Sent Successfully”);
});

In queued sending, the ‘ShouldQueue’ interface tells Laravel that this mailable should be sent using the queue system. Then, the Mail facade uses the queue () method instead of the ‘send ()’ method. 

Pro Tips:

  • It’s important to have a strategy for handling failed jobs in the queue. For instance, Laravel allows you to set a max number of tries and timeouts for jobs.
  • When developing and testing queued emails, you can use the ‘sync’ driver in your ‘.env’ file to handle jobs immediately without setting up a queue service. Set QUEUE_CONNECTION=sync for testing purposes.

Gmail SMTP limitations and possible issues

Gmail, although widely used, does come with limitations and issues that are not talked about so often.

So, to help you keep your Laravel project on the right track and avoid any potential pitfalls, we’ll now go over these limitations and issues one by one.

  • Sending limit – With Gmail SMTP, users have a daily email sending limit, which depends on the Gmail account they use. Exceeding this limit will cause errors or a temporary suspension.
  • Rate limit – Another limit Gmail SMTP users have to deal with is the limit on how many emails they can send per minute or per hour. Exceeding this limit will block or delay your email sending.
  • Security – Sending emails through Gmail SMTP will require a secure connection such as SSL or TLS, which you need to configure correctly in the Gmail SMTP settings. Failing to do so will leave your emails and their data vulnerable to interception and tampering.

Google’s security settings also require proper configuration of things such as two-factor authentication to avoid the blocking of email sending.

  • Custom domain – For sending emails from a custom domain such as johndoe@yourcompany.com with Gmail SMTP, you need to configure the correct DNS settings. Not doing so will cause your emails to fail SPF, DKIM, and DMARC authentication checks and thus be marked as spam or simply rejected.

Send email in Laravel using Gmail API

To send via API from your Google account, you need to set up OAuth 2.0 credentials in Google Cloud Console. The process takes a few steps and I’ll be starting with that. 

Important note: Google is discontinuing the ‘less secure app’ options so you need OAuth. 

1. Create a project in Google Cloud Console

  • Go to Google Cloud Console.
  • Click the project dropdown near the top of the dashboard and then click “New Project”. Provide a project name and location, then click “Create”.
Creating a new project in Google Cloud Console.

2. Enable Gmail API 

  • Click on the navigation menu (three horizontal lines) in the upper left corner.
  • Go to the API library – “APIs & Services” > “Library”.
  • Type “Gmail” into the search box and select the Gmail API.
  • Click the “Enable” button to enable Gmail API for your project.
Google Cloud Console APIs & Services -> Library

3. Create OAuth 2.0 credentials

This action has a bunch of substeps, so I structured them separately to make it easier for you to follow. 

OAuth consent screen configuration

  • In the APIs & Services dashboard, navigate to “OAuth consent screen”.
  • Select “External” for user type if you want to allow users outside of your organization to use your application. Then click “Create”.
Google Cloud Console OAuth consent screen
  • Fill in the app details like app name, user support email, and developer contact information. Save and continue.
  • Add the scopes you require. For sending emails, add the scope ‘https://www.googleapis.com/auth/gmail.send’.
  • Save and continue, and then submit for verification if needed (for production use).

Create credentials

  • Go to “Credentials” on the APIs & Services dashboard.
Google Cloud Console Credentials
  • Click “Create Credentials” > “OAuth 2.0 Client IDs”.
  • Select the application type (usually “Web application”).
  • Name your OAuth 2.0 client.
  • Set the authorized redirect URIs. This URI is where Google will send responses for your OAuth requests. For local development, you might use something like ‘http://localhost:8000/callback’.

Download credentials 

  • Once the credentials are created, you can download the JSON file by clicking the “Download” button (an icon that looks like a download arrow).
  • Save this file securely; you will need the client ID and client secret for your Laravel application.

4. Integrate OAuth credentials into Laravel

Store the client ID and client secret in your Laravel ‘.env’ file:

GOOGLE_CLIENT_ID=your-google-client-id
GOOGLE_CLIENT_SECRET=your-google-client-secret
GOOGLE_REDIRECT_URI=http://localhost:8000/callback
  • Run ‘composer require google/apiclient’ in your Laravel project to install the Google Client Library.
  • Create routes and a controller for handling OAuth callbacks and sending emails. Your controller will use the Google Client to authenticate users and store access tokens.

This setup will allow your Laravel application to authenticate with Google and send emails through the Gmail API. In the next sections, I’ll cover how to use these credentials to send emails to multiple recipients, with attachments, as HTML emails, and through queued jobs.

Send email to multiple recipients

Sending to multiple recipients via Gmail API, requires you to construct a MIME message that specifies all the recipients in the To, Cc, or Bcc headers. But that’s not all, since you’ll also need to set up a controller. Anyway, I’m listing all the steps below. 

  1. The aforementioned controller handles the sending logic. I’ll create a method in the controller to send an email to multiple recipients. But a note before that:
    • Ensure you have the Google Client set up in the controller. It involves loading the client with credentials and setting the access token, which should be retrieved from your storage (like a database or session, depending on your application’s authentication flow).
    • If it is the first time for you to get access token for Gmail API, you need to be redirected to authenticate yourself for Google API on the browser. In this case, in the Laravel project, you need to add the handler for callback url from Google authenticator. And in the handler, we can get access code by parsing the url.
use Google\Client;
use Google\Service\Gmail;
use Google\Service\Gmail\Message;

class EmailController extends Controller
{
    private $client;

    public function __construct()
    {
        $this->client = new Client();
        $this->client->setClientId(env('GOOGLE_CLIENT_ID'));
        $this->client->setClientSecret(env('GOOGLE_CLIENT_SECRET'));
        $this->client->setRedirectUri(env('GOOGLE_REDIRECT_URI'));
        $this->client->addScope(Gmail::GMAIL_SEND);
  1. I’ll use a standard PHP library to create a MIME message. It’s convenient as it includes all the necessary headers and the email body.

public function handleCallback(Request $request)
    {
        if (!$request->has('code')) {
            return redirect('/')->with('error', 'Authorization code not available');
        }

        $token = $this->client->fetchAccessTokenWithAuthCode($request->query('code'));
        $this->client->setAccessToken($token);
        // Store the token in the user's session or database for making authenticated calls later

        $this->sendEmailToMultipleRecipients();

public function redirectToAuthUrl()
{
    $authUrl = $this->client->createAuthUrl();
        return redirect($authUrl);
}

public function sendEmailToMultipleRecipients()
{
    $to = ['recipient1@example.com', 'recipient2@example.com'];
    $subject = 'Hello from Gmail API';
    $messageText = 'This is a test email sent to multiple recipients using the Gmail API from a Laravel application.';

    $message = new Message();
    
    $rawMessageString = "To: " . implode(', ', $to) . "\r\n";
    $rawMessageString .= "Subject: {$subject}\r\n";
    $rawMessageString .= "MIME-Version: 1.0\r\n";
    $rawMessageString .= "Content-Type: text/html; charset=utf-8\r\n";
    $rawMessageString .= "Content-Transfer-Encoding: quoted-printable\r\n\r\n";
    $rawMessageString .= "<p>{$messageText}</p>";

    // URL-safe base64 encode the message
    $rawMessage = base64_encode($rawMessageString);
    $rawMessage = str_replace(['+', '/', '='], ['-', '_', ''], $rawMessage); // URL-safe

    $message->setRaw($rawMessage);

    $service = new Gmail($this->client);
    try {
        $service->users_messages->send('me', $message);
        return 'Email sent successfully to multiple recipients.';
    } catch (\Exception $e) {
        return 'An error occurred: ' . $e->getMessage();
    }
}
  1. Finally, define a route in the ‘web.php’ or ‘api.php’ that triggers the email sending method:
Route::get('/send-email', [EmailController::class, redirectToAuthUrl]);
Route::get('/callback', [EmailController::class, 'handleCallback']);

Good to know:

  • The email headers and body are constructed as a plain text string, which includes To, Subject, and Content-Type. The recipients are concatenated into a single string separated by commas for the To header.
  • The message must be base64 encoded and made URL-safe according to Gmail API specifications.
  • Using the Gmail service, the constructed message is sent by the authenticated client.

Send email with attachments

Sending with attachments requires you to construct a multipart MIME message that includes both the base message and the attachments.

Check out the code snippet below:

use Google\Client;
use Google\Service\Gmail;
use Google\Service\Gmail\Message;

class EmailController extends Controller
{
    // Existing setup code...

    public function sendEmailWithAttachments()
    {
        $subject = 'Subject with Attachments';
        $to = 'recipient@example.com';
        $messageText = 'This is a test email with attachments sent through the Gmail API from a Laravel application.';

        // Construct the MIME message with attachment
        $boundary = uniqid(rand(), true);
        $subjectCharset = $charset = 'utf-8';

        $messageBody = "--{$boundary}\r\n";
        $messageBody .= "Content-Type: text/plain; charset={$charset}\r\n";
        $messageBody .= "Content-Transfer-Encoding: 7bit\r\n\r\n";
        $messageBody .= "{$messageText}\r\n";

        // Attachments
        $filePath = '/path/to/your/file.pdf'; // Example file path
        $fileName = 'example.pdf'; // Example file name
        $fileData = file_get_contents($filePath);
        $base64File = base64_encode($fileData);

        $messageBody .= "--{$boundary}\r\n";
        $messageBody .= "Content-Type: application/pdf; name={$fileName}\r\n";
        $messageBody .= "Content-Description: {$fileName}\r\n";
        $messageBody .= "Content-Disposition: attachment; filename={$fileName}; size=".filesize($filePath)."\r\n";
        $messageBody .= "Content-Transfer-Encoding: base64\r\n\r\n";
        $messageBody .= "{$base64File}\r\n";
        $messageBody .= "--{$boundary}--";

        $rawMessage = "To: {$to}\r\n";
        $rawMessage .= "Subject: =?{$subjectCharset}?B?" . base64_encode($subject) . "?=\r\n";
        $rawMessage .= "MIME-Version: 1.0\r\n";
        $rawMessage .= "Content-Type: multipart/mixed; boundary=\"{$boundary}\"\r\n\r\n";
        $rawMessage .= $messageBody;

        $rawMessage = base64_encode($rawMessage);
        $rawMessage = str_replace(['+', '/', '='], ['-', '_', ''], $rawMessage); // URL-safe

        $gmailMessage = new Message();
        $gmailMessage->setRaw($rawMessage);

        $service = new Gmail($this->client);
        try {
            $service->users_messages->send('me', $gmailMessage);
            return 'Email with attachments sent successfully.';
        } catch (\Exception $e) {
            return 'An error occurred: ' . $e->getMessage();
        }
    }
}

Here, the message is constructed with multiple parts separated by boundaries. Each part has appropriate headers for content type and encoding. Also, each attachment is read into memory, base64 encoded, and then added to the MIME message. 

Headers for each attachment include details such as Content-Disposition and Content-Type to properly identify the file in the email. And lastly, the entire MIME message is encoded in base64 and modified to be URL-safe before being sent to Gmail.

Pro Tips:

  • Make sure proper permissions are given to read files (be it via your app password or Gmail password or tokens). Also, consider security implications when handling file uploads from users.
  • Uploading large files can be resource-intensive. Limiting the size of attachments and possibly offload the upload process to a background job if necessary.

Send HTML email

Now, we’re getting into the realm of rich formatting and styling, and here’s how to set it all up. 

  1. I’ll continue using the EmailController and add a method specifically for sending HTML content.
use Google\Client;
use Google\Service\Gmail;
use Google\Service\Gmail\Message;

class EmailController extends Controller
{
    // Existing setup code...

    public function sendHtmlEmail()
    {
        $to = 'recipient@example.com';
        $subject = 'Hello, HTML Email!';
        $htmlContent = '<h1>Welcome to Our Service</h1><p>This is a <strong>HTML</strong> email, sent via the <em>Gmail API</em>.</p>';

        // MIME Type message
        $boundary = uniqid(rand(), true);
        $subjectCharset = $charset = 'utf-8';

        $messageBody = "--{$boundary}\r\n";
        $messageBody .= "Content-Type: text/html; charset={$charset}\r\n";
        $messageBody .= "Content-Transfer-Encoding: quoted-printable\r\n\r\n";
        $messageBody .= "{$htmlContent}\r\n";
        $messageBody .= "--{$boundary}--";

        $rawMessage = "To: {$to}\r\n";
        $rawMessage .= "Subject: =?{$subjectCharset}?B?" . base64_encode($subject) . "?=\r\n";
        $rawMessage .= "MIME-Version: 1.0\r\n";
        $rawMessage .= "Content-Type: multipart/alternative; boundary=\"{$boundary}\"\r\n\r\n";
        $rawMessage .= $messageBody;

        $rawMessage = base64_encode($rawMessage);
        $rawMessage = str_replace(['+', '/', '='], ['-', '_', ''], $rawMessage); // URL-safe

        $gmailMessage = new Message();
        $gmailMessage->setRaw($rawMessage);

        $service = new Gmail($this->client);
        try {
            $service->users_messages->send('me', $gmailMessage);
            return 'HTML email sent successfully.';
        } catch (\Exception $e) {
            return 'An error occurred: ' . $e->getMessage();
        }
    }
}
  1. Add a new route to trigger the HTML sending. 
Route::get('/send-html-email', [EmailController::class, 'sendHtmlEmail']);

The HTML content is included as a part of a MIME multipart message. It’s important to set the Content-Type to text/html and specify the correct character set (‘utf-8’ in this case).

The message includes HTML content and uses a boundary to separate different parts of the email. This is crucial when sending multipart messages, which might include plain text alternatives or attachments in addition to the HTML content.

Like with any content sent via the Gmail API, the message needs to be base64 encoded and made URL-safe to ensure it is transmitted correctly.

Pro Tips:

  • This is more of a reminder 😀You can include CSS within HTML emails, but it’s recommended to use inline styles for better compatibility with various email clients.
  • Always test how your HTML emails render in different email clients (like Gmail, Outlook, Yahoo, etc.) to ensure they look as intended across platforms. I’ll show you how to do that under the email testing section
  • Be cautious when including dynamic content or user-generated HTML to avoid injection attacks. Sanitize any user input included in HTML emails.

Queued email sending

By queuing email tasks, your application can handle heavy loads and process emails in the background, improving response times for user interactions.

API queuing isn’t for the faint of heart, but I’ll lead you through it. 

1. Setting up Laravel configuration

The Laravel project has to be configured to use queues. Like with the SMTP method, the supported drivers include database, Redis, SQS, etc. 

To make things simple and focus on the workflow, I’ll use the database queue driver. 

So, first, I need to edit the ‘.env’ file to use the database as the queue connection. 

QUEUE_CONNECTION=database

Also, if you haven’t already set up the jobs table for the database queue, run these Artisan commands. 

php artisan queue:table
php artisan migrate

2. Create email-sending jobs

Start by running the Artisan command to create a new job class.

php artisan make:job SendGmailApiEmail

Then, edit the generated job class to send an email using the Gmail API.

namespace App\Jobs;

use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Google\Client;
use Google\Service\Gmail;
use Google\Service\Gmail\Message;

class SendGmailApiEmail implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    protected $to;
    protected $subject;
    protected $messageText;

    public function __construct($to, $subject, $messageText)
    {
        $this->to = $to;
        $this->subject = $subject;
        $this->messageText = $messageText;
    }

    public function handle()
    {
        $client = new Client();
        $client->setClientId(env('GOOGLE_CLIENT_ID'));
        $client->setClientSecret(env('GOOGLE_CLIENT_SECRET'));
        $client->setRedirectUri(env('GOOGLE_REDIRECT_URI'));
        $client->setAccessToken($this->getAccessToken()); // Ensure this method retrieves a valid token

        $gmail = new Gmail($client);
        $gmailMessage = new Message();

        $rawMessageString = "To: {$this->to}\r\n";
        $rawMessageString .= "Subject: {$this->subject}\r\n";
        $rawMessageString .= "Content-Type: text/plain; charset=utf-8\r\n\r\n";
        $rawMessageString .= $this->messageText;

        // Base64 encode and make it URL-safe
        $rawMessage = base64_encode($rawMessageString);
        $rawMessage = str_replace(['+', '/', '='], ['-', '_', ''], $rawMessage);
        $gmailMessage->setRaw($rawMessage);

        $result = $gmail->users_messages->send('me', $gmailMessage);
        return $result;
    }
}

3. Dispatch the job from your application

SendGmailApiEmail::dispatch($to, $subject, $messageText);

I’d advise you to take full advantage of the job system. For instance, Laravel’s queue system allows you to defer the execution of a task, such as sendmail, until a later time, reducing the load on your web server and improving the user experience.

And you shouldn’t forget about the error handling. Set up automatic retries of failed jobs at a specified number of times. 

Pro Tips:

  • Make sure to run a queue worker that processes the jobs. You can use the ‘php artisan queue:work’ command to start a worker.
  • Consider setting up a monitoring system to track the status of queued jobs to ensure they are processed successfully.

Gmail API limitations and possible issues

The limitations are quite similar between the Gmail API and traditional SMTP services in terms of rate limiting and message size restrictions. 

However, the Gmail API provides a more granular control and detailed feedback mechanism through its quota units system, which can be both an advantage and a complexity depending on the application’s needs.

I’ll be covering the key limitations and offer a light comparison to the SMTP limitations discussed earlier. 

Disclaimer: The limits covered below were valid at the time of writing, but could be subject to change based on Google’s policies. 

  • Rate limiting

Gmail API imposes a per-user rate limit of 250 quota units per user per second on average, allowing short bursts. This limit is calculated based on the number of quota units consumed by each API method.

SMTP services often have rate limits based on the number of emails sent per day or per hour. But usually there’s no complex quota system based on types of operations like the Gmail API.

  • Quota units per operation

Different operations consume different amounts of quota units. For instance, sending an email (‘messages.send’) costs 100 units, whereas retrieving an email (‘messages.get’) costs only 5 units. 

This detailed accounting of operations can impact how you manage your application’s email interactions, especially in bulk (marketing) operations. And there’s no comparable equivalent to SMTP due to the fundamental differences between the two methods. 

  • Daily usage limit 

The Gmail API is subject to a daily usage limit, which applies to all requests made from your application (e.g. 1K emails a day for Gmail, and 10K for G Suite accounts). It’s crucial to monitor your usage to avoid exceeding these limits, which could result in your application being temporarily blocked from sending emails.

SMTP services offer a fixed limit on the number of emails per day (e.g. 500 emails per day to 100 addresses on Gmail, and 2K emails per day on Google Workspace accounts).

But usually the service won’t be disabled disable upon reaching the limit. Instead, your excess emails could get queued for future delivery. Though, you might need to restart the sending manually. 

  • Attachment and message size limits

Gmail API, like SMTP, will have limitations on the size of messages and attachments. Typically, Gmail allows up to 25 MB per email, and anything above that requires using Google Drive links.

  • Handling limit errors

The documentation advises on handling limit errors effectively, which is crucial for maintaining a robust application. Implementing proper error handling and backoff strategies is essential to manage these limits and ensure continuous service.

SMTP services also require handling of delivery failures, often managed through bounce messages and retry mechanisms that are less transparent than API-based feedback.

Is there an alternative to Gmail email infrastructure?

After introducing you to the limitations and possible issues that come with using Gmail SMTP, it’s only natural that the question “Is there an alternative?” pops up in your head. And the answer is, yes, there is!

Along with the common but somewhat basic alternatives, such as Outlook SMTP, Yahoo Mail SMTP, and so on, at your disposal, you also have the SMTP server of Mailtrap Email Sending, a sending solution that gives developers an infrastructure with high deliverability rates by design.

Mailtrap Email Sending also facilitates effortless maintenance and troubleshooting through alerts (deliverability and critical) and unique monitoring tools, which include helicopter-view dashboards, drill-down reports with detailed stats on mailbox providers (Gmail, Google Workspace, O365, and Outlook), email categories, and extended email history with historical data from up to 60 days of email logs.

Other benefits of the sending solution are a smooth and secure setup, the option to use an email API or SMTP service, and an email delivery time of around 1 sec, which allows you to reach recipients’ inboxes just in time.

In the following sections, I’ll cover SMTP and API methods. Plus, I’ll show you how to send bulk emails with Mailtrap since we allow Transactional and Bulk streams under the same sending plan.

SMTP

To find the Mailtrap Email Sending SMTP credentials, you first need to create a Mailtrap account and log in. 

Then, in your account, navigate to Sending Domains, where you need to add and verify your domain.

The process is described in detail in the video below:

Upon domain verification, you will be taken to the SMTP/API Settings menu, where you can copy the SMTP credentials you need to paste into your Laravel app/project to start using Mailtrap’s SMTP server.

Of course, make sure to choose Transactional or Bulk Stream based on your needs. 

API

As mentioned earlier, an email API is also available within Mailtrap Email Sending for those that want to automate the sending process and have better flexibility.

To integrate this email API into your Laravel application, you can use the Mailtrap PHP SDK, which makes the integration easier and more efficient than manually writing the integration code for your project.

These are the steps for using the SDK:

  • Install the Mailtrap PHP client and dependencies using Composer:
composer require railsware/mailtrap-php symfony/http-client nyholm/psr7
  • Add the Mailtrap transport into your config/mail.php file:
<?php

return [
    /*
    |--------------------------------------------------------------------------
    | Mailer Configurations
    |--------------------------------------------------------------------------
    */
    'mailers' => [
        // start mailtrap transport
        'mailtrap' => [
            'transport' => 'mailtrap'
        ],
        // end mailtrap transport
    ]
];
  • Add the Mailtrap credentials into your Laravel .env file:
MAIL_MAILER="mailtrap"
MAILTRAP_HOST="send.api.mailtrap.io"
MAILTRAP_API_KEY="YOUR_API_KEY_HERE"
MAILTRAP_INBOX_ID=1000001
  • Create a mailable class to send email:
php artisan make:mail WelcomeMail
  • Configure the app/Mail/WelcomeMail.php class:
<?php

namespace App\Mail;

use Illuminate\Bus\Queueable;
use Illuminate\Mail\Attachment;
use Illuminate\Mail\Mailable;
use Illuminate\Mail\Mailables\Address;
use Illuminate\Mail\Mailables\Content;
use Illuminate\Mail\Mailables\Envelope;
use Illuminate\Mail\Mailables\Headers;
use Illuminate\Queue\SerializesModels;
use Mailtrap\EmailHeader\CategoryHeader;
use Mailtrap\EmailHeader\CustomVariableHeader;
use Symfony\Component\Mime\Email;
use Symfony\Component\Mime\Header\UnstructuredHeader;

class WelcomeMail extends Mailable
{
    use Queueable, SerializesModels;

    private string $name;

    /**
     * Create a new message instance.
     */
    public function __construct(string $name)
    {
        $this->name = $name;
    }

    /**
     * Get the message envelope.
     */
    public function envelope(): Envelope
    {
        return new Envelope(
            from: new Address('jeffrey@example.com', 'Jeffrey Way'),
            replyTo: [
                new Address('taylor@example.com', 'Taylor Otwell'),
            ],
            subject: 'Welcome Mail',
            using: [
                function (Email $email) {
                    // Headers
                    $email->getHeaders()
                        ->addTextHeader('X-Message-Source', 'example.com')
                        ->add(new UnstructuredHeader('X-Mailer', 'Mailtrap PHP Client'));

                    // Custom Variables
                    $email->getHeaders()
                        ->add(new CustomVariableHeader('user_id', '45982'))
                        ->add(new CustomVariableHeader('batch_id', 'PSJ-12'));

                    // Category (should be only one)
                    $email->getHeaders()
                        ->add(new CategoryHeader('Integration Test'));
                },
            ]
        );
    }

    /**
     * Get the message content definition.
     */
    public function content(): Content
    {
        return new Content(
            view: 'mail.welcome-email',
            with: ['name' => $this->name],
        );
    }

    /**
     * Get the attachments for the message.
     *
     * @return array<int, \Illuminate\Mail\Mailables\Attachment>
     */
    public function attachments(): array
    {
        return [
            Attachment::fromPath('https://mailtrap.io/wp-content/uploads/2021/04/mailtrap-new-logo.svg')
                ->as('logo.svg')
                ->withMime('image/svg+xml'),
        ];
    }

    /**
     * Get the message headers.
     */
    public function headers(): Headers
    {
        return new Headers(
            'custom-message-id@example.com',
            ['previous-message@example.com'],
            [
                'X-Custom-Header' => 'Custom Value',
            ],
        );
    }
}
  • Create an email template under resources/views/mail/welcome-email.blade.php:
Hey, {{$name}} and welcome 😉

<br>
Funny Coder
  • Add the CLI router to the app/routes/console.php file:
<?php

use App\Mail\WelcomeMail;
use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\Mail;

/*
|--------------------------------------------------------------------------
| Console Routes
|--------------------------------------------------------------------------
|
*/

Artisan::command('send-welcome-mail', function () {
    Mail::to('testreceiver@gmail.com')->send(new WelcomeMail("Jon"));
    // Also, you can use a specific mailer if your default mailer is not "mailtrap", but you want to use it for welcome emails
    // Mail::mailer('mailtrap')->to('testreceiver@gmail.com')->send(new WelcomeMail("Jon"));
})->purpose('Send welcome mail');
  • Run the CLI command, which will send your email:
php artisan send-welcome-mail

Now, let me show you how to integrate the bulk stream to send segmented marketing emails and newsletters, for example.

Bulk

To adjust your Laravel setup to send bulk emails using the Mailtrap Bulk Email API, you’ll need to change the host configuration. The method is very close to what’s been covered above, and I’ll lead you every step of the way. 

1. Update the ‘.env’ configuration

You need to change the MAILTRAP_HOST to point to the Bulk Email API host.

MAILTRAP_HOST="bulk.api.mailtrap.io"

2. Update the Mailable class

If the bulk email API uses a similar structure for sending emails, no changes may be needed in your ‘WelcomeMail’ mailable class. However, you should ensure that any specific headers or parameters required by the bulk API are included.

Check the Mailtrap API documentation for any additional headers or parameters required specifically for bulk operations. If such parameters are needed, you would modify the ‘envelope()’ method in your ‘WelcomeMail’ class to include these headers.

For example, I’ll suppose there are specific headers to handle the bulk requests better. So, I’ll add the following inside the ‘using’ lambda function in the ‘envelope’ method.

function (Email $email) {
    // Existing headers
    $email->getHeaders()
        ->addTextHeader('X-Message-Source', 'example.com')
        ->add(new UnstructuredHeader('X-Mailer', 'Mailtrap PHP Client'));

    // Bulk specific headers
    $email->getHeaders()
        ->addTextHeader('X-Bulk-Email', 'true')  // Hypothetical header for bulk processing
        ->addTextHeader('X-Campaign', 'SummerSaleCampaign');
}

3. Check the mail configuration

Make sure your config/mail.php is set to use the mailtrap mailer for the default or specify it when sending the email. 

// Use default Mailtrap mailer
Mail::to('testreceiver@gmail.com')->send(new WelcomeMail("Jon"));

// Or specify the Mailtrap mailer explicitly if not set as default
Mail::mailer('mailtrap')->to('testreceiver@gmail.com')->send(new WelcomeMail("Jon"));

4. Test the configuration

Run the provided Artisan command to send an email through the bulk stream.

php artisan send-welcome-mail

And that’s it! Mailtrap Email Sending API is now integrated with your Laravel app.

Testing emails before sending: why and how?

For some, the process of setting up the email-sending functionality in their Laravel app stops when the email-sending code has been written. For others, who take the more cautious route, this process also involves adding email testing as a crucial pre-sending step.

Through email testing, you get to check how your emails get rendered by web browsers as well as how responsive they are. You can also check the spam score, search for your domain/IP on blacklists, and do even more if you have the proper email testing tool.

An email testing tool that comes with all the mentioned features and others as well, such as the creation of virtual inboxes for different projects and project stages, insight into detailed tech info (email headers and SMTP transaction data), and so on, is Mailtrap Email Testing

Mailtrap Email Testing is a solution that enables you to inspect and debug emails in staging, dev, and QA environments before sending them to recipients. This way, it creates a safe environment for email testing that bears no risk of spamming recipients in the process.

This is an image showing the Mailtrap Email Testing inbox

The solution comes with ready-to-use integrations in 20+ languages, user management, and SSO. 

And what about getting started with Mailtrap Email Testing? That takes just five minutes and consists of the following steps:

  • Create a Mailtrap account and log in. 
  • In your account, navigate to Email Testing – > Inboxes – > SMTP Settings.
  • Select Laravel from the list of integrations.
  • Copy the integration code generated and paste it into your email-sending script.
  • Run the script and receive the first test email in your virtual inbox shortly, where you can test and debug it.

Note: When testing, you don’t have to use real sender or receiver email addresses as the test emails are being sent to virtual inboxes, and email verification is not being done.

Mailtrap Email Testing also offers SMTP credentials for each of your virtual inboxes, which can be accessed by clicking “Show Credentials” on the SMTP Settings page. So, if you prefer using credentials over code snippets for integrating this testing solution, simply copy and paste these into your email-sending script, MTA settings, email client settings, or any other system that supports them.

API

To integrate the Mailtrap email testing API, you need to create a service class to encapsulate the API functionality and adapt Laravel’s HTTP client. Here are the steps:

  1. Use Artisan to create a new service class.
php artisan make:service MailtrapService

Note: If ‘make:service’ is not available, simply create a PHP file in your ‘app/Services’ directory.

  1. Edit the new ‘MailtrapService.php’ to include the Mailtrap API functionality.
namespace App\Services;

use Illuminate\Support\Facades\Http;

class MailtrapService
{
    protected $baseUrl = 'https://sandbox.api.mailtrap.io/api/send';
    protected $apiKey = '123'; // It's better to move this to your .env file

    public function sendEmail(array $data)
    {
        $response = Http::withHeaders([
            'Accept' => 'application/json',
            'Api-Token' => $this->apiKey,
            'Content-Type' => 'application/json',
        ])->post("{$this->baseUrl}/inbox_id", $data);

        if ($response->failed()) {
            return 'Error: ' . $response->body();
        }

        return $response->body();
    }
}
  1. Add your API key and possibly the base URL to your ‘.env’ file for better security and flexibility.
MAILTRAP_API_KEY=YOUR_MT_API_KEY
MAILTRAP_BASE_URL=https://sandbox.api.mailtrap.io/api/send
  1. Update the service class to use the ‘.env’ values.
protected $baseUrl;
protected $apiKey;

public function __construct()
{
    $this->baseUrl = env('MAILTRAP_BASE_URL', 'https://sandbox.api.mailtrap.io/api/send');
    $this->apiKey = env('MAILTRAP_API_KEY');
}
  1. Use the service in a controller or command to test emails.
use App\Services\MailtrapService;

class EmailController extends Controller
{
    protected $mailtrap;

    public function __construct(MailtrapService $mailtrap)
    {
        $this->mailtrap = $mailtrap;
    }

    public function sendTestEmail()
    {
        $data = [
            // Your email data structure here
        ];

        $result = $this->mailtrap->sendEmail($data);
        return response()->json(['message' => $result]);
    }
}
  1. Define a route to trigger the email testing method.
Route::get('/test-mailtrap', [EmailController::class, 'sendTestEmail']);

Wrapping things up

Laravel and Gmail have two major things in common – widespread usage and lots of educational resources. Hopefully, this article will now serve as your go-to resource for learning how to use Laravel to send email through Gmail or a great alternative like Mailtrap Email Sending. 

To further expand your knowledge on using this framework for email sending, testing, verification, and more, make sure to check out a couple of our other articles:

As well as our YouTube channel featuring videos on Laravel-related topics:

Article by Dzenana Kajtaz Technical Content Writer @Mailtrap

I’m a Technical Content Writer with an educational background in software engineering and 4 years of experience as a writer, editor, and content marketer. Currently, I mainly focus on writing about email infrastructure, sending, testing, and so on, but I do love creating fun and engaging content on email marketing topics as well.

Article by Veljko Ristić Content Manager @ Mailtrap

Linguist by trade, digital marketer at heart, I’m a Content Manager who’s been in the online space for 10+ years. From ads to e-books, I’ve covered it all as a writer, editor, project manager, and everything in between. Now, my passion is with email infrastructure with a strong focus on technical content and the cutting-edge in programming logic and flows. But I still like spreading my gospels while blogging purely about marketing.