ddeboer/imap: an object oriented IMAP client for PHP
Object-oriented, fully tested PHP IMAP library
At a glance
- What is it?
- The ddeboer/imap library wraps PHP's IMAP extension in an object oriented API of Server, Connection, Mailbox, Message and Attachment objects, with a composable search expression builder and helpers for flags, attachments and MIME bodies. It requires PHP 8.4 and the IMAP extension, and it inherits the c-client library's gaps, including the unsupported OR search criterion.
- Who is it for?
- Adopt ddeboer/imap if you are writing PHP code that has to read or manipulate mailboxes over IMAP and you want typed objects and a composable search builder instead of raw imap_* function calls, and if you can install the IMAP, iconv and mbstring extensions.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 123 days ago.
- What is it written in?
- Mainly PHP, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 21, 2026, and from our analysis. They are not legal advice.
Editorial analysis
Objects instead of a wall of imap_ functions
PHP ships an IMAP extension, and using it directly means a set of procedural functions taking resource handles that you pass around and that you must remember to clean up. The ddeboer/imap library exists to put an object oriented layer over that, and the class names tell you the shape of the API.
A Server object represents the mail server. A Connection is what you get back from authenticating, and it owns the mailbox operations. A Mailbox holds messages and knows how to count, flag and search them. A Message represents one email, with properties for subject, addresses, date and flags, and methods for bodies, attachments and movement. An Attachment is a part of a message that can be downloaded.
The library is described as a PHP IMAP library to read and process emails over the IMAP protocol, built with object oriented architecture, and the packaging is via Packagist with a CI workflow. The stated requirement is PHP 8.4 or newer with the IMAP, iconv and Multibyte String extensions installed, which is the first thing to check on a hosting target.
The 8.4 floor is worth noting. It rules out the older PHP versions still common in shared hosting, and it is a deliberate choice rather than an accident of a missing constraint in the composer.json.
Connecting with sensible SSL defaults
Connection is two calls. You construct a Server with a hostname and authenticate with credentials.
use Ddeboer\Imap\Server;
$server = new Server('imap.gmail.com');
// $connection is instance of \Ddeboer\Imap\Connection
$connection = $server->authenticate('my_username', 'my_password');The constructor takes more than the hostname if you need it. The port defaults to 993, the flags default to the string that enables SSL and validates the certificate, and the parameters argument is passed through.
$server = new Server(
$hostname, // required
$port, // defaults to '993'
$flags, // defaults to '/imap/ssl/validate-cert'
$parameters
);That default is the right default. Validating the certificate is on unless you turn it off, and 993 is the implicit TLS port. If you need to talk to a server on a non standard port or with a self signed certificate, you override the flags deliberately, which is a good property for a security relevant default.
Installation is a single Composer command, and the note that it requires Composer installed globally is the only prerequisite.
$ composer require ddeboer/imapMailboxes, container folders and bulk flag changes
Mailboxes are retrieved as a collection you can iterate, and each one reports a name, attributes and a message count.
$mailboxes = $connection->getMailboxes();
foreach ($mailboxes as $mailbox) {
// Skip container-only mailboxes
// @see https://secure.php.net/manual/en/function.imap-getmailboxes.php
if ($mailbox->getAttributes() & \LATT_NOSELECT) {
continue;
}
// $mailbox is instance of \Ddeboer\Imap\Mailbox
printf('Mailbox "%s" has %s messages', $mailbox->getName(), $mailbox->count());
}The container check matters in practice. The attribute test skips folders that only exist to hold other folders, which otherwise show up as empty entries in a listing.
Flags can be set or cleared in bulk, addressed by UID ranges rather than one message at a time.
$mailbox->setFlag('\\Seen \\Flagged', ['1:5', '7', '9']);
$mailbox->setFlag('\\Seen', '1,3,5,6:8');
$mailbox->clearFlag('\\Flagged', '1,3');The warning attached to this is the part to internalise: you must retrieve new Message instances in case of bulk modify flags to refresh the single Message flags. In other words, a Message object holds a snapshot of the flags it was created with, and a bulk update changes the server state without updating the objects you already hold. Code that reads isSeen or isDeleted after a bulk set will read stale values unless it re-fetches.
The search expression builder and its two documented gaps
Searching is expressed by composing conditions into a SearchExpression, with one class per criterion and a namespace split by what is being searched.
use Ddeboer\Imap\SearchExpression;
use Ddeboer\Imap\Search\Email\To;
use Ddeboer\Imap\Search\Text\Body;
$search = new SearchExpression();
$search->addCondition(new To('[email protected]'));
$search->addCondition(new Body('contents'));
$messages = $mailbox->getMessages($search);Two limitations are stated in the documentation and both are worth knowing before you design around this class.
The first is escaping. The documentation warns that it is currently unable to have both spaces and double quotes escaped together, and that only spaces are currently escaped correctly. The escape hatch is the RawExpression class, where you write the complete search condition yourself.
The second is the OR criterion, and this one is not the library's fault. PHP's IMAP extension relies on the c-client library, which does not fully support some IMAP4 search criteria such as OR. The documented remedy is to manually patch the latest c-client version and recompile PHP against it, which is an operation that takes your PHP build out of the distribution's hands.
The documentation reassures that most common search criteria are available and working, and points at the ./src/Search directory to browse them. Results can also be sorted as per the imap_sort function, with a sort criterion and a descending flag.
$today = new DateTimeImmutable();
$thirtyDaysAgo = $today->sub(new DateInterval('P30D'));
$messages = $mailbox->getMessages(
new Ddeboer\Imap\Search\Date\Since($thirtyDaysAgo),
\SORTDATE, // Sort criteria
true // Descending order
);Message properties, bodies and the unseen semantics
A Message exposes its number and its Message-ID, plus the properties you would expect of an email header set.
$message->getSubject();
$message->getFrom(); // Message\EmailAddress
$message->getTo(); // array of Message\EmailAddress
$message->getDate(); // DateTimeImmutable
$message->isAnswered();
$message->isDeleted();
$message->isDraft();
$message->isSeen();Bodies come in two flavours, and the distinction is deliberate. The single part versions return the content of the first text/html or text/plain part if present, while the complete body versions walk all parts of a multipart message.
$body = $message->getCompleteBodyHtml(); // Content of text/html part, if present
if ($body === null) { // If body is null, there are no HTML parts, so let's try getting the text body
$body = $message->getCompleteBodyText(); // Content of text/plain part, if present
}The behaviour worth internalising is that reading the message body keeps the message as unseen. If you want it marked, you ask for it.
$message->markAsSeen();That is not a quirk, it is a sensible default for a mailbox reader, and it means a read-only integration will not silently mark a user's mail as handled. Moving a message to another mailbox and deleting messages are likewise method calls, with deletion needing a separate expunge on the connection to take effect.
$mailbox = $connection->getMailbox('another-mailbox');
$message->move($mailbox);$mailbox->getMessage(1)->delete();
$mailbox->getMessage(2)->delete();
$connection->expunge();Attachments, the test harness and how the project is checked
Attachments are exposed as objects, both inline ones and attached ones, and can be iterated or downloaded to a local file. The documentation notes that getDecodedContent() decodes the attachment contents automatically.
$attachments = $message->getAttachments();
foreach ($attachments as $attachment) {
// $attachment is instance of \Ddeboer\Imap\Message\Attachment
}The development setup is more interesting than the code, because it explains how the library can claim to be fully tested. There is a Dockerfile based on php:8.4 that installs the composer, iconv, imap and pcov extensions with an external extension installer, and pulls in composer-normalize. The Makefile drives everything through that container, with a tag of ddeboer-imap-local-dev and the working directory mounted into it.
The interesting part is the test server. A dedicated target builds the image, pulls an IMAP development server container, creates a Docker network, runs the server with a test domain and credentials, exposes port 993 and publishes 10993 on the host, then waits for it with the wait-for-it script fetched over wget.
docker pull antespi/docker-imap-devel:latest
docker network create ddeboer_imap_networkdocker run \
--name=ddeboer_imap_server \
--network=ddeboer_imap_network \
--detach \
--rm \
--expose 993 \
--publish 10993:993 \
--env MAILNAME=test.test \
--env [email protected] \
--env MAIL_PASS=p4ssword \
antespi/docker-imap-devel:latestTests then run inside the container with zend.assertions=1 and phpunit, and the default make target chains code style fixing with php-cs-fixer, static analysis with phpstan at a 256M memory limit, and the test suite. There is also a composer-require-checker configuration and a phpstan baseline, both of which suggest a project that cares about its dependency surface.
Editorial conclusion
Adopt ddeboer/imap if you are writing PHP code that has to read or manipulate mailboxes over IMAP and you want typed objects and a composable search builder instead of raw imap_* function calls, and if you can install the IMAP, iconv and mbstring extensions. Do not adopt it if your host cannot provide the IMAP extension, which rules out most managed PHP hosting, or if you need OR search criteria, because that requires patching and recompiling PHP against the c-client library yourself. Verify first that the c-client library on your system supports the search criteria you plan to use, that your mail server is reachable on port 993 with certificate validation on, and that you read the licence file, since the project is released under the MIT licence.
Frequently asked questions
How do I install the ddeboer/imap library?
Use Composer with the command composer require ddeboer/imap, which requires Composer to be installed globally. The library also needs PHP 8.4 or newer with the IMAP, iconv and Multibyte String extensions installed.
How do I search for messages with ddeboer/imap?
Build a SearchExpression and add condition objects, then pass it to getMessages. Search classes live under the Search namespace and are split by target, with Ddeboer\Imap\Search\Email\To for a recipient and Ddeboer\Imap\Search\Text\Body for body content, and RawExpression lets you write the condition yourself.
Why does the OR search criterion fail in ddeboer/imap?
Because the PHP IMAP extension relies on the c-client library, which does not fully support some IMAP4 search criteria like OR. The documented workaround is to manually patch the latest c-client version and recompile PHP onto that patched library.
Does reading a message body mark it as seen in ddeboer/imap?
No, reading the body keeps the message as unseen, and you call markAsSeen() if you want it changed. A related detail is that after bulk flag modifications you must retrieve new Message instances, because existing objects keep the flags they were created with.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/ddeboer-imap)Was this useful?