Mail::Box(3pm)

πŸ’Œ Mail::Box(3pm) β€” the perl doc you're reading is literally User Contributed Perl Documentation 🌸

okay bestie, sit down, hydrate, because we're doing Mail::Box β€” manage a mailbox, a folder with messages. it's giving folder wrangler, it's giving main character energy for your email. πŸ’…


✨ NAME ✨

Mail::Box β€” manage a mailbox, a folder with messages πŸ“¬


🌈 INHERITANCE 🌈

Mail::Box
  is a Mail::Reporter

Mail::Box is extended by
  Mail::Box::Dir
  Mail::Box::File
  Mail::Box::Net

so like, Mail::Box is a Mail::Reporter, and then Mail::Box::Dir, Mail::Box::File and Mail::Box::Net all extend it. family tree but make it folders. 🧚


⭐ SYNOPSIS ⭐

use Mail::Mbox::Mbox ();
my $emails = Mail::Mbox::Mbox->new(folder => $ENV{MAIL});
print $folder->name;

print $folder->message(0);    # get the first message.
$folder->message(3)->delete;  # delete the third message
say "Folder size = ", $folder->nrMessages;

# Iterate over the messages.
foreach my $msg ($folder->messages) {...} # all messages
foreach my $msg (@$folder) {...}          # all messages

$folder->addMessage(Mail::Message->new(...));

Managing multiple mailboxes:

use Mail::Box::Manager;
my $mgr    = Mail::Box::Manager->new;
my $folder = $mgr->open(folder => $ENV{MAIL}, ...);

Tied-interface:

tie my(@inbox), 'Mail::Box::Tie::ARRAY', $folder;
tie my(%inbox), 'Mail::Box::Tie::HASH', $inbox;

# Four times the same:
$inbox[3]->print;                 # tied
$folder->[3]->print;              # overloaded folder
$folder->message(3)->print;       # usual
print $folder->[3];               # overloaded message

# Twice times the same
$inbox{$msgid}->print;            # tied
$folder->messageId($msgid)->print;# usual

no cap, look at all those ways to grab the same message. four different paths, one destiny. iconic. πŸ‘‘


πŸ’– DESCRIPTION πŸ’–

A Mail::Box::Manager creates Mail::Box objects. But you already knew that, because you started with the Mail::Box-Overview manual page. That page is obligatory reading, sorry bestie 😭 it's not optional, it's the vibe check you have to pass first.

⚠️ Be aware: This module versions 4.0 and up is not fully compatible with older releases: mainly the exception handling has changed. When you need to upgrade, please read the wiki <https://github.com/markov2/perl5-Mail-Box/wiki/>. Version 3 is still maintained and may see new releases as well.

Mail::Box is the base class for accessing various types of mailboxes (folders) in a uniform manner. The various folder types vary on how they store their messages, but when some effort those differences could be hidden behind a general API. For example, some folders store many messages in one single file, where other store each message in a separate file within the same directory. lowkey that's the whole flex of this module β€” one API, many storages, zero chaos (okay some chaos). πŸ’«

No object in your program will be of type Mail::Box: it is only used as base class for the real folder types. Mail::Box is extended by file, directory, and remote folder types. See "Different kinds of folders" πŸ—‚οΈ

Extends "DESCRIPTION" in Mail::Reporter.


πŸ¦‹ OVERLOADED πŸ¦‹

overload: '""' stringification

The folder objects stringify to their name. This simplifies especially print statements and sorting a lot. fr fr, folders as strings is a whole quality-of-life moment. ✨

Β» example: use overloaded folder as string

# Three lines with overloading: resp. cmp, @{}, and ""
foreach my $folder (sort @folders)
{   my $msgcount = @$folder;
    print "$folder contains $msgcount messages\n";
}

overload: '@{}' use as ARRAY

When the folder is used as if it is a reference to an array, it will show the messages, like messages() and message() would do.

Β» example: use overloaded folder as array

my $msg = $folder->[3];
my $msg = $folder->message(3);          # same

foreach my $msg (@$folder) ...
foreach my $msg ($folder->messages) ... # same

overload: cmp string comparison

Folders are compared based on their name. The sort rules are those of the build-in cmp. so yes, you can sort your folders and it just works. periodt. πŸ’…


πŸ”₯ METHODS πŸ”₯

Extends "METHODS" in Mail::Reporter.

πŸŽ€ Constructors πŸŽ€

Extends "Constructors" in Mail::Reporter.

$class->new(%options)

Open a new folder. A list of labeled %options for the mailbox can be supplied. Some options pertain to Mail::Box, and others are added by sub-classes.

To control delay-loading of messages, as well the headers as the bodies, a set of *_type options are available. extract determines whether we want delay-loading. Improves base, see "Constructors" in Mail::Reporter

here's the whole default table bestie, every single one, no skipping 🫢

 -Option           --Default
  access             'r'
  body_delayed_type  Mail::Message::Body::Delayed
  body_type          <folder specific>
  coerce_options     +[]
  create             false
  extract            10240
  field_type         undef
  fix_headers        false
  folder             $ENV{MAIL}
  folderdir          undef
  head_delayed_type  Mail::Message::Head::Delayed
  head_type          Mail::Message::Head::Complete
  keep_dups          false
  lock_file          undef
  lock_timeout       1 hour
  lock_type          Mail::Box::Locker::DotLock
  lock_wait          10 seconds
  locker             undef
  manager            undef
  message_type       <folder-class>::Message
  multipart_type     Mail::Message::Body::Multipart
  remove_when_empty  true
  save_on_exit       true
  trusted            <depends on folder location>

same thing but as a lil checklist of what the defaults literally are:

  • access β†’ 'r'
  • body_delayed_type β†’ Mail::Message::Body::Delayed
  • body_type β†’ <folder specific>
  • coerce_options β†’ +[]
  • create β†’ false
  • extract β†’ 10240
  • field_type β†’ undef
  • fix_headers β†’ false
  • folder β†’ $ENV{MAIL}
  • folderdir β†’ undef
  • head_delayed_type β†’ Mail::Message::Head::Delayed
  • head_type β†’ Mail::Message::Head::Complete
  • keep_dups β†’ false
  • lock_file β†’ undef
  • lock_timeout β†’ 1 hour
  • lock_type β†’ Mail::Box::Locker::DotLock
  • lock_wait β†’ 10 seconds
  • locker β†’ undef
  • manager β†’ undef
  • message_type β†’ <folder-class>::Message
  • multipart_type β†’ Mail::Message::Body::Multipart
  • remove_when_empty β†’ true
  • save_on_exit β†’ true
  • trusted β†’ <depends on folder location>

and now the full receipts on each one, because we do not gatekeep: πŸ’œ


  • access => $mode β€” Access-rights to the folder. Folders are opened for read-only (which means write-protected) by default! The $mode can be 'r' (read-only, default), 'a' (append only), 'rw' (read-write), and 'd' (delete only).

This $mode describes the abstract folder access: how the underlaying files are really accessed depends on the action which needs to take place.

🚨 Be warned: writing a MBOX type folder may create a new file to replace the old folder. The permissions and owner of the file may get changed by this. like, it will not ask you first. it just does it. πŸ’€
  • body_delayed_type => CLASS β€” The bodies which are delayed: which will be read from file when it is needed, but not before. lazy girl behavior, but make it intentional. πŸ’…
  • body_type => CLASS|CODE β€” When messages are read from a folder-file, the headers will be stored in a head_type object. For the body, however, there is a range of choices about type, which are all described in Mail::Message::Body.

Specify a CODE-reference which produces the body-type to be created, or a CLASS of the body which is used when the body is not a multipart or nested. In case of a code reference, the header structure is passed as first argument to the routine.

Do not return a delayed body-type (like ::Delayed), because that is determined by the extract option while the folder is opened. Even delayed message will require some real body type when they get parsed eventually. Multiparts and nested messages are also outside your control.

For instance:

$mgr->open('InBox', body_type => \&which_body);

sub which_body($) {
    my $head = shift;
    my $size = $head->guessBodySize || 0;
    my $type = $size > 100000 ? 'File' : 'Lines';
    "Mail::Message::Body::$type";
}

The default depends on the mail-folder type, although the general default is Mail::Message::Body::Lines. Please check the applicable manual pages. πŸ‘€

  • coerce_options => ARRAY β€” Keep configuration information for messages which are coerced into the specified folder type, starting with a different folder type (or even no folder at all). Messages which are coerced are always fully read, so this kind of information does not need to be kept here.
  • create => BOOLEAN β€” Automatically create the folder when it does not exist yet. This will only work when access is granted for writing or appending to the folder.
🚨 Be careful: you may create a different folder type than you expect unless you explicitly specify Mail::Box::Manager::open(type). bestie, read the type. please. πŸ™ˆ
  • extract => INTEGER|CODE|METHOD|'LAZY'|'ALWAYS' β€” Defines when to parse (process) the content of the message. When the header of a message is read, you may want to postpone the reading of the body: header information is more often needed than the body data, so why parse it always together? The cost of delaying is not too high, and with some luck you may never need parsing the body. literally the procrastination option and it's good. ✨

If you supply an INTEGER to this option, bodies of those messages with a total size less than that number will be extracted from the folder only when necessary. Messages where the size (in the Content-Length field) is not included in the header, like often the case for multiparts and nested messages, will not be extracted by default.

If you supply a CODE reference, that subroutine is called every time that the extraction mechanism wants to determine whether to parse the body or not. The subroutine is called with the following arguments:

CODE->(FOLDER, HEAD)

where FOLDER is a reference to the folder we are reading. HEAD refers to the Mail::Message::Head::Complete head of the message at hand. The routine must return a "true" value (extract now) or a "false" value (be lazy, do not parse yet). Think about using the Mail::Message::Head::guessBodySize() and Mail::Message::guessTimestamp() on the header to determine your choice.

The third possibility is to specify the NAME of a method. In that case, for each message is called:

FOLDER->NAME(HEAD)

Where each component has the same meaning as described above.

The fourth way to use this option involves constants: with "LAZY" all messages will be delayed. With "ALWAYS" you enforce unconditional parsing, no delaying will take place. The latter is usuful when you are sure you always need all the messages in the folder.

$folder->new(extract => 'LAZY');  # Very lazy
$folder->new(extract => 10000);   # Less than 10kB

# same, but implemented yourself
$folder->new(extract => &large);
sub large($) {
   my ($f, $head) = @_;
   my $size = $head->guessBodySize;
   defined $size ? $size < 10000 : 1
};

# method call by name, useful for Mail::Box
# extensions. The example selects all messages
# sent by you to be loaded without delay.
# Other messages will be delayed.
$folder->new(extract => 'sent_by_me');
sub Mail::Box::send_by_me($) {
    my ($self, $header) = @_;
    $header->get('from') =~ m/\bmy\@example.com\b/i;
}

(yes the example really does contain usuful, we keep everything exactly as it is around here, typos and all 🫢)

  • field_type => $class β€” The type of the fields to be used in a header. Must extend Mail::Message::Field.
  • fix_headers => BOOLEAN β€” so normally, when MIME headers are broken, the parser just gives up 😭 and every line it couldn't parse gets dumped into the body of the message. Set this flag and instead the messed-up line gets tacked onto the previous header field and parsing keeps going. See Mail::Box::Parser::Perl::new(fix_header_errors).
  • folder => $name β€” which folder are we opening, bestie (for reading or for writing)? When it's for reading (that's the access option set to "r" or "a") the mailbox has to already exist and be readable, periodt. The file or directory doesn't actually need to exist if you're opening for reading and writing ("rw"). And yes, write-permission gets checked when you open an existing mailbox.

The folder name can start with a "=" β€” that means "hey, this is named relative to whatever directory is in new(folderdir)". No equals sign? Then it's just taken as a relative or absolute path. Slay.

  • folderdir => $directory β€” where do folders live by default? πŸ” If a folder-name is preceded by an equals-sign ("=", a "mutt" convention) it's explicitly saying "this folder sits below the default directory". Like: with folderdir => '/tmp' and folder => '=abc', the folder-file's name is /tmp/abc. Each folder type already has some default set. iykyk.
  • head_delayed_type => $class β€” the headers that get delayed: read from the file only when they're actually needed, not a second before. Chill vibes.
  • head_type => $class β€” the type of header that holds all the header info. Must extend Mail::Message::Head::Complete.
  • keep_dups => BOOLEAN β€” do we keep duplicate messages inside the folder or not? A message counts as a duplicate if its message-id is the same as a message already parsed within that same folder. If this option is false (which is the default, ngl) those dupes get automatically deleted, because storing the same message twice is just useless 🫠.
  • lock_file => $file β€” the name of the $file used to lock. You must specify this whenever locking is going to be used.
  • lock_timeout => $seconds β€” if the lock file is older than the given number of $seconds, it's treated as a mistake: the original lock gets released and accepted for this folder. Bye ghost lock πŸ’….
  • lock_type => $class|$name|\@names β€” the type of the locker object. Could be a $class that extends Mail::Box::Locker, or one of the known locker types by $name: "DotLock", "Flock", "FcntlLock", "Mutt", "NFS", "POSIX", or "NONE". Pass an ARRAY and a Multi locker gets built using the specified list of locker @names.
  • lock_wait => $seconds β€” how many $seconds do we wait before giving up on opening this folder.
  • locker => $locker β€” an object that extends Mail::Box::Locker, which will handle the folder locking and replace the default lock behavior. main character energy πŸ‘‘
  • manager => $manager β€” a reference to the object managing this folder β€” typically a Mail::Box::Manager instance.
  • message_type => $class β€” what kind of message objects live in this type of folder? The default is built from the folder class followed by "::Message". So for instance, the message type for Mail::Box::POP3 is Mail::Box::POP3::Message.
  • multipart_type => CLASS β€” the default type of objects created for multipart message bodies.
  • remove_when_empty => BOOLEAN β€” should the folder file or directory get removed automatically when writing would result in a folder with no messages and no sub-folders? ✨
  • save_on_exit => BOOLEAN β€” sets the policy for saving the folder when it's closed. You can close a folder manually (see close()) or in a bunch of implicit ways, including the moment the program terminates.
  • trusted => BOOLEAN β€” flags whether to trust the data in the folder or not. Folders sitting in your folderdir are trusted by default (even when their names weren't specified starting with a "="). Folders outside the folderdir, or read from STDIN (Mail::Message::Construct::read()), are not trusted by default and require some extra checking.
If you do not check the encodings of received messages, you may end up printing binary data to the screen, which is a security risk. Not cute. Not iconic. Be careful. 😭

πŸŽ€ Attributes πŸŽ€

Extends Attributes in Mail::Reporter. Okay so here's what we got:

  • $obj->fixHeaders() β€” the accessor for that fixy behavior.
  • $obj->folderdir( [$directory] ) β€” get it or set it: the $directory used to store mail-folders by default.

πŸ’« example:

print $folder->folderdir;
$folder->folderdir("$ENV{HOME}/nsmail");
  • $obj->isTrusted() β€” is this folder trusted or nah.
  • $obj->name() β€” returns the name of the folder. What that name actually represents depends on the real type of mailbox being used.

πŸ’« example:

print $folder->name;
print "$folder";       # overloaded stringification
  • $obj->removeEmpty() β€” the accessor for the remove-when-empty setting.
  • $obj->type() β€” returns a name for the type of mail box. So it can be "mbox", "mh", "maildir", or "pop3" πŸ¦‹.

🌸 The folder 🌸

  • $obj->addMessage($message, %options) β€” adds a message to the folder. Usually that message is a Mail::Box::Message object or a sub-class of it. Heads up: the message shall not be in another folder when you use this method. If it is, use Mail::Box::Manager::moveMessage() or Mail::Box::Manager::copyMessage() through the manager instead, bestie.

Messages whose ids already exist in this folder are not added. Sorry to that message.

BE WARNED that message labels may get lost when a message is moved from one folder type to another. An attempt is made to translate labels, but there are so many differences in interpretation by applications. It's giving chaos, fr fr.
-Option--Default
 share   <not used>
  • share => BOOLEAN β€” try to share the physical resource of the current message with the indicated message. Sometimes it's possible to share messages between different folder types. When sharing isn't possible, this option is simply ignored.
Sharing the resource is quite dangerous, and only available for a limited number of folder types β€” at the moment only some Mail::Box::Dir folders; these file-based messages can be hardlinked (on platforms that support it). The link may get broken when one message is modified in one of the folders.... but maybe not, depending on the folder types involved. Risky business πŸ’€.

πŸ’« example:

$folder->addMessage($msg);
$folder->addMessages($msg1, $msg2, ...);
  • $obj->addMessages(@messages) β€” adds a set of message objects to the open folder all at once. For some folder types this may be faster than adding them one at a time. Efficiency slay ✨.

πŸ’« example:

$folder->addMessages($msg1, $msg2, ...);
  • $class->appendMessages(%options) β€” append one or more messages to a folder that hasn't been opened. Usually this method gets called by Mail::Box::Manager::appendMessage(), in which case the correctness of the folder type is checked for you 🫢.

For some folder types you're required to open the folder before it can be used for appending. This can be fast, but it can also be very slow (depends on the implementation, no cap). All %options passed will also be used to open the folder, if that's needed.

-Option  --Default
 folder    <required>
 message   undef
 messages  undef
 share     false
  • folder => $name β€” the $name of the folder the messages are to be appended to. The folder implementation will avoid opening the folder when possible, because that's resource consuming.
  • message => $message β€” one single message.
  • messages => $message|\@messages β€” one reference to one $message or an ARRAY of @messages, which may be of any type. The messages will first be coerced into the correct message type to fit in the folder, and then added to it.
  • share => BOOLEAN β€” try to share physical storage of the message. Only available for a limited number of folder types, otherwise it's a no-op.

πŸ’« example:

my $message = Mail::Message->new(...);
Mail::Box::Mbox->appendMessages(
  folder    => '=xyz',
  message   => $message,
  folderdir => $ENV{FOLDERS},
 );

better:

my Mail::Box::Manager $mgr;
$mgr->appendMessages($message, folder => '=xyz');
  • $obj->close(%options) β€” closes the folder, which usually means writing the changes. It returns "false" when writing is required but fails. Please actually check that result, bestie, please πŸ™.
WARNING: When moving messages from one folder to another, be sure to write the destination folder before writing and closing the source folder. Otherwise you may lose data if the system crashes or if there are software problems. Do not play with this one.
-Option      --Default
 force         false
 save_deleted  false
 write         'MODIFIED'
  • force => BOOLEAN β€” override the new(access) setting that was specified when the folder was opened. This option only has an effect if its value is TRUE. NOTE: writing to the folder may not be permitted by the operating system, in which case even "force" will not help. Sorry 😬.
  • save_deleted => BOOLEAN β€” also write messages that were flagged to be deleted to their folder. The flag for deletion is conserved (when possible), which means that re-opening the folder may remove the messages for real. See write(save_deleted).
  • write => 'ALWAYS'|'NEVER'|'MODIFIED' β€” specifies whether the folder should be written. As you'd expect, "ALWAYS" means always (even if there are no changes), "NEVER" means changes to the folder will be lost 😭, and "MODIFIED" only saves the folder if there are any changes.

πŸ’« example:

my $f = $mgr->open('spam', access => 'rw')
    or die "Cannot open spam: $!\n";

$f->message(0)->delete
    if $f->messages;

$f->close
    or die "Couldn't write $f: $!\n";
  • $obj->copyTo($folder, %options) β€” copies the folder's messages to a new folder. The new folder may be of a different type. Glow-up for your mail πŸ¦‹.
-Option       --Default
 delete_copied  false
 select         'ACTIVE'
 share          <not used>
 subfolders     <folder type dependent>
  • delete_copied => BOOLEAN β€” marks the messages from the source folder for deletion, right after they got copied over. Heads up bestie: the deletion only actually happens once the originating folder gets closed. πŸ‘‹
  • select => 'ACTIVE'|'DELETED'|'ALL'|LABEL|!LABEL|FILTER β€” which messages are we copying, exactly? Go read the description of messages() if you wanna know how this whole selection thing works. ✨
  • share => BOOLEAN β€” try to share the message between both folders. Some Mail::Box::Dir folder types are down for this and do it by creating a hardlink (on UNIX/Linux). 🫢
  • subfolders => BOOLEAN|'FLATTEN'|'RECURSE' β€” how do we treat sub-folders, hmm? When it's "false" (0 or "undef"), sub-folders just get ignored, no thoughts. With "FLATTEN", messages from the sub-folders get pulled up into the main copy. "RECURSE" recursively copies the sub-folders too. By default, if the destination folder supports sub-folders then "RECURSE" is used, otherwise "FLATTEN". And a value of "true" just picks the default for you. πŸ’…
Β» example:
my $mgr  = Mail::Box::Manager->new;
my $imap = $mgr->open(type => 'imap', host => ...);
my $mh   = $mgr->open(type => 'mh', folder => '/tmp/mh',
    create => 1, access => 'w');

$imap->copyTo($mh, delete_copied => 1);
$mh->close; $imap->close;

πŸ—‘οΈ $obj->delete(%options)

Yeets the specified folder file or folder directory (whichever it is, depends on the folder type) off of disk. And okay bestie, let's be so real: THIS IS DANGEROUS β€” you "may" lose data. It returns a "true" value when it worked. 😭

WARNING: when you're moving messages from one folder to another, please please PLEASE write the destination folder before deleting the source folder. Otherwise you may lose data if the system crashes or if there are software problems. Not a vibe, don't do it. πŸ’€

Here's the option rundown (defaults included):

  • recursive => BOOLEAN β€” default: true.
Β» example: removing an open folder
my $folder = Mail::Box::Mbox->new(folder => 'InBox', access => 'rw');
... some other code ...
$folder->delete;
Β» example: removing an closed folder
my $folder = Mail::Box::Mbox->new(folder => 'INBOX', access => 'd');
$folder->delete;

🌈 $obj->organization()

Returns how the folder is organized: as one "FILE" with many messages, a "DIRECTORY" with one message per file, or by a "REMOTE" server. Three whole personality types, iconic. πŸ‘‘

πŸ“ $obj->size()

Returns the size of the folder in bytes, and no, it does not count the deleted messages. Fair warning though: the error in the result it hands you can be as large as 10%, because the in-memory representation of messages isn't always the same as the size when they're written. So like... approximately, bestie. πŸ™ˆ

πŸ”„ $obj->update(%options)

Reads new messages from the folder β€” the ones that were received after it got opened. Ngl this is quite dangerous and technically shouldn't even be possible, since folders that are open are locked. But some applications don't use locks, or use the wrong kind of locks. πŸ™ƒ So this method reads the changes (not always failsafe, watch out) and folds them into the open folder's administration.

The %options are extra values that get passed along to the updateMessages() method, which is the one doing the actual work here. πŸ’ͺ

πŸ”— $obj->url()

Represents the folder as a URL (Universal Resource Locator) string. You can hand a URL like this straight over as a folder name to Mail::Box::Manager::open(). So convenient honestly. ✨

Β» example:
print $folder->url;
# may result in
#   mbox:/var/mail/markov   or
#   pop3://user:password@pop.aol.com:101

🚩 Folder flags

$obj->access()

Returns the access mode of the folder, as set by new(access). πŸ”‘

$obj->isModified()

Checks if the folder β€” as it lives in memory β€” is modified. You get a "true" value when any of the messages is marked for deletion, has changed, or when messages were added after the folder was read from file. πŸ’«

WARNING: this flag is not related to an external change to the folder structure on disk. For that whole situation, go look at update() instead. Don't mix them up bestie. 😀

$obj->modified( [BOOLEAN] )

Sets whether the folder is modified or not. You're in charge here. πŸ’…

$obj->writable()

Checks whether the current folder is writable. Simple as that.

Β» example:
$folder->addMessage($msg) if $folder->writable;

πŸ’Œ The messages

$obj->current( [$number|$message|$message_id] )

Some mail-readers keep track of the current message β€” the last one that got used. This method returns [after setting] the current message. You can pass a $number to say "that message number is now current", or a $message/$message_id instead (as long as you're sure the header is already loaded, otherwise they won't be recognized, no cap). 🎯

Β» example:
$folder->current(0);
$folder->current($message);

$obj->find($message_id)

Like messageId(), this method searches for a message with the $message_id and returns the matching message object. But here's the difference: "find" will cause unparsed messages in the folder to actually get parsed until the message-id turns up. And it scans the folder back to front. πŸ”

$obj->findFirstLabeled( $label, [BOOLEAN, [\@msgs]] )

Finds the first message that has this $label with the correct setting. The "BOOLEAN" says whether you want any "true" value or any "false" value found in the ARRAY of @msgs. By default it goes looking for a "true" value. If a message doesn't have the requested label at all, it's treated as "false". 🀷

Β» example: looking for a labeled message
my $current = $folder->findFirstLabeled('current');

my $first   = $folder->findFirstLabeled(seen => 0);

my $last    = $folder->findFirstLabeled(seen => 0,
  [ reverse $self->messages('ACTIVE') ] )

$obj->message( $index, [$message] )

Gets or sets a message at a certain index. Messages that are flagged for deletion still get counted, jsyk. And negative indexes start from the end of the folder. πŸ”’

Β» example:
my $msg = $folder->message(3);
$folder->message(3)->delete;   # status changes to `deleted'
$folder->message(3, $msg);
print $folder->message(-1);    # last message.

$obj->messageId( $message_id, [$message] )

With just one argument, it returns the message in the folder with the specified $message_id. If you pass a reference to a message object as that optional second argument, the message gets stored in the folder first, replacing any existing message whose message ID is $message_id. (Fun fact: the message ID of $message doesn't even have to match $message_id.) πŸŒ€

!!WARNING!!: when the message headers are delay-parsed, the message might be sitting in the folder but not yet parsed into memory. In that case, use find() instead of messageId() if you genuinely need a thorough search. This is especially the case for directory organized folders without special indexi, like Mail::Box::MH. πŸ’€

The $message_id may still be wrapped in angles, which will get stripped. When that happens, blanks (which come from header line folding) are removed too. And any other info hanging around the angles gets removed as well. 🧹

Β» example:
my $msg = $folder->messageId('<complex-message.id>');
$folder->messageId("<complex-message\n.id>", $msg);
my $msg = $folder->messageId('complex-message.id');
my $msg = $folder->messageId('garbage <complex-message.id> trash');

$obj->messageIds()

Returns a list of all message-ids in the folder, including the ones belonging to messages that are marked for deletion. Everyone's invited. πŸŽ€

For some folder-types (like MH), this method may cause all the message-files to be read. Go check their respective manual pages if you care.

Β» example:
foreach my $id ($folder->messageIds) {
   $folder->messageId($id)->print;
}

$obj->messages( <'ALL'|$range|'ACTIVE'|'DELETED'|$label| !$label|$filter> )

Returns multiple messages from the folder. The default is "ALL", which returns all the messages (as you probably expected, bestie). The "ACTIVE" flag returns the messages that are not flagged for deletion. That's the exact opposite of "DELETED", which returns all the messages that are gonna be deleted when the folder is closed. βœ‚οΈ

You can also pass a $range: two numbers specifying the begin and end index in the array of messages. Negative indexes count from the end of the folder. And if an index is out-of-range, the returned list just comes back shorter β€” no complaints, no drama. 😌

Anything that isn't one of the predefined names is seen as a label, and the messages with that label set get returned. When the sequence starts with an exclamation mark (!), the search result gets flipped. πŸ”

For more complex searches you can specify a $filter, which is simply a code reference. The message is passed in as the only argument.

Β» example:
foreach my $message ($folder->messages) {...}
foreach my $message (@$folder) {...}

# twice the same
my @messages   = $folder->messages;
my @messages   = $folder->messages('ALL');

# Selection based on a range (begin, end)
my $subset     = $folder->messages(10,-8);

# twice the same:
my @not_deleted= grep {not $_->isDeleted}
                    $folder->messages;
my @not_deleted= $folder->messages('ACTIVE');

# scalar context the number of messages
my $nr_of_msgs = $folder->messages;

# third message, via overloading
$folder->[2];

# Selection based on labels
$mgr->moveMessages($spam, $inbox->message('spam'));
$mgr->moveMessages($archive, $inbox->message('seen'));

$obj->nrMessages(%options)

Just calls messages() in scalar context, so you get a count back instead of the actual messages themselves. Some people seem to understand it better this way, and honestly, respect. 🫑

Note that nrMessages() defaults to giving you a count of "ALL" messages in the folder β€” including both "ACTIVE" and "DELETED" ones. So like, everybody.

The %options get passed to (and explained in) messages(). πŸ“¬

$obj->scanForMessages($message, $message_ids, $timespan, $window)

So you start with a $message, and you're hunting for a set of messages that are related to it. Think messages that show up in the 'In-Reply-To' and 'Reference' header fields of that message, for example. You know these messages by their $message_ids, and you want to find them inside the folder. 🧡

When all the message-ids are already known, looking up messages is simple: they're found in a plain hash using messageId(). But Mail::Box is lazy wherever it can be, so lots of messages may not have been read from file yet β€” and honestly? That's the preferred situation, because it saves time and memory. Stay lazy, stay efficient. πŸ’€βœ¨

...and it's way smarter to search backward, bestie, no cap πŸ’… β€” related messages are almost always chilling right next to each other, so this method starts scanning the folder from the $message you hand it and goes back toward the front of the folder.

The $timespan is your little cutoff switch based on the time wrapped up inside the message πŸ”₯. When you pass the constant string "EVER", then the search is never limited by that. When you hand it an integer, it gets used as absolute time in time-ticks β€” the kind your platform-dependent "time" function gives you. And in every other case, it gets passed to timespan2seconds() to work out the threshold as time relative to the message's time.

Then there's $window, which limits the search by number of messages to be scanned β€” an integer, or the constant string "ALL" if you're feeling feral ✨.

What comes back to you? The message-ids that were not found during the scan. But ⚠️ be warned: a message-id could already be known and therefore not found, so check that first, okay bestie?

πŸ’‘ example: scanning through a folder for a message
my $refs   = $msg->get('References') or return;
my @msgids = $ref =~ m/\<([^>]+\>/g;
my @failed = $folder->scanForMessages($msg, \@msgids, '3 days', 50);

πŸ¦‹ Sub-folders πŸ¦‹

$any->listSubFolders(%options)

Lists out the names of all the sub-folders of this folder β€” but not recursively descending, okay, we're staying on one level πŸŽ€. Use these names as the argument to openSubFolder() to actually get access to that folder.

For MBOX folders, the sub-folders are simulated, jsyk.

Here's the gossip on the options πŸ’…:

  • check => BOOLEAN β€” default false. Should every returned foldername be checked to make sure it's actually the right type? Each sub-folder may legitimately need to be opened to check this, which comes with a folder-type-dependent penalty β€” and in some cases it's very expensive 😭.
  • folder => $name β€” default is <from calling object>. The folder whose sub-folders you want listed.
  • folderdir => $dir β€” default is <from folder>.
  • skip_empty => BOOL β€” default false. Shall empty folders (folders that currently don't contain any messages) be included? Empty folders aren't useful to open, but they may be useful to save to!
πŸ’‘ example:
my $folder = $mgr->open('=in/new');
my @subs = $folder->listSubFolders;

my @subs = Mail::Box::Mbox->listSubFolders(folder => '=in/new');
my @subs = Mail::Box::Mbox->listSubFolders; # toplevel folders.

$any->nameOfSubFolder( $subname, [$parentname] )

Returns the constructed name of the folder with NAME, which is a sub-folder of this current one 🫢. You've either gotta call this method as an instance method, or specify a $parentname.

πŸ’‘ example: how to get the name of a subfolder
my $sub = Mail::Box::Mbox->nameOfSubfolder('xyz', 'abc');
print $sub;                        # abc/xyz

my $f = Mail::Box::Mbox->new(folder => 'abc');
print $f->nameOfSubfolder('xyz');  # abc/xyz

my $sub = Mail::Box::Mbox->nameOfSubfolder('xyz', undef);
print $sub;                        # xyz

$obj->openRelatedFolder(%options)

Opens a folder (usually a sub-folder) using the same options as this one 🌸. If there's a folder manager in play, it'll be told about this new folder. The %options overrule the options that were used for the folder this method got called on.

$obj->openSubFolder($subname, %options)

Opens (or creates, if it doesn't exist yet!) a new subfolder inside an existing folder ✨.

πŸ’‘ example:
my $folder = Mail::Box::Mbox->new(folder => '=Inbox');
my $sub    = $folder->openSubFolder('read');

$any->topFolderWithMessages()

Some folder types can have messages in the top-level folder, and some other ones literally can't. That's the whole vibe 😌.


πŸ”§ Internals πŸ”§

$obj->coerce($message, %options)

Coerces the $message to be the correct type so it can be placed in the folder πŸ’ͺ. You can specify Mail::Internet and MIME::Entity objects here β€” they'll be translated into Mail::Message messages first.

$any->create($foldername, %options)

Creates a folder πŸŽ€. If the folder already exists, it gets left unchanged β€” no drama. The folder is created, but not opened! If you want to open a file that may need to be created, then use Mail::Box::Manager::open() with the create flag, or Mail::Box::new(create).

  • folderdir => $directory β€” default undef. When the foldername is preceded by a β€œ=”, the $directory will be searched for the named folder.

$obj->determineBodyType($message, $head)

Determines which kind of body will be created for this message when the folder is read initially πŸ’œ.

$class->foundIn( [$foldername], %options )

Figures out if the specified folder is of the type handled by the folder class. This method gets extended by each folder sub-type.

The $foldername specifies the name of the folder, as specified by the application. You need to specify the "folder" option when you skip this first argument.

The %options is a list of extra info for the request. Read the docs for each type of folder for type-specific options, but every folder class will at least support the "folderdir" option:

  • folderdir => $directory β€” default undef. The location where the folders of this class are stored by default. If the user specifies a name that starts with a β€œ=”, that means the folder is to be found in this default $directory.
πŸ’‘ example:
Mail::Box::Mbox->foundIn('=markov', folderdir => "$ENV{HOME}/Mail");
Mail::Box::MH->foundIn(folder => '=markov');

$obj->lineSeparator( [<STRING|'CR'|'LF'|'CRLF'>] )

Returns the character (or characters!) used to separate lines in the folder file β€” optionally after setting it to STRING, or one of the constants πŸ¦‹. The first line of the folder sets the default.

UNIX uses a "LF" character, Mac a "CR", and Windows uses both a "CR" and a "LF". Each separator gets represented by a "\n" inside your program 🌈. But when you're processing platform-foreign folders, complications pop up β€” think about the "Size" field in the header, bestie.

When the separator is changed, the whole folder may need to be rewritten. Although… that may not actually be required πŸ˜….

$obj->locker()

Returns the locking object, periodt.

$obj->read(%options)

Reads messages from the folder into memory 🧠. The %options are folder specific. Do not call read() yourself β€” it will be called for you when you open the folder via the manager, or when you instantiate a folder object directly.

πŸ“Œ NOTE: if you're copying messages from one folder to another, use addMessages() instead of read().
πŸ’‘ example:
my $mgr = Mail::Box::Manager->new;
my $folder = $mgr->open('InBox');             # implies read
my $folder = Mail::Box::Mbox->new(folder => 'Inbox'); # same

$obj->readMessages(%options)

Called by read() to actually read the messages from one specific folder type πŸ‘‘. The read() handles organizing all the general activities.

The %options are "trusted", "head_type", "field_type", "message_type", "body_delayed_type", and "head_delayed_type" as defined by the folder at hand. The defaults are the constructor defaults (see new()).

$obj->storeMessage($message)

Stores the message in the folder without the checks that addMessage() performs πŸ™ˆ.

$obj->toBeThreaded($messages)

The specified message is ready to be removed from a thread 🧡. This gets passed on to the mail-manager, which keeps an overview of which thread-detection objects are floating around.

$obj->toBeUnthreaded($messages)

The specified message is ready to be included in a thread 🧡✨. This gets passed on to the mail-manager, which keeps an overview of which thread-detection objects are floating around.

$obj->updateMessages(%options)

Called by update() to read the messages that arrived in the folder after it was opened πŸ“¬. Sometimes external applications dump messages into a folder without locking (or using a different lock than your application does).

Although this is quite dangerous, it only fails when a folder is updated (reordered or a message removed) at exactly the same time as new messages arrive. These collisions are sparse, so like… lowkey fine.

The options are the same as for readMessages().

$obj->write(%options)

Writes the data to disk πŸ’Ύ. The folder (a "true" value) is returned if it was successful. Deleted messages are transformed into destroyed messages β€” their memory gets freed.

⚠️ WARNING: When moving messages from one folder to another, be sure to write (or close()) the destination folder before writing (or closing) the source folder β€” otherwise you may lose data if the system crashes or if there are software problems. This is the one that bites people, bestie, pls don't skip it 😭

To write a folder to a different file, you must first create a new folder, then move all the messages, and then write or close() that new folder.

  • force => BOOLEAN β€” default false. Overrides write-protection with new(access) while opening the folder (whenever possible β€” it may still be blocked by the operating system).
  • save_deleted => BOOLEAN β€” default false. Do also write messages that where flagged to be deleted to their folder. The flag for deletion is conserved (when possible), which means that a re-open of the folder may remove the messages for real. See close(save_deleted).

$obj->writeMessages(%options)

Called by write() to actually write the messages from one specific folder type πŸ’…. The "write" handles organizing the general activities. All options to write() are passed to "writeMessages" as well. Besides that, a few extras get added by "write" itself.

  • messages => ARRAY β€” default <required>. The messages to be written, which is a sub-set of all the messages in the current folder.

🌈 Other methods 🌈

$any->timespan2seconds($time)

The parameter $time is a string that starts with a float, and then one of the words 'hour', 'hours', 'day', 'days', 'week', or 'weeks' ⏳. For instance: '1 hour' or '4 weeks'.


πŸ’œ Error handling πŸ’œ

Extends "Error handling" in Mail::Reporter.

$obj->AUTOLOAD()

Inherited, see "Error handling" in Mail::Reporter

$obj->notImplemented()

Inherited, see "Error handling" in Mail::Reporter


🧼 Cleanup 🧼

Extends "Cleanup" in Mail::Reporter.

  • $obj->DESTROY() β€” Perl calls this one automatically when a folder object isn't reachable by the rest of your program anymore, like it's being ghosted by the runtime πŸ’€. It improves on the base version β€” peep "Cleanup" in Mail::Reporter for the deets.

🌈 DETAILS 🌈

πŸ—‚οΈ Different kinds of folders

Okay so bestie, folders come in three general flavors: the ones that cram messages into one file, the ones that spread them out into a directory, and the ones that don't even let you touch the message data directly. Each flavor has its own base class, iykyk.

  • File based folders β€” Mail::Box::File

These keep the whole folder (aka the whole set of messages) inside one single file. The slay of it? Your folder is literally one file, so when you need to access everything all at once, it's speedy πŸƒβ€β™€οΈπŸ’¨.

The main downside compared to directory based folders is that you gotta build some whole system to keep the messages apart. Like MBOX drops a message separator line between messages, and that line can get confused with the actual message contents 😭.

So where file based folders are faster for grabbing everything at once, grabbing one message is (wayyy) slower, because the whole folder has to be read first. Though tbh, with directory based folders you have to figure out which message you actually need, and that's its own little hassle.

Examples of file based folders: MBOX, DBX, and NetScape ✨.

  • Directory based folders β€” Mail::Box::Dir

Instead of shoving multiple messages into one file, you can put each message in its own separate file and collect all those files in a directory to represent the folder πŸŽ€.

Main disadvantages: you get an enormous amount of tiny little files in your file-system πŸ™ˆ, and searching through the whole folder is extremely slow because sooo many files have to be opened to do it.

But the best feature of this whole setup is that each message is kept exactly as it was received, and can be processed with external scripts too β€” you don't even need a mail user agent (MUA) πŸ’….

Examples of directory organized folders: MH, Maildir, EMH and XMH.

  • Network (external) folders β€” Mail::Box::Net

See, both types above give you direct access to the message data. These ones? They hold the message data for you: you gotta request messages, or parts of them. These folders don't have a filename, file-system privileges or system locking to stress about, but typically they need a hostname, folder and message IDs, and authorization πŸ”.

Examples: the popular POP and IMAP, plus database oriented message storage.

πŸ‘‘ Available folder types

  • Mail::Box::Dbx (read only)

Dbx files are made by Outlook Express. Using the external (optional) Mail::Transport::Dbx module, you can read these folders, even when you're running MailBox on a UNIX/Linux platform 🐧.

Writing and deleting messages just isn't supported by the library, and therefore not by MailBox either. Read access is enough to do folder conversions, for instance.

  • Mail::Box::IMAP4 (partially)

The IMAP protocol is very complex, bestie, like it's giving final boss energy 😀. Some parts are implemented to create (sub-optimal but usable) IMAP clients. Besides that, there are also some parts for IMAP servers present. The most important lacking feature is support for encrypted connections πŸ’”.

  • Mail::Box::Maildir

Maildir folders have a directory for each folder. A folder directory contains "tmp", "new", and "cur" sub-directories, each containing messages with a different purpose. Files with new messages are created in "tmp", then moved to "new" (ready to be accepted). Later, they're moved to the "cur" directory (accepted) ✨. Each message is one file with a name starting with a timestamp. The name also contains flags about the status of the message.

Maildir folders can not be used on Windows by reason of file-name limitations on that platform 🚫.

  • Mail::Box::Mbox

A folder type in which all related messages are stored in one file. This is a very common folder type for UNIX πŸ’œ.

  • Mail::Box::MH

This folder creates a directory for each folder, and a message is one file inside that directory. The message files are numbered sequentially in order of arrival. A special .mh_sequences file maintains flags about the messages 🏷️.

  • Mail::Box::POP3 (read/delete only)

POP3 is a protocol you can use to retrieve messages from a remote system. After the connection to a POP server is made, the messages can be looked at and removed as if they're right there on the local system 🌐.

  • Mail::Box::Netzwert

The Netzwert folder type is optimized for mailbox handling on a cluster of systems with a shared NFS storage. The code is not released under GPL (yet) πŸ“.

Other folder types are on the (long) wishlist to get implemented. Please, help implementing more of them 🫢.

🧚 Folder class implementation

The class structure of folders is very close to that of messages. For instance, a Mail::Box::File::Message relates to a Mail::Box::File folder. The folder types are:

                       Mail::Box::Netzwert
    Mail::Box::Mbox    | Mail::Box::Maildir  Mail::Box::POP3
    |  Mail::Box::Dbx  | | Mail::Box::MH     |  Mail::Box::IMAP4
    |  |               | | |                 |  |
    |  |               | | |                 |  |
    Mail::Box::File   Mail::Box::Dir       Mail::Box::Net
          |                  |                   |
          `--------------.   |   .---------------'
                         |   |   |
                         Mail::Box
                             |
                             |
                Mail::Reporter (general base class)

By far most folder features are implemented in Mail::Box, so they're available to all folder types πŸ’…. Sometimes, features which appear in only some of the folder types get simulated for folders that miss them β€” like sub-folder support for MBOX ✨.


🚨 DIAGNOSTICS 🚨

  • Error: Folder $name is opened read-only

You can't write to this folder unless you've opened the folder to write or append with new(access), or the "force" option is set "true". Cast by write().

  • Warning: changes not written to read-only folder $name.

You opened the folder read-only β€” which is the default set by new(access) β€” made modifications, and now want to close it πŸ™ˆ. Set close(force) if you want to overrule the access mode, or close the folder with close(write) set to "NEVER". Cast by close().

  • Error: class $package does not implement method $method.

Fatal error: that specific $package (or one of its superclasses) doesn't implement this method where it should. This message means some other related classes do implement this method, however the class at hand does not πŸ‘€. Probably you should investigate this and probably inform the author of the package. Cast by notImplemented().

  • Error: copying failed for one message.

For some reason β€” like disk full, removed by external process, or read-protection β€” it's impossible to copy one of the messages 😭. Copying will proceed for the other messages. Cast by copyTo().

  • Error: destination folder $name is not writable.

The folder where the messages are copied to is not opened with write access (see new(access)). This has no relation with write permission to the folder which is controlled by your operating system. Cast by copyTo().

  • Warning: different messages with id $msgid.

The message id is discovered more than once within the same folder, but the content of the message seems to be different 🀨. This should not be possible: each message must be unique. Cast by messageId().

  • Warning: folder $name not deleted: not writable.

The folder must be opened with write access via new(access), otherwise removing it will be refused. So, you may have write-access according to the operating system, but that will not automatically mean that this "delete" method permits you to. The reverse remark is valid as well. Cast by delete().

  • Error: invalid timespan '$span'.

The string does not follow the strict rules of the time span syntax which is permitted as parameter. Cast by timespan2seconds().

  • Warning: message-id '$msgid' does not contain a domain.

According to the RFCs, message-ids need to contain a unique random part, then an "@", and then a domain name. This is made to avoid the creation of two messages with the same id. The warning emerges when the "@" is missing from the string. Cast by messageId().

  • Error: no folder name specified.

You didn't specify the name of a folder to be opened 🫠. Use the new(folder) option or set the "MAIL" environment variable. Cast by new().

  • Error: unknown value to folder->close(write => $when).

Cast by close().

  • Error: you cannot add a message which is already part of a folder to a new one. Please use moveTo or copyTo.

Cast by addMessage().


πŸ‘€ SEE ALSO πŸ‘€

This module is part of Mail-Box version 4.02, built on April 10, 2026. Website: http://perl.overmeer.net/CPAN/ 🌸


πŸ’œ LICENSE πŸ’œ

For contributors see file ChangeLog.

This software is copyright (c) 2001-2026 by Mark Overmeer.

This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself ✨.


πŸ› POD ERRORS πŸ›

Hey! The above document had some coding errors, which are explained below:

  • Around line 316:

Unterminated C< ... > sequence πŸ’€


and that's the whole page bestie, we ate and left no crumbs πŸ‘‘πŸ’… β€” this was perl v5.40.1, dated 2026-05-09, and it's the Mail::Box(3pm) manual, periodt πŸŽ€πŸ¦‹

πŸ“¦ where this page comes from