Software developers often encounter situations needing access to email inboxes. Typically, they achieve this using IMAP](https://en.wikipedia.org/wiki/Internet_Message_Access_Protocol), which stands for Internet Message Access Protocol. The standard PHP library provides IMAP functionality [PHP developer, I first turned to PHP’s built in IMAP library, however, it suffers from bugs, lacks debugging capabilities, and modifications are not feasible. Furthermore, it doesn’t offer customization of IMAP commands for leveraging the full potential of the protocol.
This article demonstrates how to build a fully functional IMAP email client using PHP from scratch. We’ll also explore how to incorporate Gmail’s special commands in the process.
We’ll encapsulate our IMAP implementation within a custom class named imap_driver. Each step of the class construction will be thoroughly explained. At the end of this article, you can download the complete source code for imap_driver.php.
Establishing a Connection
IMAP, being a connection-oriented protocol, usually operates over TCP/IP with SSL encryption. Therefore, establishing a connection is a prerequisite before executing any IMAP commands.
We need the IMAP server’s URL and port number, typically found in the service’s documentation or website. For instance, for Gmail, the URL is ssl://imap.gmail.com and the port is 993.
To check if the initialization is successful, our class constructor will remain empty, and a custom init() method will handle the connection process. If the connection fails, the method will return false:
| |
A 15-second timeout is set for both fsockopen() to establish the connection and for the data stream to respond to requests once connected. Timeouts are crucial for network calls because servers might become unresponsive, and our code needs to handle such situations.
The initial line from the stream, usually a greeting or connection confirmation, is retrieved and discarded. Consult your email service’s documentation to confirm this behavior.
Let’s execute this code to verify the success of init():
| |
Basic IMAP Syntax
With an active socket connection to our IMAP server, we can start sending commands. Let’s delve into IMAP syntax.
For formal documentation, refer to the Internet Engineering Task Force (IETF) RFC3501. In IMAP interactions, the client typically sends commands, and the server responds, indicating success or failure, often accompanied by the requested data.
The basic command syntax is:
| |
The “tag,” or line number, uniquely identifies the command, allowing the server to correlate responses when processing multiple commands simultaneously.
Here’s an example illustrating the LOGIN command:
| |
The server may start its response with an “untagged” data response. For example, upon successful login, Gmail sends an untagged response detailing server capabilities and options. Similarly, fetching an email results in an untagged response containing the message body. Regardless, responses always conclude with a “tagged” command completion line. This line identifies the corresponding command’s line number, a completion status indicator, and any additional metadata:
| |
Gmail’s response to the LOGIN command is shown below:
- Success:
| |
- Failure:
| |
The status can be OK for success, NO for failure, or BAD for invalid commands or syntax errors.
Implementing Basic Commands:
Let’s create a function to send commands to the IMAP server and retrieve the response along with the endline:
| |
The LOGIN Command
Now, we can define functions for specific commands that utilize our command() function internally. Let’s write one for the LOGIN command:
| |
We can test it as follows (ensure you have an active email account):
| |
Gmail prioritizes security and, by default, restricts IMAP access from countries other than the account profile’s country unless configured otherwise. This can be easily rectified by enabling less secure settings in your Gmail account, as explained here.
The SELECT Command
Let’s now explore selecting an IMAP folder for email manipulation. Thanks to our command() method, the syntax resembles LOGIN. We use the SELECT command and provide the folder name.
| |
To test, let’s select the INBOX:
| |
Implementing Advanced Commands
Let’s examine the implementation of more sophisticated IMAP commands.
The SEARCH Command
Searching for emails within specific date ranges or with particular flags is a common requirement. The SEARCH command accepts search criteria as space-separated arguments. For instance, to retrieve emails since November 20th, 2015:
| |
A typical response would be:
| |
Detailed documentation on search terms can be found here. The SEARCH command output is a space-separated list of email UIDs. A UID is a chronologically ordered, unique identifier for an email within a user’s account, with 1 being the oldest. To implement this command, we simply return the resulting UIDs:
| |
Let’s test this by fetching emails from the past three days:
| |
The FETCH Command with BODY.PEEK
Fetching email headers without marking them as SEEN is another frequent task. The IMAP manual states that the appropriate command for this is command for retrieving all or part of an email FETCH. The first argument specifies the desired part, usually BODY, which returns the entire message, including headers, but marks it as SEEN. Alternatively, BODY.PEEK accomplishes the same without marking the message as read.
IMAP syntax requires us to specify the desired email section within square brackets, which is [HEADER] in this case. Consequently, the command becomes:
| |
We expect a response resembling this:
| |
To create a function for fetching headers, we need to return the response as a hash structure (key/value pairs):
| |
Testing this code involves providing the target message’s UID:
| |
Gmail IMAP Extensions
Gmail offers specialized commands that can greatly simplify our tasks. A list of these Gmail IMAP extensions can be found here. Let’s focus on what’s arguably the most useful one: X-GM-RAW. This extension allows using Gmail’s search syntax within IMAP. For example, we can search for emails categorized as Primary, Social, Promotions, Updates, or Forums.
Essentially, X-GM-RAW extends the SEARCH command, allowing us to reuse our existing SEARCH code. We only need to add the X-GM-RAW keyword followed by the criteria:
| |
This code will retrieve all UIDs of emails categorized as “Primary”.
Note: As of December 2015, Gmail sometimes confuses the “Primary” category with the “Updates” category for certain accounts. This known Gmail bug remains unresolved.
Conclusion
The custom socket approach empowers developers with greater flexibility and control. It allows implementing any command defined in IMAP RFC3501 and provides transparency by eliminating the need to speculate about “behind-the-scenes” operations.
The complete imap_driver class implementation from this article is available for download here. It’s ready for immediate use, and developers can easily extend it by adding new functions or requests to interact with their IMAP server. A debug feature is also included for verbose output.