ElkunCoding

How To Use IMAP/POP3 with Laravel to Fetch Mailbox

How To Use IMAP/POP3 with Laravel to Fetch Mailbox

In this article, I will reveal a method for accessing your personal email inbox, such as those from Gmail or Yahoo, using PHP and the Laravel framework.

Many enterprise systems, such as Customer Relationship Management (CRM) platforms, offer the ability to manage mailboxes and emails directly within the system, allowing system administrators to easily monitor their inbox and compose messages without having to switch to external email clients like Outlook.

PHP offers support for connecting to and reading mailboxes through the use of the IMAP or POP3 protocols. I will demonstrate this technique by utilizing the Webklex/laravel-imap package, which is built on the PHP imap extension, in the Laravel framework.

Installation

Before getting started, it’s important to ensure that the PHP IMAP extension library is properly installed on your system. If it’s not already installed, you can use the following command to install it:

sudo apt-get install php*-imap php*-mbstring php*-mcrypt && sudo apache2ctl graceful

After installation, be sure to add it to your PHP.ini file and restart your Apache server. You can also check if the IMAP extension was installed successfully by running the phpinfo() function.

To use the Webklex/laravel-imap package in your Laravel project, you’ll need to install it via Composer by running the following command:

composer require webklex/laravel-imap

If you’re using Laravel 5.5 or later, the package discovery will automatically configure the service provider and Client alias for you.

However, if you’re using Laravel 5.4 or earlier, you’ll need to manually add the following to your config/app.php file:

  • Add this to the providers array: Webklex\IMAP\Providers\LaravelServiceProvider
  • Add this to the aliases array: Client => Webklex\IMAP\Facades\Client::class

Once you have completed these steps, you can generate the config file by running the command:

php artisan vendor:publish --provider="Webklex\IMAP\Providers\LaravelServiceProvider"

Configuration

1- Single Account

If you intend to use the package for only a single account then the process is pretty simple, add those configuration items into the .env file:

IMAP_HOST=somehost.com
IMAP_PORT=993
IMAP_ENCRYPTION=ssl
IMAP_VALIDATE_CERT=true
IMAP_USERNAME=root@example.com
IMAP_PASSWORD=secret
IMAP_DEFAULT_ACCOUNT=default
IMAP_PROTOCOL=imap

1- Multi Accounts

If you intend to use it for multi accounts such as every user in the system can control their mailbox it’s preferred to store the above variables into the database in a separate table connected with user_id.

If you open config/imap.php their will a lot of options:

<?php

return [
   
 'default' => env('IMAP_DEFAULT_ACCOUNT', 'default'),

'accounts' => [
        'default' => [// account identifier
            'host'  => env('IMAP_HOST', 'localhost'),
            'port'  => env('IMAP_PORT', 993),
            'protocol'  => env('IMAP_PROTOCOL', 'imap'), //might also use imap, [pop3 or nntp (untested)]
            'encryption'    => env('IMAP_ENCRYPTION', 'ssl'), // Supported: false, 'ssl', 'tls'
            'validate_cert' => env('IMAP_VALIDATE_CERT', true),
            'username' => env('IMAP_USERNAME', 'root@example.com'),
            'password' => env('IMAP_PASSWORD', ''),
        ],
        /*
        'gmail' => [ // account identifier
            'host' => 'imap.gmail.com',
            'port' => 993,
            'encryption' => 'ssl', // Supported: false, 'ssl', 'tls'
            'validate_cert' => true,
            'username' => 'example@gmail.com',
            'password' => 'PASSWORD',
        ],
        'another' => [ // account identifier
            'host' => '',
            'port' => 993,
            'encryption' => false, // Supported: false, 'ssl', 'tls'
            'validate_cert' => true,
            'username' => '',
            'password' => '',
        ]
        */
    ],

    'options' => [
        'delimiter' => '/',
        'fetch' => \Webklex\IMAP\IMAP::FT_UID,
        'fetch_body' => true,
        'fetch_attachment' => true,
        'fetch_flags' => true,
        'message_key' => 'id',
        'fetch_order' => 'asc',
        'open' => [
            // 'DISABLE_AUTHENTICATOR' => 'GSSAPI'
        ],
        'decoder' => [
            'message' => [
                'subject' => 'utf-8' // mimeheader
            ],
            'attachment' => [
                'name' => 'utf-8' // mimeheader
            ]
        ]
    ],
    
    'masks' => [
        'message' => \Webklex\IMAP\Support\Masks\MessageMask::class,
        'attachment' => \Webklex\IMAP\Support\Masks\AttachmentMask::class
    ]
];
  • default: This is the default account
  • accounts: This array contains all available accounts such as default and gmail. You can add as many accounts.
  • delimiter — you can use any supported char such as “.”, “/”, etc
  • fetch — IMAP::FT_UID (message marked as read by fetching the message) or IMAP::FT_PEEK (fetch the message without setting the “read” flag)
  • fetch_body — If set to false all messages will be fetched without the body and any potential attachments
  • fetch_attachment — If set to false all messages will be fetched without any attachments
  • fetch_flags — If set to false all messages will be fetched without any flags
  • message_key — Message key identifier option useful when you want to save the messages into the database.
  • fetch_order — Message fetch order by ascending or descending
  • open — special configuration for imap_open()
    • DISABLE_AUTHENTICATOR — Disable authentication properties.
  • decoder — Currently only the message subject and attachment name decoder can be set
  • masks — Default masking config
    • message — Default message mask
    • attachment — Default attachment mask

Example Fetching all messages

use Webklex\IMAP\Client;

$oClient = new Client([
    'host'          => 'somehost.com',
    'port'          => 993,
    'encryption'    => 'ssl',
    'validate_cert' => true,
    'username'      => 'username',
    'password'      => 'password',
    'protocol'      => 'imap'
]);

//Connect to the IMAP Server
$oClient->connect();

//Get all Mailboxes
/** @var \Webklex\IMAP\Support\FolderCollection $aFolder */
$aFolder = $oClient->getFolders();

//Loop through every Mailbox
/** @var \Webklex\IMAP\Folder $oFolder */
foreach($aFolder as $oFolder){

    //Get all Messages of the current Mailbox $oFolder
    /** @var \Webklex\IMAP\Support\MessageCollection $aMessage */
    $aMessage = $oFolder->messages()->all()->get();
    
    /** @var \Webklex\IMAP\Message $oMessage */
    foreach($aMessage as $oMessage){
        echo $oMessage->getSubject().'<br />';
        echo 'Attachments: '.$oMessage->getAttachments()->count().'<br />';
        echo $oMessage->getHTMLBody(true);
    }
}

Sometimes when you run this example it takes a long time to execute and this is because this code fetch all messages in your inbox and this can be a huge number so the best way is to limit the results as demonstrated below in the pagination and limit sections.

Using Facade

use Webklex\IMAP\Facades\Client;

$oClient = Client::account('default');
$oClient->connect();

Get specific folder by name

$oFolder = $oClient->getFolder('INBOX.read');  // get the read inbox

Searching For Messages:

//Get all messages
/** @var \Webklex\IMAP\Support\MessageCollection $aMessage */
$aMessage = $oFolder->query()->all()->get();

//Get all messages from example@domain.com
/** @var \Webklex\IMAP\Support\MessageCollection $aMessage */
$aMessage = $oFolder->query()->from('example@domain.com')->get();

//Get all messages since march 15 2018
/** @var \Webklex\IMAP\Support\MessageCollection $aMessage */
$aMessage = $oFolder->query()->since('15.03.2018')->get();

//Get all messages within the last 5 days
/** @var \Webklex\IMAP\Support\MessageCollection $aMessage */
$aMessage = $oFolder->query()->since(now()->subDays(5))->get();
//Or for older laravel versions..
$aMessage = $oFolder->query()->since(\Carbon::now()->subDays(5))->get();

//Get all messages containing "hello world"
/** @var \Webklex\IMAP\Support\MessageCollection $aMessage */
$aMessage = $oFolder->query()->text('hello world')->get();

//Get all unseen messages containing "hello world"
/** @var \Webklex\IMAP\Support\MessageCollection $aMessage */
$aMessage = $oFolder->query()->unseen()->text('hello world')->get();

//Extended custom search query for all messages containing "hello world" 
//and have been received since march 15 2018
/** @var \Webklex\IMAP\Support\MessageCollection $aMessage */
$aMessage = $oFolder->query()->text('hello world')->since('15.03.2018')->get();
$aMessage = $oFolder->query()->Text('hello world')->Since('15.03.2018')->get();
$aMessage = $oFolder->query()->whereText('hello world')->whereSince('15.03.2018')->get();

// Build a custom search query
/** @var \Webklex\IMAP\Support\MessageCollection $aMessage */
$aMessage = $oFolder->query()
->where([['TEXT', 'Hello world'], ['SINCE', \Carbon::parse('15.03.2018')]])
->get();

Refer to Webklex/laravel-imap for more options on searching.

Result limiting

To limit the results you can use function limit(number, page):

//Get all messages for page 2 since march 15 2018 where each page contains 10 messages
$aMessage = $oFolder->query()->limit(10, 2)->get();

Pagination

Use function paginate() to paginate:

$paginator = $oFolder->query()->paginate();

Paginate a message collection:

$paginator = $aMessage->paginate();

By default the pagination displays 15 results per page but you can customize it like this:

$paginator = $oFolder->search()
->since(\Carbon::now()->subDays(14))->get()
->paginate(5);

<table>
    <thead>
        <tr>
            <th>UID</th>
            <th>Subject</th>
            <th>From</th>
            <th>Attachments</th>
        </tr>
    </thead>
    <tbody>
        @if($paginator->count() > 0)
            @foreach($paginator as $oMessage)
                <tr>
                    <td>{{$oMessage->getUid()}}</td>
                    <td>{{$oMessage->getSubject()}}</td>
                    <td>{{$oMessage->getFrom()[0]->mail}}</td>
                    <td>{{$oMessage->getAttachments()->count() > 0 ? 'yes' : 'no'}}</td>
                </tr>
            @endforeach
        @else
            <tr>
                <td colspan="4">No messages found</td>
            </tr>
        @endif
    </tbody>
</table>

{{$paginator->links()}}

Messages

Get specific message

Get a specific message by uid (Please note that the uid is not unique and can change):

$oMessage = $oFolder->getMessage($uid = 1);

Set message flags:

$oMessage->setFlag(['Seen', 'Spam']);
$oMessage->unsetFlag('Spam');

// Mark all messages as "read" while fetching:
$aMessage = $oFolder->query()->text('Hello world')->markAsRead()->get();

// Don't mark all messages as "read" while fetching:
$aMessage = $oFolder->query()->text('Hello world')->leaveUnread()->get();

Attachments

Get message attachments

// Save message attachments:

$aAttachment = $oMessage->getAttachments();

$aAttachment->each(function ($oAttachment) {
    /** @var \Webklex\IMAP\Attachment $oAttachment */
    $oAttachment->save();
});

Fetch messages without body fetching (decrease load) useful in listing pages:

$aMessage = $oFolder->query()->whereText('Hello world')->setFetchBody(false)->get();
$aMessage = $oFolder->query()->whereAll()->setFetchBody(false)->setFetchAttachment();

This example will disable body fetching to speed the retrieval process while fetching.

Fetch messages without body, flag and attachment fetching (decrease load) useful in listing pages:

$aMessage = $oFolder->query()->whereText('Hello world')
->setFetchFlags(false)
->setFetchBody(false)
->setFetchAttachment(false)
->get();


$aMessage = $oFolder->query()->whereAll()
->setFetchFlags(false)
->setFetchBody(false)
->setFetchAttachment(false)
->get();

This example will disable body and attachment fetching to speed the retrieval process while fetching.

Best practices

  • When you intend to read a mailbox in your own system try to save the messages into the database.
  • Use a cronjob to read and save the messages in the background.
  • When saving the data check if the message not exist in the database otherwise save it.
  • Always save the important data only such as subject, body, from, to, cc, bcc, and attachments.
  • Create a sync button which syncs your local messages with the remote messages.

Leave a Comment

Your email address will not be published. Required fields are marked *