The London Perl and Raku Workshop takes place on 26th Oct 2024. If your company depends on Perl, please consider sponsoring and/or attending.


Mail::MsgStore - Complete mail client back end.


  use Mail::MsgStore;

  # set mailroot 
  # get new messages from server 
  $count= Mail::MsgStore::getmail(\&prompt);
  # send a Mail::Internet message 

  # add an account
  Mail::MsgStore::acct_set('Joe User <> (work)',$password);
  # delete an account 
  Mail::MsgStore::acct_del('Joe User <> (work)');
  # change mailroot 
  # change from address 
  Mail::MsgStore::from('Brian Lalonde <>');
  # get SMTP server address 
  $smtp= Mail::MsgStore::smtp;
  # add message 
  $MsgStore{'/'}= $msg;                # auto-filter 
  $MsgStore{'path/to/folder/'}= $msg;  # add to specific folder 
  # delete message 
  delete $MsgStore{'path/to/folder/msgid'};
  # delete folder 
  delete $MsgStore{'path/to/folder/'};

  # get message 
  $msg= $MsgStore{'path/to/folder/msgid'};
  # mark message as read, unmark 'general' flag 
  $MsgStore{'path/to/folder/msgid'}= 'read, -general';
  # get folder's message id list 
  @msgids= $MsgStore{'path/to/folder/'};
  # get list of folders 
  @folders= keys %MsgStore;

  # move message 
  $MsgStore{'newfolder/'}= delete $MsgStore{'path/to/folder/msgid'};
  # copy message 
  $MsgStore{'path/to/newfolder/'}= $MsgStore{'path/to/folder/msgid'};


The primary goal of this module is ease of use. The Mail::Folder module, on top of not quite being complete yet, is a pretty low-level API. I was very impressed with how Win32::TieRegistry simplified an otherwise complex task, and decided to adopt a similar interface for handling a mail store.

Another, equally important, reason for creating this module was user-configurability. I was unhappy with existing mail clients' filtering capabilities-- I wanted to pass every new message through some arbitrary Perl code that was smart enough to forward, reply, send pages, activate emergency-type alerts, etc. based on properties of the message. What I didn't want was more bloatware--Exchange, Outlook and Groupwise have already been written, and despite being huge, still don't do enough.

Storage Format

MsgStore uses a modified form of qmail's maildir format. Here's how it works: new messages are downloaded into a file guaranteed to have a unique, but incomplete, name. The filename is completed once the entire message has been successfully downloaded (the finishing of the filename replaces maildir's state subdirectories).

The unique filename is generated as a dot-separated list of (uppercase) hexadecimal numbers: seconds past epoch (12 digits), IP address (8 digits), process id (4 digits), and download number (2 digits). The IP should guarantee uniqueness to a machine, the time and pid narrows it down to a specific process, and a simple incremental number ensures that 256 messages can be downloaded per second and still retain uniqueness. The filename also begins and ends with 'mail', also separated by dots.

Message flags are part of the message id (although requesting a message by an id with the wrong flags still works). The flags are five characters delimited by parens. Each position is either a dash (off) or a letter (on). Order is significant, but since the letters spell the word FLAGS, that shouldn't be a problem. Here are what the letters stand for:

  F  flame
  L  list/group
  A  answered/replied
  G  general/flag
  S  seen/opened/read


The storage format used for this module quickly becomes unusable for large message stores; hundreds or thousands of tiny files are rarely stored efficiently on the disk.

Although the module is completely usable, I hope it will inspire better storage formats to use the same simple tied-hash interface.


The message store allows definition of the following subroutines in the file located in the mailroot directory:


Accepts the Mail::Internet message object. The message's recipient account is available as X-Recipient-Account in the message header.

Returns the name of the folder that the Mail::Internet $msg belongs in. Returning undef implies the Inbox. Also, all message flags should be stored in the X-Msg-Info header, either as the native (FLOR!) format of the message ID, or the english equivalents: flame, mailing-list, opened, replied, flagged.


Accepts the Mail::Internet message object. The message's recipient account is available as X-Recipient-Account in the message header.

Returns a boolean value that determines whether the message should be kept on the server.


Signs a message before it is sent.


Sending and Receiving


Logs on to each mail account, checking for new messages, which are downloaded, passed to filter() and added.

Returns number of messages downloaded. Requires a callback that will be used if there is a problem logging in:


Parameters: $acct ISA Mail::Address: user is the POP3 username, host is the POP3 server.

The function must return a password, or undef to cancel. The password will be updated if it was initially set, or left blank otherwise.


Parameters: $status_message is a string describing what is going on suitable for GUI statusbars, etc. $percent_done is an integer between 0 and 100 (when included, else undef) suitable for feeding to progress bars, etc.


Signs a Mail::Internet message, using the sign() function from the user-defined


Sends a Mail::Internet message, and stores a copy in Sent/.



Gets/sets the root directory of the mailstore. The user's login is appended to this directory. If the directory doesn't exist, it is created. If the directory doesn't contain an file, one (fully commented) is created.

Defaults to $ENV{MAILROOT} or current dir unless set.


Reloads the file. Useful if you provide an editing facility for that file, or otherwise know that it has changed.


Gets/sets the address of the outgoing mail server.


Gets/sets the email From: address.


Returns a list of account strings.


Adds/sets an POP3 account to the list handled by getmail(). Parameters: account and optional password.

Accounts strings are parsed by Mail::Address; the server portion is used to connect, and the user portion is used to log in. Everything else is mnemonic.


Deletes an account.

The Address Book


Returns a list of (references to) hashes for the entire address book.

address( field => $value, ... )

Add an entry to the address book. The key for the new entry is returned. The full list of fields is available in @addr_field, pretty names for the fields are in %addr_field (neither exported by default).

Some fields of note:


A guaranteed unique identifier for the address entry. Auto-generated on insert.


The only field allowed to contain tabs and newlines.

firstname, lastname, nickname, email

Standard mail-client stuff.

tons more...

(and in no guaranteed order)


Retrive the hash for an address.

address($key, field => $value, ...)

Update fields on an existing address. Boolean success is returned.

address($key, DELETE => 1 )

Delete an entry from the address book.


Gets/sets a comma or space-delimited list of LDAP servers.

whosearch(qr/regex/, [ @fields ] )

Searches the address book fields specified by fields, looking for records that match the regex, the firstname and lastname fields by default. (Actually, matches with "@addr{@fields}"=~ /regex/.) The special field nickname is also checked to match. A list of (references to) hashes of matching records are returned, plus a MATCHED field in each hash that contains the value of either $field[0] or nickname, depending on which field matched.

The result set is sorted by matching field.

This function is probably unneccessarily complex for most mail clients.

addrsearch( -starts => $namestart, [ -number => $hitnum, ] [ -fields => \@fields, ] )

This is a simpler version of "whosearch" that just returns address strings (rather than entire hashrefs for each record). (Actually, matches with "@addr{@fields}"=~ /regex/.) By default, the firstname and lastname fields are used, just as in "whosearch". The special field nickname is also checked to match. In list context, the list of matching address strings is returned, but in a scalar context, the $hitnum-th element is returned (this allows passing of a kind of "Nope, next one." request).

Each address is formatted this way: firstname lastname <email> unless the match was via nickname, in which case the nickname and a tab character are prepended to the address string.


Searches the server(s) specified by ldaps() for an entry that starts with $startswith, and returns a list similar to "addrsearch". Ignores queries shorter than 3 letters.

This function is called by "addrsearch", and probably needn't be called directly.



Searches messages in $folder (and all subfolders) for messages that produce a true value when passed to &match. Returns a list of fully-qualified message IDs.


Returns a text-only body of $msg. If the actual $msg is a multipart/mixed or multipart/alternative, for example, this just gives you the text portion of the message for display purposes.


Given a fully-qualified messsage ID (one that begins with the folder path), breaks the string into folder path and message ID. (Similar in spirit to the File::Basename module.)


Given a message ID whose flags may have changed (the message ID contains the message flags), returns the new message ID.


Returns a valid flagstring for the Mail::MsgStore message ID, given either a msgid or english string ('+read -list !flame') to parse. Mostly for internal use.


v, <>


perl(1), Sys::UniqueId, Mail::Internet, Mail::Folder, Win32::TieRegistry, Net::LDAP, Net::POP3, Time::ParseDate