Mail::IMAPTalk(3pm)
πΈ NAME πΈ
Mail::IMAPTalk(3pm) β bestie, this is an IMAP client interface with lots of features π
she does the absolute most so you don't have to.
β¨ SYNOPSIS β¨
use Mail::IMAPTalk;
$IMAP = Mail::IMAPTalk->new(
Server => $IMAPServer,
Username => 'foo',
Password => 'bar',
) || die "Failed to connect/login to IMAP server";
# Append message to folder
open(my $F, 'rfc822msg.txt');
$IMAP->append($FolderName, $F) || die $@;
close($F);
# Select folder and get first unseen message
$IMAP->select($FolderName) || die $@;
$MsgId = $IMAP->search('not', 'seen')->[0];
# Get message envelope and print some details
$MsgEV = $IMAP->fetch($MsgId, 'envelope')->{$MsgId}->{envelope};
print "From: " . $MsgEv->{From};
print "To: " . $MsgEv->{To};
print "Subject: " . $MsgEv->{Subject};
# Get message body structure
$MsgBS = $IMAP->fetch($MsgId, 'bodystructure')->{$MsgId}->{bodystructure};
# Find imap part number of text part of message
$MsgTxtHash = Mail::IMAPTalk::find_message($MsgBS);
$MsgPart = $MsgTxtHash->{text}->{'IMAP-Partnum'};
# Retrieve message text body
$MsgTxt = $IMAP->fetch($MsgId, "body[$MsgPart]")->{$MsgId}->{body};
$IMAP->logout();
π DESCRIPTION π
So this module talks to an IMAP server, okay? Every single IMAP server command gets mapped to a method on this object. It's giving "one method per command" and honestly? Iconic π
Yeah there are other IMAP modules on CPAN, but this one has several advantages over the rest. Like, lowkey a glow-up:
- She parses the messier IMAP structures β envelopes and body structures β into nice, tidy Perl data structures. Chef's kiss.
- She correctly handles atoms, quoted strings and literals at any point. Some parsers in other modules aren't fully IMAP compatiable and will just randomly break with certain messages on some servers, and that's so not the vibe π
- She lets big return values (say, attachments on a message) be read straight into a file, instead of yanking the whole thing into memory. Memory said not today π
- She comes with helper functions to find the actual
text/plainortext/htmlpart of a message out of a complicated MIME structure. She can also find a list of attachments, plus CID links for HTML messages that have images attached. So thorough, so main character β¨ - She supports decoding MIME headers into Perl utf-8 strings automatically, so you never have to deal with MIME encoded headers yourself (and this one's enabled optionally, as a treat).
Now, the IMAP protocol does allow for asynchronous running of commands, but this module? She's designed to be used synchronously. Meaning: you issue a command by calling a method, and the command will block until the right response comes back. Then the method returns the parsed results from that command. Simple. Clean. No chaos π«Ά
π CLASS OVERVIEW π
The object methods have been broken up into several sections, because organization is self-care.
π Sections
π§Έ CONSTANTS
Lists the available constants the class uses.
π CONSTRUCTOR
Explains all the options available when constructing a new instance of the Mail::IMAPTalk class.
π CONNECTION CONTROL METHODS
These are the methods that control the overall IMAP connection object β stuff like logging in and logging out, how results get parsed, how folder names and message id's are treated, etc.
π IMAP FOLDER COMMAND METHODS
These are the methods to inspect, add, delete and rename IMAP folders on the server.
βοΈ IMAP MESSAGE COMMAND METHODS
These are the methods to retrieve, delete, move and add messages to/from IMAP folders.
π HELPER METHODS
Extra methods that users of this class might find useful. They generally do extra parsing on the returned structures to give you higher-level functionality. Consider it a little gift π
π INTERNAL METHODS
Internal methods the Mail::IMAPTalk object uses to get work done. They may be useful if you need to extend the class yourself. Note: internal methods will always 'die' if they hit any errors. No chill, no mercy.
π INTERNAL SOCKET FUNCTIONS
Functions used internally by the Mail::IMAPTalk object to read/write data to/from the IMAP connection socket. The class does its own buffering, so if you want to read/write to the IMAP socket, you should use these functions. Don't go rogue.
π§ INTERNAL PARSING FUNCTIONS
Functions used to parse the results the IMAP server hands back into Perl style data structures.
π¬ Method results
All methods return undef on failure. There are four main modes of failure, and we're listing them all because you deserve the truth:
- An error occurred reading/writing to a socket. Maybe the server closed it, or you're not connected to any server.
- An error occurred parsing the response of an IMAP command. This is usually only a problem if your IMAP server returns invalid data.
- An IMAP command didn't return an 'OK' response.
- The socket read operation timed out waiting for a response from the server.
In each case, some readable form of error text is placed in $@, or you can call the get_last_error() method. For commands which return responses (e.g. fetch, getacl, etc), the result is returned β see each command for details of the response result. For commands with no response but which succeed (e.g. setacl, rename, etc) the result 'ok' is generally returned. Slay π«Ά
π§Ύ Method parameters
Every method that sends data to the IMAP server (e.g. fetch(), search(), etc) has its arguments processed before they get sent. You can specify arguments in several ways, and honestly, pick your fighter:
scalar
The value is first checked and quoted if required. Values containing [\000\012\015] are turned into literals, values containing [\000-\040\{\} \%\*\"] are quoted by surrounding with a "..." pair (any " themselves are turned into \"). undef is turned into NIL
file ref
The contents of the file is sent as an IMAP literal. Note that because IMAPTalk has to know the length of the file being sent, this must be a true file reference that can be seeked and not just some stream. The entire file will be sent regardless of the current seek point.
scalar ref
The string/data in the referenced item should be sent as is, no quoting will occur, and the data won't be sent as quoted or as a literal regardless of the contents of the string/data.
array ref
Emits an opening bracket, and then each item in the array separated by a space, and finally a closing bracket. Each item in the array is processed by the same methods, so it can be a scalar, file ref, scalar ref, another array ref, etc.
hash ref
The hash reference should contain only 1 item. The key is a text string which specifies what to do with the value item of the hash.
'Literal'β The string/data in the value is sent as an IMAP literal regardless of the actual data in the string/data.'Binary'β The string/data in the value is sent as an IMAP literal8 regardless of the actual data in the string/data. (see RFC 3516)'Quote'β The string/data in the value is sent as an IMAP quoted string regardless of the actual data in the string/data.
Examples, because visuals help:
# Password is automatically quoted to "nasty%*\"passwd"
$IMAP->login("joe", 'nasty%*"passwd');
# Append $MsgTxt as string
$IMAP->append("inbox", { Literal => $MsgTxt })
# Append MSGFILE contents as new message
$IMAP->append("inbox", \*MSGFILE ])
# Append $MsgTxt as UTF-8 string. Assumes UTF8=ACCEPT is enabled.
$IMAP->append("inbox", { LiteralUtf8 => $MsgTxt })
π CONSTANTS π
These constants relate to the standard 4 states that an IMAP connection can be in. They are passed to and returned from the state() method. See RFC 3501 for more details about IMAP connection states.
Unconnectedβ Current not connected to any server.Connectedβ Connected to a server, but not logged in.Authenticatedβ Connected and logged into a server, but not current folder.Selectedβ Connected, logged in and have 'select'ed a current folder.
π CONSTRUCTOR π
Mail::IMAPTalk->new(%Options) β¨
okay bestie, this is where it all begins π«Ά β this makes you a brand new Mail::IMAPTalk object. Here's every option it ffs supports.
π Connection Options π
Serverβ the hostname or IP address you wanna connect to. You gotta give this unless you're passing theSocketoption instead, no cap.Portβ the port number on that host. Defaults to143if you don't supply it, or to993if you don't supply it andUseSSLis true. πUseSSLβ if it's true, you get anIO::Socket::SSLconnection β¨. All the otherSSL_*arguments get handed straight to theIO::Socket::SSLconstructor, iykyk.Socketβ an existing socket to use as your connection to the IMAP server. If you passSocket, you should not pass aServerorPortoption, okay?
This is lowkey perfect if you wanna build an SSL socket connection with IO::Socket::SSL and then hand that already-connected socket over to the new() call. π
It's also iconic alongside the release_socket() method described below, for reusing the same socket past the lifetime of the IMAPTalk object. There's a whole description in the release_socket() method section if you wanna know more.
β οΈ You must have write flushing enabled on any socket you pass in here, or your commands will just... sit there, not actually get sent, not get responses, and eventually time out. π You can do it with Perl'sselect()call and the$|($AUTOFLUSH) variable, like this:
my $ofh = select($Socket); $| = 1; select ($ofh);
UseBlockingβ for historical reasons π when it reads from a socket, this module puts the socket in non-blocking mode and does aselect(). If you're on an SSL socket that just does not work, so you have to setUseBlockingto true to get blocking reads instead. Periodt.Stateβ if you supply aSocketoption, you can tell it which IMAP state that socket is currently in: one of'Unconnected','Connected','Authenticated'or'Selected'. Defaults to'Connected'if you don't supply it and theSocketoption is supplied.ExpectGreetingβ if you supply it and it's true, and a socket came in via theSocketoption, it checks that the server actually sent a greeting line and reads that greeting line. πPreserveINBOXβ for historical reasons (again π), the special name"INBOX"gets rewritten asInboxbecause it looks nicer on the way out, and back again on the way in. If you want the nameINBOXpreserved on the outside, set this flag to true. πUseCompressβ if you've got theCompress::Zlibpackage installed, and the server supports compress, then setting this flag true makes compression kick in immediately after login. slay.
π Login Options π
Usernameβ the username to connect to the IMAP server as. If you don't supply it, no login is attempted and the IMAP object is left sitting in theCONNECTEDstate. If you do supply it, you also gotta supply thePasswordoption, and then a login gets attempted. If that login fails, the connection is closed andundefis returned. So if you wanna still do stuff with a connection even when the login fails, don't pass aUsernameoption at all β use theloginmethod described below instead. π«ΆPasswordβ the password you use to log into the account.AsUserβ if the server supports it, access the server as this user rather than the authenticated user.
See the "login" method for more info β¨
π¬ IMAP message/folder options π¬
Uidβ controls whether message ids are message uids or not. This is1(on) by default, because generally that's how most people wanna use it. It affects most commands that require/use/return message ids (e.g.fetch,search,sort, etc). πRootFolderβ if supplied, sets the root folder prefix. Same thing as callingset_root_folder()with the value you passed. If no value is supplied,set_root_folder()is called with no value. Peep theset_root_folder()method for more deets.Separatorβ if supplied, sets the folder name text string separator character. It gets passed as the second parameter to theset_root_folder()method.AltRootRegexpβ if supplied, it's passed along withRootFolderto theset_root_folder()method.NoLiteralPlusβ if set, this avoids ever sending literal+ non-synchronising literals. Default is off (false value). Use this when you're dealing with a buggy server that doesn't handle literal+ correctly π. Also turns off RFC7888"literal-"support.
πΈ Examples πΈ
$imap = Mail::IMAPTalk->new(
Server => 'foo.com',
Port => 143,
Username => 'joebloggs',
Password => 'mypassword',
Separator => '.',
RootFolder => 'INBOX',
) || die "Connection to foo.com failed. Reason: $@";
$imap = Mail::IMAPTalk->new(
Socket => $SSLSocket,
State => Mail::IMAPTalk::Authenticated,
Uid => 0
) || die "Could not query on existing socket. Reason: $@";
π CONNECTION CONTROL METHODS π
okay bestie, this is the part where we actually talk to the server. logging in, logging out, checking if the socket is still alive, all of it. slay. β¨
login($User, $Password, [$AsUser])β tries to log in with the username and password you hand it. π«Ά
The actual authentication might go down via the "LOGIN" command or the "AUTHENTICATE" command, depending on what the server says it supports. it's giving "we'll figure it out together."
And if you pass $AsUser? then it'll also try to log in on behalf of that user. very on-behalf-of-energy of it. π
authenticate($Sasl)β attempt to authenticate using the givenAuthen::SASLobject. that's it, that's the method.logout()β log out of the IMAP server. this usually closes the server's connection too, so likeβ¦ say your goodbyes first. π₯²state(optional $State)β set or get the current IMAP connection state. the value you return or pass in should be one of the constants:Unconnected,Connected,Authenticated,Selected. no mixing and matching, bestie. πuid(optional $UidMode)β get or set the UID status of all UID-possible IMAP commands. set it to1and every command that can take a UID gets flipped into 'UID Mode', which means any ID you send to IMAPTalk is assumed to be a UID. periodt. πcapability()β hands you back the IMAP server's capability command results. the result is a hash reference full of(lc(Capability) => 1)key/value pairs. so you can do cute stuff like:
if ($IMAP->capability()->{quota}) { ... }
to test whether the server has the QUOTA capability. and if you just want the list of capabilities? use Perl's keys function to pull a list of keys out of that hash reference. iconique. π
namespace()β returns the result of the IMAP server's namespace command.noop()β performs the standard IMAP'noop'command which, and I cannot stress this enough, does nothing. it's the main character who shows up to the party and just vibes. πenable($option)β enabled the given imap extension. (yes, we're saying "enabled", the original said it, we're keeping it, no cap.)is_open()β returns true if the current socket connection is still open (like, the socket hasn't been closed from this end or the other end because of a timeout). a little vibe check for your connection. π¦set_root_folder($RootFolder, $Separator, $AltRootRegexp)β change the root folder prefix. some IMAP servers require that all user folders/mailboxes live under a root folder prefix (current versions of cyrus, for example, use'INBOX'for personal folders and'user'for other users' folders). if you don't specify a value, it sets it to''. you might wanna use thenamespace()method to find out what roots are actually available. π
Setting this affects all commands that take a folder argument. basically, if the folder name begins with the root folder prefix, it's left as is. otherwise, the root folder prefix and separator char get prefixed onto the folder name. simple as.
The AltRootRegexp is a regexp where, if the start of the folder name matches it, then $RootFolder is not prepended. you can use this to protect other namespaces in your IMAP server. π‘οΈ
Examples:
# This is what cyrus uses
$IMAP->set_root_folder('INBOX', '.', qr/^user/);
# Selects 'Inbox' (because 'Inbox' eq 'inbox' case insensitive)
$IMAP->select('Inbox');
# Selects 'INBOX.blah'
$IMAP->select('blah');
# Selects 'INBOX.Inbox.fred'
#IMAP->select('Inbox.fred');
# Selects 'user.john' (because 'user' is alt root)
#IMAP->select('user.john'); # Selects 'user.john'
_set_separator($Separator)β checks if the given separator is the same as the one we used before. if it's not, it callsset_root_folderto recreate the settings with the newSeparator. keeping things consistent, as she should. β¨literal_handle_control(optional $FileHandle or sub { })β sets the mode for whether to read literals as file handles or as scalars.
There's three options here, bestie, listen up:
undef/0β always read literals into a scalar in memory.- filehandle β always read literals into the given filehandle. note: you'll need to make sure your
'fetch'call only returns a single literal, so you won't want to do much more than->fetch($uid, "rfc822"). - sub β whenever we parse an imap response and encounter a literal, call this sub, and if it returns a filehandle, read into that filehandle, otherwise read into a scalar in memory.
If a sub is used, when it's called it's passed two arguments:
bytesβ the size of the literal in bytes.previous atomβ if parsing a list response, the previous atom that was parsed. useful when parsing"fetch"responses since you get key/value type list in the fetch response.
Examples:
# Read rfc822 text of message 3 into file
# (note that the file will have /r/n line terminators)
open(my $fh, ">messagebody.txt");
$IMAP->literal_handle_control($fh);
$IMAP->fetch(3, 'rfc822');
$IMAP->literal_handle_control(0);
# Read the full rfc822 content into a filehandle, but the headers into a scalar
$IMAP->literal_handle_control(sub { lc $_[1] eq 'rfc822' ? scalar File::Temp::tempfile() : undef });
my $res = $IMAP->fetch(3, [ qw(rfc822 rfc822.header) ]);
$IMAP->literal_handle_control(0);
my $filehandle = $res->{3}->{rfc822};
my $hdrstxt = $res->{3}->{'rfc822.header'};
release_socket($Close)β releases IMAPTalk's ownership of the current socket it's using so it doesn't get disconnected on DESTROY. this returns the socket, and makes sure the IMAPTalk object doesn't hold a reference to it anymore and the connection state is set to"Unconnected". π«‘
Which means β and I need you to hear me on this β you can't call any methods on the IMAPTalk object anymore. she's gone, bestie. π
If the socket is being released and being closed, then $Close is set to true.
get_last_error()β returns a text string describing the last error that occurred. the receipts. π§Ύget_last_completion_response()β returns the last completion response to the tagged command.
This is either the string "ok", "no" or "bad" (always lower case). short king energy. π
get_response_code($Response)β returns the extra response data generated by a previous call. this is most often used after callingselect, which usually generates some set of the following sub-results:- permanentflags β array reference of flags which are stored permanently.
- uidvalidity β whether the current UID set is valid. see the IMAP RFC for more information on this. if this value changes, then all UIDs in the folder have been changed. π¨
- uidnext β the next UID number that will be assigned.
- exists β number of messages that exist in the folder.
- recent β number of messages that are recent in the folder.
Other possible responses are alert, newname, parse, trycreate, appenduid, etc.
The values are stored in a hash keyed on the $Response item. they're kept until either they get overwritten by a future response, or explicitly cleared via clear_response_code(). no spontaneous vanishing here. π
Examples:
# Select inbox and get list of permanent flags, uidnext and number
# of message in the folder
$IMAP->select('inbox');
my $NMessages = $IMAP->get_response_code('exists');
my $PermanentFlags = $IMAP->get_response_code('permanentflags');
my $UidNext = $IMAP->get_response_code('uidnext');
clear_response_code($Response)β clears any response code information. response code information is not normally cleared between calls, so this is your little reset button. π§Όparse_mode(ParseOption => $ParseMode)β changes how results offetchcommands are parsed. available options are:- BodyStructure β parse bodystructure into a more Perl-friendly structure. see the FETCH RESULTS section. π¦
- Envelope β parse envelopes into a more Perl-friendly structure. see the FETCH RESULTS section.
- Annotation β parse annotation (from RFC 5257) into a more Perl-friendly structure. see the FETCH RESULTS section.
- EnvelopeRaw β if parsing envelopes, create
To/Cc/BccandRaw-To/Raw-Cc/Raw-Bccentries which are array refs of 4 entries each as returned by the IMAP server. - DecodeUTF8 β if parsing envelopes, decode any MIME encoded headers into Perl UTF-8 strings.
For this to work, you must have 'used' Mail::IMAPTalk with:
use Mail::IMAPTalk qw(:utf8support ...)
set_tracing($Tracer)β lets you trace both the IMAP input and output sent to the server and returned from the server. super useful for debugging. it returns the previous value of the tracer and then sets it to the value you passed in. possible values for$Tracerare:0β disable all tracing.1β print toSTDERR.- Code ref β call the code ref for each line input and output. pass line as parameter.
- Glob ref β print to glob.
- Scalar ref β appends to the referenced scalar.
Note: literals are never passed to the tracer. π
set_unicode_folders($Unicode)β$Unicodeshould be1or0.
Sets whether folder names are expected and returned as perl unicode strings.
The default is currently 0, BUT YOU SHOULD NOT ASSUME THIS, because it will probably change in the future. the future is coming, bestie. β³
If you want to work with perl unicode strings for folder names, you should call
$ImapTalk->set_unicode_folders(1)
and IMAPTalk will automatically encode the unicode strings into IMAP-UTF7 when sending to the IMAP server, and will also decode IMAP-UTF7 back into perl unicode strings when returning results from the IMAP server. it's giving translator-friend energy. π
If you want to work with folder names in IMAP-UTF7 bytes, then call
$ImapTalk->set_unicode_folders(0)
and IMAPTalk will leave folder names as bytes when sending to and returning results from the IMAP server.
get_select_state()β returns an opaque value that represents the current selected state and folder. you can callselect,unselect, etc and then later callset_select_state(...)to return to the previous selected state. save point unlocked. πΎset_select_state($state)β restores selected state to a value returned fromget_select_state. rewind time, but make it legal. β¨
π IMAP FOLDER COMMAND METHODS π
Note: in all cases where a folder name is used, the folder name is first manipulated according to the current root folder prefix as described in set_root_folder(). don't forget it, it's literally doing work in the background. π«Ά
select($FolderName, @Opts)β performs the standard IMAP'select'command to select a folder for retrieving/moving/adding messages. if$Opts{ReadOnly}is true, the IMAPEXAMINEverb is used instead ofSELECT. choosing violenceβ¦ politely. π
Mail::IMAPTalk will cache the currently selected folder, and if you issue another ->select("XYZ") for the folder that is already selected, it will just return immediately. This can confuse code that expects to get side effects of a select call. for that case, call ->unselect (or ->unselect_or_close) first, then ->select. π
unselect()
Runs the plain ol' standard IMAP unselect command. That's it, that's the whole thing. π
unselect_or_close()
Does the IMAP unselect command if the server actually has that capability in its list, and if it doesn't? Falls back to the close command instead. Server-dependent behavior, bestie β it picks whichever one your server can handle. β¨
examine($FolderName)
Runs the standard IMAP examine command to select a folder in read only mode for retrieving messages. Literally the exact same thing as calling select($FolderName, 1). Peep select() for the deets. π
select_with_state($FolderName)
Runs the standard IMAP select command to select a folder. When it works you get back a hashref of uidnext, uidvalidity and messagecount; when it doesn't, you get an undef. No in-between, it's giving all or nothing. π
create($FolderName)
Runs the standard IMAP create command to create a new folder. New folder, who dis. β¨
delete($FolderName)
Runs the standard IMAP delete command to delete a folder. Gone. Poof. Bye. π
localdelete($FolderName)
Runs the IMAP localdelete command to delete a folder β and the way that it doesn't delete subfolders, even of INBOX, and it's always immediate. No takebacks, no chill. π«Ά
rename($OldFolderName, $NewFolderName)
Runs the standard IMAP rename command to rename a folder. Glow-up for your mailbox name. π
list($Reference, $Name)
Runs the standard IMAP list command and hands you back a list of the available folders. Window shopping, but for mailboxes. πΈ
xlist($Reference, $Name)
Runs the IMAP xlist extension command β same vibe as list but it also returns the available folders and their special-use attributes. Extra credit info, iykyk. π
id($key = $value, ...)
Runs the IMAP extension command id. β¨
lsub($Reference, $Name)
Runs the standard IMAP lsub command and returns a list of your subscribed folders. Only the ones you actually committed to. π
subscribe($FolderName)
Runs the standard IMAP subscribe command to subscribe to a folder. Hitting the follow button, basically. π₯
unsubscribe($FolderName)
Runs the standard IMAP unsubscribe command to unsubscribe from a folder. Unfollowing, but make it mail. π
check()
Runs the standard IMAP check command to checkpoint the current folder. Lil save point. πΎ
setacl($FolderName, $User, $Rights)
Runs the IMAP setacl command to set the access control list details of a folder/mailbox. Sling RFC 4314 for the full tea on the IMAP ACL extension.
$User is the username you're setting the access rights for. $Rights is either a list of absolute rights to set, OR a list prefixed by - to remove those rights, OR prefixed by + to add those rights. Choose your fighter. π
The rights, bestie, and what they actually mean:
lβ lookup (mailbox is visible to LIST/LSUB commands)rβ read (SELECT the mailbox, perform CHECK, FETCH, PARTIAL, SEARCH, COPY from mailbox)sβ keep seen/unseen information across sessions (STORE SEEN flag)wβ write (STORE flags other than SEEN and DELETED)iβ insert (perform APPEND, COPY into mailbox)pβ post (send mail to submission address for mailbox, not enforced by IMAP4 itself)kβ create mailboxes (CREATE new sub-mailboxes in any implementation-defined hierarchy, parent mailbox for the new mailbox name in RENAME)xβ delete mailbox (DELETE mailbox, old mailbox name in RENAME)tβ delete messages (set or clear\DELETEDflag via STORE, set\DELETEDflag during APPEND/COPY)eβ perform EXPUNGE and expunge as a part of CLOSEaβ administer (perform SETACL)
β οΈ drama alert: because RFC 2086 is ambiguous, some existing RFC 2086 server implementations use thecright to control the DELETE command, while others chose to use thedright for it. Check out "2.1.1. Obsolete Rights" in RFC 4314 for the full breakdown.
cβ create (CREATE new sub-mailboxes in any implementation-defined hierarchy)dβ delete (STORE DELETED flag, perform EXPUNGE)
The standard access control configurations for cyrus are:
read = "lrs"
post = "lrsp"
append = "lrsip"
write = "lrswipcd"
all = "lrswipcda"
Examples:
# Get full access for user 'joe' on his own folder
$IMAP->setacl('user.joe', 'joe', 'lrswipcda') || die "IMAP error: $@";
# Remove write, insert, post, create, delete access for user 'andrew'
$IMAP->setacl('user.joe', 'andrew', '-wipcd') || die "IMAP error: $@";
# Add lookup, read, keep unseen information for user 'paul'
$IMAP->setacl('user.joe', 'paul', '+lrs') || die "IMAP error: $@";
getacl($FolderName)
Runs the IMAP getacl command to get the access control list details of a folder/mailbox. Sling RFC 4314 for the full tea on the IMAP ACL extension. Returns an array of pairs β each pair is a username followed by the access rights for that user. Check setacl for what those rights mean. πΈ
Examples:
my $Rights = $IMAP->getacl('user.joe') || die "IMAP error : $@";
$Rights = [
'joe', 'lrs',
'andrew', 'lrswipcda'
];
$IMAP->setacl('user.joe', 'joe', 'lrswipcda') || die "IMAP error : $@";
$IMAP->setacl('user.joe', 'andrew', '-wipcd') || die "IMAP error : $@";
$IMAP->setacl('user.joe', 'paul', '+lrs') || die "IMAP error : $@";
$Rights = $IMAP->getacl('user.joe') || die "IMAP error : $@";
$Rights = [
'joe', 'lrswipcd',
'andrew', 'lrs',
'paul', 'lrs'
];
deleteacl($FolderName, $Username)
Runs the IMAP deleteacl command to nuke all access control information for the given user on the given folder. Check setacl for what those rights mean. π
Examples:
my $Rights = $IMAP->getacl('user.joe') || die "IMAP error : $@";
$Rights = [
'joe', 'lrswipcd',
'andrew', 'lrs',
'paul', 'lrs'
];
# Delete access information for user 'andrew'
$IMAP->deleteacl('user.joe', 'andrew') || die "IMAP error : $@";
$Rights = $IMAP->getacl('user.joe') || die "IMAP error : $@";
$Rights = [
'joe', 'lrswipcd',
'paul', 'lrs'
];
setquota($FolderName, $QuotaDetails)
Runs the IMAP setquota command to set the usage quota details of a folder/mailbox. Sling RFC 2087 for the deets on the IMAP quota extension. $QuotaDetails is a bracketed list of limit item/value pairs, which represent a particular type of limit and the value to set it to.
Current limits are:
STORAGEβ Sum of messages' RFC822.SIZE, in units of 1024 octetsMESSAGEβ Number of messages
Examples:
# Set maximum size of folder to 50M and 1000 messages
$IMAP->setquota('user.joe', '(storage 50000)') || die "IMAP error: $@";
$IMAP->setquota('user.john', '(messages 1000)') || die "IMAP error: $@";
# Remove quotas
$IMAP->setquota('user.joe', '()') || die "IMAP error: $@";
getquota($FolderName)
Runs the standard IMAP getquota command to get the quota details of a folder/mailbox. Sling RFC 2087 for the deets on the IMAP quota extension. Returns an array reference to quota limit triplets. Each triplet is made of: limit item, current value, maximum value. π§
Heads up bestie: this only returns the quota for a folder if it's actually had a quota set on it. It's possible a parent folder has a quota too, which affects sub-folders. Use getquotaroot to find out if that's the case. π
Examples:
my $Result = $IMAP->getquota('user.joe') || die "IMAP error: $@";
$Result = [
'STORAGE', 31, 50000,
'MESSAGE', 5, 1000
];
getquotaroot($FolderName)
Runs the IMAP getquotaroot command to get the quota details of a folder/mailbox and the possible root quota too. Sling RFC 2087 for the deets on the IMAP quota extension.
Ngl the result of this one is a little complex β unfortunately it doesn't map super easily into any structure, because there are several different responses.
Basically it's a hash reference. The 'quotaroot' item is the response that lists the root quotas applying to the given folder. The first item is the folder name, and the remaining items are the quota root items. There's then a hash item for each quota root item. Honestly it's easiest to just look at the example below. π
Examples:
my $Result = $IMAP->getquotaroot('user.joe.blah') || die "IMAP error: $@";
$Result = {
'quotaroot' => [
'user.joe.blah', 'user.joe', ''
],
'user.joe' => [
'STORAGE', 31, 50000,
'MESSAGES', 5, 1000
],
'' => [
'MESSAGES', 3498, 100000
]
};
message_count($FolderName)
Returns the number of messages in a folder. Wanna know more about what's in there? Peep status() for extra info about the messages in a folder. β¨
status($FolderName, $StatusList)
Runs the standard IMAP status command to retrieve status information about a folder/mailbox.
$StatusList is a bracketed list of folder items to get the status of. It can contain: messages, recent, uidnext, uidvalidity, unseen.
The return value is a hash reference of lc(status-item) => value. π
Examples:
my $Res = $IMAP->status('inbox', '(MESSAGES UNSEEN)');
$Res = {
'messages' => 8,
'unseen' => 2
};
multistatus($StatusList, @FolderNames)
Performs many IMAP status commands across a whole list of folders. It fires all the commands off at once and waits for the responses, which is how it speeds up latency issues. Efficiency queen. π
Returns a hash ref of folder name => status results.
If an error occurs, the annotation result is a scalar ref to the completion response string (eg 'bad', 'no', etc). π
getannotation($FolderName, $Entry, $Attribute)
Runs the IMAP getannotation command to get the annotation(s) for a mailbox. Sling the imap-annotatemore extension for the deets. π
Examples:
my $Result = $IMAP->getannotation('user.joe.blah', '/*' '*') || die "IMAP error: $@";
$Result = {
'user.joe.blah' => {
'/vendor/cmu/cyrus-imapd/size' => {
'size.shared' => '5',
'content-type.shared' => 'text/plain',
'value.shared' => '19261'
},
'/vendor/cmu/cyrus-imapd/lastupdate' => {
'size.shared' => '26',
'content-type.shared' => 'text/plain',
'value.shared' => '26-Mar-2004 13:31:56 -0800'
},
'/vendor/cmu/cyrus-imapd/partition' => {
'size.shared' => '7',
'content-type.shared' => 'text/plain',
'value.shared' => 'default'
}
}
};
getmetadata($FolderName, [ \%Options ], @Entries)
Runs the IMAP getmetadata command to get the metadata items for a mailbox. Sling RFC 5464 for the deets. π
If $Options is passed, it's a hashref of options you wanna set. π
And if foldername is the empty string '', you get the server annotations instead of some folder's. Sneaky but useful.
Examples, bestie, watch closely:
my $Result = $IMAP->getmetadata('user.joe.blah', {depth => 'infinity'}, '/shared') || die "IMAP error: $@";
$Result = {
'user.joe.blah' => {
'/shared/vendor/cmu/cyrus-imapd/size' => '19261',
'/shared/vendor/cmu/cyrus-imapd/lastupdate' => '26-Mar-2004 13:31:56 -0800',
'/shared/vendor/cmu/cyrus-imapd/partition' => 'default',
}
};
my $Result = $IMAP->getmetadata('', "/shared/comment");
$Result => {
'' => {
'/shared/comment' => "Shared comment",
}
};
multigetmetadata(@Entries, @FolderNames)
Bestie this one is the β¨batchβ¨ version. It runs many IMAP 'getmetadata' commands across a whole list of folders β sends all of them at once and then waits for the responses. The whole point? It speeds up latency issues. No more waiting around one by one like it's 2004. π
Returns a hash ref of folder name => metadata results.
If an error happens, the annotation result is a scalar ref to the completion response string (like 'bad', 'no', etc). So heads up, that slot isn't always shaped like the rest. π«Ά
setannotation($FolderName, $Entry, [ $Attribute, $Value ])
Runs the IMAP 'setannotation' command to get the annotation(s) for a mailbox. Go read the imap-annotatemore extension for the deets, iykyk. π
Examples:
my $Result = $IMAP->setannotation('user.joe.blah', '/comment', [ 'value.priv' 'A comment' ])
|| die "IMAP error: $@";
setmetadata($FolderName, $Name, $Value, $Name2, $Value2)
Runs the IMAP 'setmetadata' command. See RFC 5464 for the full tea. π΅
Examples:
my $Result = $IMAP->setmetadata('user.joe.blah', '/comment', 'A comment')
|| die "IMAP error: $@";
close()
Runs the standard IMAP 'close' command β it expunges the deleted messages from the current folder and takes you back to the Authenticated state. Very "and we're done here, bye." π¦
Heads up: expunged means gone gone, okay? That's your deleted messages, actually deleted, fr fr. π
idle(&Callback, [ $Timeout ])
Performs an IMAP idle call. It calls the callback you give it for each IDLE event it receives. π
How the callback talks to idle:
- If the callback returns
0, the idle keeps going. - If the callback returns
1, the idle is finished and this call returns.
If no timeout is passed, it'll just keep idling until your callback returns 1 or the server disconnects. Committed. Loyal. π
If a timeout is passed (and yes, a 0 timeout counts), the call will return if no events show up within that time. It returns the result of the DONE command, and sets $Self->get_response_code('timeout') to true. β¨
If the server closes the connection with a "bye" response, it returns undef and $@ =~ /bye/ will be true, with the rest of the bye line following after it. Rude of the server, but at least you'll know.
π IMAP MESSAGE COMMAND METHODS π
fetch([ %ParseMode ], $MessageIds, $MessageItems)
Runs the standard IMAP 'fetch' command to retrieve the specified message items from the specified message IDs. This is the big one, bestie. π
That first parameter can be an optional hash reference that overrides particular parse mode parameters just for this fetch. See "parse_mode" for possible keys. πΈ
$MessageIds can be one of two forms:
- A text string with a comma separated list of message IDs or message ranges separated by colons. A
'*'represents the highest message number.
Examples:
'1'- first message'1,2,5''1:*'- all messages'1,3:*'- all but message 2
Note that , separated lists and : separated ranges can be mixed, but to make sure a certain hack works, if a '*' is used, it must be the last character in the string. No cap. π¨
- An array reference with a list of message IDs or ranges. The array contents are
"join(',', ...)"ed together.
Note: If the uid() state has been set to true, then all message IDs must be message UIDs. Don't mix it up, bestie. π
$MessageItems can be one of, or a bracketed list of:
uidflagsinternaldateenvelopebodystructurebodybody[section]<partial>body.peek[section]<partial>rfc822rfc822.headerrfc822.sizerfc822.textfastallfull
It would be a good idea to see RFC 3501 for what all these mean. Not me just trusting you to read the RFC. π
Examples:
my $Res = $IMAP->fetch('1:*', 'rfc822.size');
my $Res = $IMAP->fetch([1,2,3], '(bodystructure envelope)');
Return results:
The results the IMAP server sends back get parsed into a Perl structure. See the section FETCH RESULTS for all the interesting details. π
Note that messages can disappear on you β so you may not get back all the entries you expect in the hash. Ghosting, but from the server. π
There is one piece of magic. If your request is for a single uid, (eg "123"), and no data is returned, we return undef, because it's easier to handle as an error condition. That's it, that's the magic. β¨
copy($MsgIds, $ToFolder)
Runs the standard IMAP copy command to copy a set of messages from one folder to another. Simple, clean, slay. π
append($FolderName, optional $MsgFlags, optional $MsgDate, $MessageData)
Runs the standard IMAP append command to append a new message into a folder. π
The $MessageData you wanna append can either be a Perl scalar holding the data, or a file handle to read the data from. In each case, the data must be in proper RFC 822 format with \r\n line terminators. Do not skip the \r\n, I'm begging. π
Any optional fields you don't need should be removed, not left blank. Blank β absent, bestie.
Examples:
# msg.txt should have \r\n line terminators
open(F, "msg.txt");
$IMAP->append('inbox', \*F);
my $MsgTxt =<<MSG;
From: blah\@xyz.com
To: whoever\@whereever.com
...
MSG
$MsgTxt =~ s/\n/\015\012/g;
$IMAP->append('inbox', { Literal => $MsgTxt });
search($MsgIdSet, @SearchCriteria)
Runs the standard IMAP search command. The result is an array reference to a list of message IDs (or UIDs if you're in Uid mode) of the messages that are in $MsgIdSet and also meet the search criteria. π
@SearchCriteria is a list of search specifications. For example, to look for ASCII messages bigger than 2000 bytes you'd set the list to be:
my @SearchCriteria = ('CHARSET', 'US-ASCII', 'LARGER', '2000');
Examples:
my $Res = $IMAP->search('1:*', 'NOT', 'DELETED');
$Res = [ 1, 2, 5 ];
store($MsgIdSet, $FlagOperation, $Flags)
Runs the standard IMAP store command. It changes the flags associated with a set of messages. π·οΈ
Examples:
$IMAP->store('1:*', '+flags', '(\\deleted)');
$IMAP->store('1:*', '-flags.silent', '(\\read)');
expunge()
Runs the standard IMAP expunge command. This actually deletes any messages marked as deleted. Like, for real for real. No undo. π
Bestie, I'm serious: "marked as deleted" becomes "deleted" right here. Blink and they're gone. π
uidexpunge($MsgIdSet)
Runs the IMAP uid expunge command as per RFC 2359. Fancy. β¨
sort($SortField, $CharSet, @SearchCriteria)
Runs the extension IMAP sort command. The result is an array reference to a list of message IDs (or UIDs if in Uid mode) in sorted order. So satisfying when it lines up. π
It would probably be a good idea to look at the sort RFC 5256 details at somewhere like: http://www.ietf.org/rfc/rfc5256.txt
Examples:
my $Res = $IMAP->sort('(subject)', 'US-ASCII', 'NOT', 'DELETED');
$Res = [ 5, 2, 3, 1, 4 ];
thread($ThreadType, $CharSet, @SearchCriteria)
Runs the extension IMAP thread command. $ThreadType should be one of 'REFERENCES' or 'ORDEREDSUBJECT'. You should check the capability() of the server to see if it supports one or both of these. Don't just assume, bestie β vibe check the server first. π§
Examples
my $Res = $IMAP->thread('REFERENCES', 'US-ASCII', 'NOT', 'DELETED');
$Res = [ [10, 15, 20], [11], [ [ 12, 16 ], [13, 17] ];
fetch_flags($MessageIds)
Runs an IMAP 'fetch flags' command to retrieve the specified flags for the specified messages. π©
This is just a special fast path version of "fetch". Same energy, zoomed in.
fetch_meta($MessageIds, @MetaItems)
Runs an IMAP 'fetch' command to retrieve the specified meta items. These must be simple items that return only atoms (eg no flags, bodystructure, body, envelope, etc). Keep it lowkey, okay? πΈ
This is just a special fast path version of "fetch".
π IMAP CYRUS EXTENSION METHODS π
Methods provided by extensions to the cyrus IMAP server. Cyrus staying booked and busy, we love to see it. π
Note: In all cases where a folder name is used, the folder name is first manipulated according to the current root folder prefix as described in set_root_folder(). So go read that one if this surprises you. π
xrunannotator($MessageIds)
Run the xannotator command on the given message id's. That's the whole thing. Short, sweet, iconic. β¨
π IMAP HELPER FUNCTIONS π
These are the little besties that do the fiddly work for you once you've already pulled down a bodystructure. No cap, they save you so much pain.
get_body_part($BodyStruct, $PartNum)
Okay so picture this: you fetched a bodystructure, and now you want one specific little sub-part out of it. This helper does exactly that π . You hand it a top-level body structure plus a part number, and it hands you back a reference to the bodystructure sub-part that that part number points at.
Examples, because seeing it is believing:
# Fetch body structure
my $FR = $IMAP->fetch(1, 'bodystructure');
my $BS = $FR->{1}->{bodystructure};
# Parse further to find particular sub part
my $P12 = $IMAP->get_body_part($BS, '1.2');
$P12->{'IMAP->Partnum'} eq '1.2' || die "Unexpected IMAP part number";
find_message($BodyStruct)
Another glow-up helper for parsing fetched bodystructures β¨. You give it a bodystructure, it gives you back a hash reference, and inside that hash lives all of this:
text => $best_text_part
html => $best_html_part (optional)
textlist => [ ... text/html (if no alt text bits)/image (if inline) parts ... ]
htmllist => [ ... text (if no alt html bits)/html/image (if inline) parts ... ]
att => [ {
bs => $part, text => 0/1, html => 0/1, msg => 1/0,
}, { ... }, ... ]
Okay so like, say you've got a message with text and html pages, and then some list software manager came along and stapled a header/footer onto it. It ends up looking like this whole situation:
multipart/mixed
text/plain, cd=inline - A
multipart/mixed
multipart/alternative
multipart/mixed
text/plain, cd=inline - B
image/jpeg, cd=inline - C
text/plain, cd=inline - D
multipart/related
text/html - E
image/jpeg - F
image/jpeg, cd=attachment - G
application/x-excel - H
message/rfc822 - J
text/plain, cd=inline - K
In that case, the list items you'd get back are:
text => B
html => E
textlist => [ A, B, C, D, K ]
htmllist => [ A, E, K ]
att => [
{ bs => C, text => 1, html => 1 },
{ bs => F, text => 1, html => 0 },
{ bs => G, text => 1, html => 1 },
{ bs => H, text => 1, html => 1 },
{ bs => J, text => 0, html => 0, msg => 1 },
]
Examples, bestie:
# Fetch body structure
my $FR = $IMAP->fetch(1, 'bodystructure');
my $BS = $FR->{1}->{bodystructure};
# Parse further to find message components
my $MC = $IMAP->find_message($BS);
$MC = { 'plain' => ... text body struct ref part ...,
'html' => ... html body struct ref part (if present) ...
'htmllist' => [ ... html body struct ref parts (if present) ... ] };
# Now get the text part of the message
my $MT = $IMAP->fetch(1, 'body[' . $MC->{text}->{'IMAP-Part'} . ']');
generate_cid( $Token, $PartBS )
This method generates a ContentID based on $Token and $PartBS.
And the vibe here is determinism. The same value should always be returned for a given $Token and $PartBS, periodt.
build_cid_map($BodyStruct, [ $IMAP, $Uid, $GenCidToken ])
Another helper for digging deeper into fetched bodystructures π¦. It recursively parses the bodystructure and returns a hash of Content-ID to bodystruct part references. Super useful when you're trying to figure out CID links from an HTML message.
And here's the tea: if you pass a Mail::IMAPTalk object as the second parameter, the CID map it builds may be even more detailed π. Turns out some stupid versions of exchange stick the details in the Content-Location header instead of the Content-Type header. If that's the case, this will try and fetch the header from the message.
Examples:
# Fetch body structure
my $FR = $IMAP->fetch(1, 'bodystructure');
my $BS = $FR->{1}->{bodystructure};
# Parse further to get CID links
my $CL = build_cid_map($BS);
$CL = { '2958293123' => ... ref to body part ..., ... };
obliterate($CyrusName)
Give it a username (optionally username\@domain) and it will immediately delete all messages belonging to that user π. Uses LOCALDELETE. Quite FastMail Patchd Cyrus specific, iykyk. It's fast, it's brutal, it does not ask twice β so pls be careful with this one.
make_sequence_set($MessageIds)
my $SetSeq = $IMAP->make_sequence_set($Ids);
This method returns a reference to a "sequence-set" string suitable for use in IMAP commands π₯. And the reason it returns a reference instead of a plain string? So the result can be passed directly to IMAP-generating methods and the sequence set won't get quoted π. This quoting is especially problematic when quoting a range like "1:*".
π«Ά IMAP CALLBACKS π«Ά
By default, these methods do nothing. But you can dervice from Mail::IMAPTalk and override these methods to trap any things you want to catch β little hooks to do your own thing when stuff happens β¨.
cb_switch_folder($CurrentFolder, $NewFolder)
Called when the currently selected folder is being changed. Like, select is called and it's definitely a different folder being selected, or the unselect methods get called. That kind of moment π«.
cb_folder_changed($Folder)
Called when a command changes the contents of a folder (eg copy, append, etc). $Folder is the name of the folder that's changing. Not me spying on folder drama π.
π FETCH RESULTS π
okay bestie, this is the one. the fetch operation is probably the thing you'll be doing constantly with an IMAP connection. it's how you grab info about a message or a whole lil squad of messages β header fields, flags, chunks of the message body, all of it. β¨
Mail::IMAPTalk will always parse the results of a fetch call into a Perl-ish structure for you, no cap. though heads up: bodystructure, envelope and uid responses might get extra parsing depending on the parse_mode state and the uid state (more on that below π).
so like, picture this: here's an example where C is what the client sends and S is what the server claps back with.
C: a100 fetch 5,6 (flags rfc822.size uid)
S: * 1 fetch (UID 1952 FLAGS (\recent \seen) RFC822.SIZE 1150)
S: * 2 fetch (UID 1958 FLAGS (\recent) RFC822.SIZE 110)
S: a100 OK Completed
the fetch command gets sent by just... calling this:
my $Res = $IMAP->fetch('1:*', '(flags rfc822.size uid)');
and the result you get back? iconic. it looks like this:
$Res = {
1 => {
'uid' => 1952,
'flags' => [ '\\recent', '\\seen' ],
'rfc822.size' => 1150
},
2 => {
'uid' => 1958,
'flags' => [ '\\recent' ],
'rfc822.size' => 110
}
};
a couple things to clock here π:
- the message IDs got turned into a hash going from message ID β‘οΈ fetch response result.
- the response items (like
uid,flags, etc) got turned into a hash for each message, and they also got lowercased. cute. - any other bracketed
(...)lists became array references. slay.
and ngl, that's basically how all fetch responses get parsed. but there's one major plot twist when the IMAP connection is in uid mode. π in that case, the message IDs in the main hash become message UIDs instead, and the uid entry inside the inner hash gets yeeted. so the example above would become:
my $Res = $IMAP->fetch('1:*', '(flags rfc822.size)');
$Res = {
1952 => {
'flags' => [ '\\recent', '\\seen' ],
'rfc822.size' => 1150
},
1958 => {
'flags' => [ '\\recent' ],
'rfc822.size' => 110
}
};
π¦ Bodystructure π¦
when we're dealing with messages, we gotta understand the MIME structure of the thing so we can figure out what's the text body, what's an attachment, etc. that's literally where the bodystructure item from an IMAP server comes in. π
C: a101 fetch 1 (bodystructure)
S: * 1 fetch (BODYSTRUCTURE ("TEXT" "PLAIN" NIL NIL NIL "QUOTED-PRINTABLE" 255 11 NIL ("INLINE" NIL) NIL))
S: a101 OK Completed
the fetch command gets sent by calling:
my $Res = $IMAP->fetch(1, 'bodystructure');
and yeah, as expected, the resulting response looks like this:
$Res = {
1 => {
'bodystructure' => [
'TEXT', 'PLAIN', undef, undef, undef, 'QUOTED-PRINTABLE',
255, 11, UNDEF, [ 'INLINE', undef ], undef
]
}
};
but bestie, if you set the parse_mode(BodyStructure => 1), then the result becomes:
$Res = {
'1' => {
'bodystructure' => {
'MIME-Type' => 'text',
'MIME-Subtype' => 'plain',
'MIME-TxtType' => 'text/plain',
'Content-Type' => {},
'Content-ID' => undef,
'Content-Description' => undef,
'Content-Transfer-Encoding' => 'QUOTED-PRINTABLE',
'Size' => '3569',
'Lines' => '94',
'Content-MD5' => undef,
'Disposition-Type' => 'inline',
'Content-Disposition' => {},
'Content-Language' => undef,
'Remainder' => [],
'IMAP-Partnum' => ''
}
}
};
a couple points to note here π:
- all the positional fields from the bodystructure list response got turned into nicely named key/value hash items. glow up.
- the MIME-Type and MIME-Subtype fields got lowercased.
- an
IMAP-Partnumitem got added. you can pass that field's value as thesectionnumber of an IMAP body fetch call to retrieve the text of that IMAP section. v useful.
so in general, these items are defined for all body structures:
MIME-TypeMIME-SubtypeContent-TypeDisposition-TypeContent-DispositionContent-Language
for all bodystructures EXCEPT the ones that have a MIME-Type of multipart, these are defined:
Content-IDContent-DescriptionContent-Transfer-EncodingSizeContent-MD5RemainderIMAP-Partnum
for bodystructures where MIME-Type is text, there's an extra item called Lines. periodt.
for bodystructures where MIME-Type is message and MIME-Subtype is rfc822, the extra items Message-Envelope, Message-Bodystructure and Message-Lines are defined. the Message-Bodystructure item is itself a reference to a whole entire bodystructure hash with all the format info of the contained message. and Message-Envelope is a hash structure holding the message header info β peep the Envelope entry below π
for bodystructures where MIME-Type is multipart, an extra item called MIME-Subparts is defined. that MIME-Subparts item is an array reference, and each item in it is a reference to an entire bodystructure hash with all the format info of each MIME sub-part. yes, it's hashes in arrays in hashes, we love a nesting moment πͺ
if you need to keep processing, you can use the find_message() function β it'll analyse the body structure and find which part corresponds to the main text/html message parts worth displaying. you can also use find_cid_parts() to find CID links in an html message. π«Ά
π Envelope π
the envelope structure has most of the addressing header fields from an email message. here's an example envelope fetch (the IMAP server's response has been tidied up here for your viewing pleasure):
C: a102 fetch 1 (envelope)
S: * 1 FETCH (ENVELOPE
("Tue, 7 Nov 2000 08:31:21 UT" # Date
"FW: another question" # Subject
(("John B" NIL "jb" "abc.com")) # From
(("John B" NIL "jb" "abc.com")) # Sender
(("John B" NIL "jb" "abc.com")) # Reply-To
(("Bob H" NIL "bh" "xyz.com") # To
("K Jones" NIL "kj" "lmn.com"))
NIL # Cc
NIL # Bcc
NIL # In-Reply-To
NIL) # Message-ID
)
S: a102 OK Completed
the fetch command gets sent by calling:
my $Res = $IMAP->fetch(1, 'envelope');
and you already get the idea of what the resultant response would be, i fear. π again, if you change parse_mode(Envelope => 1), you get a neat structure like this:
$Res = {
'1' => {
'envelope' => {
'Date' => 'Tue, 7 Nov 2000 08:31:21 UT',
'Subject' => 'FW: another question',
'From' => '"John B" <jb@abc.com>',
'Sender' => '"John B" <jb@abc.com>',
'Reply-To' => '"John B" <jb@abc.com>',
'To' => '"Bob H" <bh@xyz.com>, "K Jones" <kj@lmn.com>',
'Cc' => '',
'Bcc' => '',
'In-Reply-To' => undef,
'Message-ID' => undef,
'From-Raw' => [ [ 'John B', undef, 'jb', 'abc.com' ] ],
'Sender-Raw' => [ [ 'John B', undef, 'jb', 'abc.com' ] ],
'Reply-To-Raw' => [ [ 'John B', undef, 'jb', 'abc.com' ] ],
'To-Raw' => [
[ 'Bob H', undef, 'bh', 'xyz.com' ],
[ 'K Jones', undef, 'kj', 'lmn.com' ],
],
'Cc-Raw' => [],
'Bcc-Raw' => [],
}
}
};
all the fields here come straight from the email headers, fr fr. see RFC 822 for more deets. π
πΈ Annotation πΈ
if the server supports RFC 5257 (the ANNOTATE Extension), then you can fetch per-message annotations. π€©
annotation responses would normally come back as a nested set of arrays. but it's so much easier to access the results as a nested set of hashes, so the results get converted like that when the Annotation parse mode is enabled β and bestie, that's on by default. king behavior π
here's part of an example straight from the RFC:
S: * 12 FETCH (UID 1123 ANNOTATION
(/comment (value.priv "My comment"
size.priv "10")
/altsubject (value.priv "Rhinoceroses!"
size.priv "13")
so the fetch command:
my $Res = $IMAP->fetch(1123, 'annotation', [ '/*', [ 'value.priv', 'size.priv' ] ]);
would have the result:
$Res = {
'1123' => {
'annotation' => {
'/comment' => {
'value.priv' => 'My comment',
'size.priv => 10
},
'/altsubject' => {
'value.priv' => '"Rhinoceroses',
'size.priv => 13
}
}
}
}
π«§ INTERNAL METHODS π«§
bestie these are the β¨backstageβ¨ methods, the ones doing all the heavy lifting in the wings. You probably don't need to call these yourself, but like... here's what they're up to π
_imap_cmd($Command, $IsUidCmd, $RespItems, @Args)β runs a standard IMAP command, no biggie, just the whole engine of this module fr fr.
π Method arguments
$Commandβ the text string of the command you're throwing at the IMAP server (think'select','search', etc).$IsUidCmdβ1if this command is about message ids and can get theUIDprefix slapped on it,0if not.$RespItemsβ the responses you're hunting for from the command (like'list','fetch', etc). Commands that return results usually hand them back untagged, and here's the vibe in action:
C123 uid fetch 1:* (flags)
* 1 FETCH (FLAGS (\Seen) UID 1)
* 2 FETCH (FLAGS (\Seen) UID 2)
C123 OK Completed
So between firing off the command and seeing the 'OK Completed' response, we have to scoop up all those untagged 'FETCH' response items β which is exactly why you'd pass 'fetch' (always lowercase, always π) as $RespItems to extract them.
And plot twist: this can also be a hash ref of callback functions! Peep _parse_response for more examples π
@Argsβ literally any extra arguments you wanna pass to the command. Go wild.
_send_cmd($Self, $Cmd, @InArgs)β helper method that_imap_cmdleans on to actually build (and quote, where quoting is needed) the command arguments, then yeet the actual command out there._send_data($Self, $Opts, $Buffer, @Args)β another helper used by_send_cmd, doing the exact same job of building (and quoting where necessary) command arguments and sending the actual command._parse_response($Self, $RespItems, [ \%ParseMode ])β the helper_imap_cmdcalls after the command goes out. This one grabs data from the IMAP socket, parses it into Perl structures, and hands you back the results. π
$RespItems is either a string β the untagged response(s) to find and return β or, for custom processing, a hash ref.
If it's a hash ref, every key is an untagged response to look out for, and every value is a callback function fired for that corresponding untagged response.
Each callback gets called with 2 or 3 arguments: the untagged response string, the remainder of the line parsed into an array ref, and β for fetch type responses β the id gets passed as that third argument. π
One more piece of magic: if you pass a 'responseitem' key, the value has to be a string, and that string is the untagged response returned from the function. No cap.
_require_capability($Self, $Capability)β helper that checks the server actually has a certain capability. If it doesn't? It sets the internal last error,$@, and returnsundef. Rude but fair π_trace($Self, $Line)β helper that outputs any tracing data. Just tracing things, being nosy π΅οΈ_is_current_folder($Self, $FolderName)β returns true if a folder is currently selected and that folder is$FolderName. Simple, iconic, does what it says.
π INTERNAL SOCKET FUNCTIONS π
_next_atom($Self)β returns the next atom from the current line. It uses$Self->{ReadLine}for line data, or if that's undef, it fills it with a fresh new line of data straight from the IMAP connection socket and then starts processing. β¨
So if the next atom is:
- An unquoted string β it just returns the string, easy peasy.
- A quoted string β it unquotes it, swaps any
\"occurrences into", and returns the string. - A literal (like
{NBytes}\r\n) β it reads the number of bytes of data in the literal into a scalar or file (depends on"literal_handle_control"btw, iykyk). - A bracketed structure β it reads all the sub-atoms inside the structure and returns an array reference with all of them.
In every single case, after parsing the atom it strips any trailing space separator, then puts the remainder of the line back into $Self->{ReadLine} so it's ready for the next _next_atom() call. Recycling queen β»οΈ
_next_simple_atom($Self)β the faster version of_next_atom()for known simple cases. Speedrun edition πβοΈ_remaining_atoms($Self)β returns all the remaining atoms for the current line in the read line buffer as an array reference. Leaves$Self->{ReadLine}eq''. Check_next_atom()for the deets._remaining_line($Self)β returns the remaining data in the read line buffer ($Self->{ReadLine}) as a scalar string/data value._fill_imap_read_buffer($Self)β waits until data is available on the IMAP connection socket (or a timeout happens, whichever comes first π). Reads the data into the internal buffer$Self->{ReadBuf}. After that you can use_imap_socket_read_line(),_imap_socket_read_bytes()or_copy_imap_socket_to_handle()to read data from the buffer in lines or bytes at a time._imap_socket_read_line($Self)β reads a\r\nterminated list from the buffered IMAP connection socket._imap_socket_read_bytes($Self, $NBytes)β reads a certain number of bytes from the buffered IMAP connection socket._imap_socket_out($Self, $Data)β writes the data in$Datato the IMAP connection socket. Sending it fr π_copy_handle_to_imapsocket($Self, $InHandle)β copies a given number of bytes from a file handle to the IMAP connection._copy_imap_socket_to_handle($Self, $OutHandle, $NBytes)β copies data from the IMAP socket to a file handle. This one's different from_copy_handle_to_imap_socket()because we internally buffer the IMAP socket, so we can't just use it to copy straight from the socket handle β we have to copy the contents of our buffer first. π«Ά
β οΈ The number of bytes specified must be available on the IMAP socket. If the function runs out of data it will literally 'die' with an error. Dramatic much? π
_quote($String)β returns an IMAP quoted version of a string. It plops"..."around the string and replaces any internal"with\". Wrapping it up cute π
π§ INTERNAL PARSING FUNCTIONS π§
_parse_list_to_hash($ListRef, $Recursive)β parses an array reference list of ($Key,$Value) pairs into a hash. Makes sure all the keys are lower cased (lc) first. lowercase supremacy π_fix_folder_name($FolderName, %Opts)β changes a folder name based on the current root folder prefix, as set with theset_root_prefix()call.
Wildcard => 1 = a folder name with % or * is left alone
NoEncoding => 1 = don't do modified utf-7 encoding, leave as unicode
_fix_folder_encoding($FolderName)β encodes folder name using IMAP-UTF-7._unfix_folder_name($FolderName)β un-changes a folder name based on the current root folder prefix as set with theset_root_prefix()call. Undoing the thing, reverse UNO π_parse_email_address($EmailAddressList)β converts a list of IMAP email address structures (as parsed and returned from an IMAP fetch (envelope) call) into a single RFC 822 email string (like"Person 1 Name" <ename@ecorp.com>, "Person 2 Name" <...>, etc) and finally returns it to you. This one's used to parse an envelope structure returned from a fetch call. Check the 'FETCH RESULTS' docs section for more. π¬_parse_envelope($Envelope, $IncludeRaw, $DecodeUTF8)β converts an IMAP envelope structure (as parsed and returned from an IMAP fetch (envelope) call) into a convenient hash structure.
If $IncludeRaw is true, it includes the XXX-Raw fields β otherwise those get left out.
If $DecodeUTF8 is true, then it checks whether the fields contain any quoted-printable chars and decodes them to a Perl UTF8 string if they do.
See the 'FETCH RESULTS' docs section for more info. π
_parse_bodystructure($BodyStructure, $IncludeRaw, $DecodeUTF8, $PartNum)β parses a standard IMAP body structure and turns it into a Perl friendly nested hash structure. This routine is recursive, so do not pass a value for$PartNumwhen you're calling it for the top level bodystructure item, okay? Also heads up: this routine destroys the array reference structure passed in as$BodyStructure. Gone. Destroyed. π
See the 'FETCH RESULTS' docs section for more info.
_parse_fetch_annotation($AnnotateItem)β takes the result from a single IMAP annotation item and turns it into a Perl friendly structure. See the 'FETCH RESULTS' docs section for more info._parse_fetch_result($FetchResult)β takes the result from a single IMAP fetch response line and parses it into a Perl friendly structure. See the 'FETCH RESULTS' docs section for more info._parse_header_result($HeaderResults, $Value, $FetchResult)β takes abody[header.fields (xyz)]fetch response and parses out the header fields and values. πΈ_decode_utf8($Value)β decodes the passed quoted printable value into a Perl Unicode string._expand_sequence(@Sequences)β expands a list of IMAP id sequences into a full list of ids. Blowing it up into the full roster β¨
πͺ PERL METHODS πͺ
DESTROY()β called by Perl when this object gets destroyed. Logs out of the IMAP server if it's still connected. The farewell arc π
π GMAIL, OFFICE 365, YAHOO and OAUTHBEARER/XOAUTH2 π
GMail, Office 365 and Yahoo are all moving to OAUTH authentication using the XOAUTH2 SASL mechanism. Authen::SASL 2.1800 and above support OAUTHBEARER and XOAUTH2 β so if you've got that version or greater, you can just use this:
my $sasl = Authen::SASL->new(
mechanism => 'XOAUTH2' / 'OAUTHBEARER',
callback => {
user => $username,
pass => $token,
}
);
$login_res = $connection->authenticate($sasl);
If you're on an older version, no worries bestie, you can use this simple module instead:
package Authen::SASL::Perl::XOAUTH2;
use strict;
use warnings
use parent qw(Authen::SASL::Perl);
sub _order { 1 }
my @FLAGS;
sub _secflags { @FLAGS }
sub mechanism { 'XOAUTH2' }
sub client_start {
my $self = shift;
$self->{error} = undef;
$self->{need_step} = 0;
my ($user, $pass) = map {
my $v = $self->_call($_);
defined($v) ? $v : ''
} qw(user pass);
return "user=$user\001auth=Bearer $pass\001\001";
}
1;
And then authenticate using:
my $sasl = Authen::SASL->new(
mechanism => 'XOAUTH2',
callback => {
user => $username,
pass => $token,
}
);
$login_res = $connection->authenticate($sasl);
π SEE ALSO π
Net::IMAP, Mail::IMAPClient, IMAP::Admin, RFC 3501
Latest news/details can also be found at:
http://cpan.robm.fastmail.fm/mailimaptalk/
Available on github at:
<https://github.com/robmueller/mail-imaptalk/>
π AUTHOR π
Rob Mueller <cpan@robm.fastmail.fm>. Thanks to Jeremy Howard <j+daemonize@howard.fm> for socket code, support and documentation setup. Legends, both of them π«Άπ
π COPYRIGHT AND LICENSE π
Okay bestie, the legal tea β β and honestly it's the good kind of legal tea, the kind where nobody's getting sued:
Copyright (C) 2003-2016 by FastMail Pty Ltd
That's it, that's the whole ownership sitch. FastMail held it down from 2003 all the way to 2016. π
Now the part everyone actually cares about, aka can I use this in my project? and the answer is a resounding slay, yes:
This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself.
Same terms as Perl itself. That's the whole license. No weird hoops, no "email us for permission," no fine print written by a lawyer at 3am. If it works for Perl, it works for this. π«Ά
π Real talk though: it says you can redistribute and/or modify it β it does not say anything else about warranty, liability, or anything beyond those terms. So don't go reading extra promises into it that aren't there. The original means what it means. Periodt.
πΈ so anyway, that's the whole page πΈ
We made it. All nine parts. You now know Mail::IMAPTalk better than it knows itself, and honestly? Iconic behaviour. π
β¨
Little final vibe check: this page is from the perl v5.40.1 distribution of the docs, dated 2025-10-04, and it's the manual page for Mail::IMAPTalk(3pm) β so if you're ever lost in a terminal at 2am wondering which page you're reading, that's your answer, bestie. ππ¦
Go forth and speak IMAP fluently. I'm so proud of you. ππ
π¦ where this page comes from
- ubuntu resolute: libmail-imaptalk-perl 4.07-1 Β· Artistic-1.0-Perl OR GPL-1.0-or-later
- ubuntu stonking: libmail-imaptalk-perl 4.07-1 Β· Artistic-1.0-Perl OR GPL-1.0-or-later
- π girlified by deepseek-ai/DeepSeek-V4.1-Flash on : a language model's rewrite of the original, under its terms