IMAP Library

If you’re new to the IMAP protocol, take a look at IMAP Concepts first!

The IMAP Library is used to interact with the email server using a pure Lua implementation of the IMAP protocol.

The IMAPclient uses Meta tables to make a simple IMAP adapter with discrete methods for different operations.

IMAPclient is Using a table as a function argument to pass all details required to authenticate with the email server.

To begin the TCP conversation, the IMAPclient initializes the TCP connection, gets the connection response and calls IMAPcommand to login and authenticate with the server.

if statements and the error() function are used to implement error handling for missing authentication details or a failed login attempt.

If the login is successful, the resultant meta table containing the starting Id, TCP connection and the IMAP methods is returned.

require 'IMAP.IMAPcommand' local MetaTable = {} MetaTable.__index = {} MetaTable.__index.selectInbox = require 'IMAP.IMAPselectInbox' MetaTable.__index.fetch = require 'IMAP.IMAPfetch' MetaTable.__index.close = require 'IMAP.IMAPclose' MetaTable.__index.fetchSummary = require 'IMAP.IMAPfetchSummary' MetaTable.__index.fetchBatch = require 'IMAP.IMAPfetchBatch' function IMAPclient(T) local Result = {} if T.email == "" or T.password == "" then error("Missing login details", 2) end setmetatable(Result, MetaTable) Result.Id = 0; Result.Conn = net.tcp.connect{host=T.host, port=993, secure=true} local Response, Success = IMAPreadResponse(Result.Conn, '') local Command = "LOGIN "..T.email.." "..T.password:gsub(" ", "") local Success = IMAPcommand(Result, Command) trace(Success) if (Success ~= 'OK') then error("Failed login", 2) end return Result end

IMAPcommand is passed the connection and IMAP command as arguments. The command is sent with an incremented ID to manage the client-server interactions. The ID and command are formated by Concatenating strings together.

function IMAPcommand(C, Command) C.Id = C.Id + 1 local ID = "a0"..C.Id C.Conn:send(ID.." "..Command.."\r\n") local Response, Ended = IMAPreadResponse(C.Conn, ID) return Ended, ID, Response end

The response is received and read by the IMAPreadResponse function. AnInfinite loop is used to continually receive responses until IMAPended returns an IMAP response code that does not equal nil.

function IMAPreadResponse(Conn, ID) local Response = '' while (true) do Response = Response..Conn:recv() local Ended = IMAPended(Response, ID) if Ended ~= nil then return Response,Ended end end end

The IMAPended function is called by IMAPreadResponse to String:find() and return the IMAP response code - OK, NO, or BAD.

IMAPselectInbox using the SELECT command to choose the INBOX to query. The IMAPhighestId function takes the response of the SELECT command, and uses String:split()to split the response into a Lua table as list. Using a for loop and the # Operator on Lua Tables, each line of the response is checked to find the position of “EXISTS“ and return the extracted number value representing the number of emails in the Inbox.

IMAPfetch gets the email body. The connection and command arguments are passed to IMAPcommand to send the command. To get the email body we are using FETCH with MailId and the “BODY.PEEK[]“ command.

String:find() is used on the IMAP response, to find the positions of the first and last line that wrap the email body, which we want to capture.

String:sub() extracts and returns the body content between the first and last line positions.

IMAPfetchSummary gets the email header. The connection and command arguments are passed to IMAPcommand to send the command. It uses the same pattern matching strategy as described above in IMAPfetch, however this time is capturing and returning the email header information with the “BODY.PEEK[HEADER]“ command.

By separating out the IMAP operations, the IMAP Library becomes very extensible!