directorytree/imapengine
IMAP Engine is a Laravel-friendly PHP package for working with IMAP mailboxes. It simplifies connecting to mail servers, browsing folders, fetching and searching messages, and handling attachments with a clean, developer-focused API for email workflows.
composer require directorytree/imapengine
use DirectoryTree\ImapEngine\Client;
$client = new Client([
'host' => 'imap.example.com',
'port' => 993,
'encryption' => 'ssl',
'username' => 'user@example.com',
'password' => 'password',
]);
$inbox = $client->getFolder('INBOX');
$messages = $inbox->search(['UNSEEN'])->fetch(['headers', 'body']);
foreach ($messages as $message) {
echo $message->subject . "\n";
}
Client Class: Entry point for connections and folder operations.Folder Methods: search(), fetch(), poll(), and idle() for real-time updates.Message Properties: headers, body, attachments, and flags for parsing.$client = new Client(config('imap'));
$client->connect(); // Throws `ImapConnectionFailedException` on failure
.env and bind Client to the container:
$this->app->singleton(Client::class, function ($app) {
return new Client($app['config']['imap']);
});
$folder = $client->getFolder('INBOX');
$messages = $folder->search([
'FROM' => 'support@example.com',
'SINCE' => '2023-01-01',
'UNSEEN',
])->fetch(['headers', 'body']);
with() to defer fetching:
$messages = $folder->search(['UNSEEN'])->with(['headers']);
foreach ($messages as $message) {
if ($message->hasBody()) {
$message->fetchBody(); // Load on demand
}
}
$folder->bulkQuery()
->flag(['\Seen'], [1, 2, 3]) // Mark messages 1, 2, 3 as read
->delete([4, 5, 6]) // Delete messages 4, 5, 6
->execute();
$folder->poll(30, function ($messages) { // Poll every 30 seconds
foreach ($messages as $message) {
// Process new messages
}
});
PollingJob::dispatch($folder, 30);
$message = $folder->search(['FROM' => 'user@example.com'])->first();
foreach ($message->attachments() as $attachment) {
$attachment->saveTo(storage_path('app/attachments/' . $attachment->filename));
}
filename or disposition:
$attachment->contentDisposition; // e.g., 'attachment; filename="report.pdf"'
$messages = $folder->search(['UNSEEN'])
->sort('DATE', 'DESC') // Sort by date (server-side)
->fetch(['headers']);
$folder->poll(60, function ($messages) {
event(new NewMessagesReceived($messages));
});
foreach ($messages as $message) {
Email::create([
'subject' => $message->subject,
'body' => $message->text,
'folder' => $folder->name,
]);
}
try {
$folder->fetch(['body']);
} catch (ImapException $e) {
$this->retryLater();
}
Connection Timeouts:
setTimeout() on the Client or implement chunked fetching:
$client->setTimeout(120); // 2 minutes
Lazy Loading Overhead:
with() or fetchBody() can lead to N+1 queries.// Bad: Fetch all headers first, then bodies
$messages = $folder->search([])->with(['headers']);
foreach ($messages as $message) {
$message->fetchBody(); // Separate call per message
}
// Good: Fetch headers + bodies in one go
$messages = $folder->search([])->fetch(['headers', 'body']);
UTF-8 Decoding:
From names).$from = mb_convert_encoding($message->from, 'UTF-8', 'auto');
Attachment Corruption:
if ($attachment->isValid()) {
$attachment->saveTo(...);
}
IDLE Mode Quirks:
$folder->idle() may hang or fail silently.$folder->idle(300, function () { // Timeout after 5 minutes
return true; // Break idle
});
Bulk Operations Limits:
$batchSize = 50;
$uids = range(1, 1000);
foreach (array_chunk($uids, $batchSize) as $chunk) {
$folder->bulkQuery()->delete($chunk)->execute();
}
$client->setLogger(new MonologLogger(Logger::create('imap')));
$client->setDebug(true); // Logs raw IMAP commands/responses
$uids = $folder->search([])->uids(); // Get UIDs first
$folder->bulkQuery()->delete($uids)->execute();
Custom Message Parsing:
Message class to add domain-specific logic:
class CustomMessage extends \DirectoryTree\ImapEngine\Message
{
public function isSupportTicket(): bool
{
return str_contains($this->subject, '[Support]');
}
}
fetch() method in your Folder class to use the custom parser.Attachment Storage:
class AttachmentStorage
{
public function store(Attachment $attachment): string
{
return Storage::disk('s3')->put(
'attachments/' . $attachment->filename,
$attachment->content
);
}
}
Query Builder Extensions:
$folder->search(['CUSTOM_CRITERIA' => 'value']);
CustomCriteria class extending SearchCriteria.Event Dispatching:
MessageFetched or FolderPolled events to trigger actions:
$folder->search([])->fetch(['headers'])->each(function ($message) {
event(new MessageProcessed($message));
});
How can I help you explore Laravel packages today?