Convert Maildir to IMAP for all users with perl

Revision as of 02:17, 25 April 2009 by Gettyless (talk | contribs)

This script needs to be run directly from the main-maildir, so first change into it with "cd". For kmail you would need to execute "cd $HOME/.kde/share/apps/kmail/mail". This script works recursive, but as maildir seems not to handle subdirs, kmail creates subfolders in parallel with prefix and suffix. For example: The maildir "Work/Administration" is located in the path "". I don't know, if other mail programs work the same way, so you should check that before you begin.

The script will report some details about the message if the import fails. Major problems I encountered while importing (you should check too): - The maximum message size accepted by the server was to small - The folder could not be created due to some special characters used on filesystem entries

To be able to run this script, you also need to enable the imap clear text login with:

su - zimbra -c "zmprov mcf zimbraImapCleartextLoginEnabled TRUE"

You can/should disable this option after migration!

# by Andreas Kammlott, thanks to perl and its community

use HTTP::Date qw(str2time);
use HTTP::Date qw(parse_date);
use Date::Format;
use Mail::IMAPClient;
use Mail::Box::Maildir;
use Mail::Message::Convert::MailInternet;

# Open imap connection, adjust values to your needs
my $imap = Mail::IMAPClient->new(
   Server   => '',
   User     => 'test',
   Password => 'test12') || die "Could not connect to IMAP server.\n";

# A maildir contains a subdir "cur"
foreach my $dir (`find ./ -type d -name cur|sort`) {
   my $imdir = ""; # to store the path on the server (imap-directory)
   my $fsdir = ""; # to store the path of the maildir (filesystem)

   # Bring the path to an array, each directory needs to be handled
   my @fspath = split(/\//, $dir);

   # Replace the leading point and the tailing .directory to acquire
   # the path, which has to be created in imap, the "cur" don't need
   # to be stored
   for (my $i = 1; $i < @fspath-1; $i++) {
      $fsdir = "$fsdir$fspath[$i]/";
      if ($fspath[$i] =~ /\..*\.directory/) {
         $fspath[$i] =~ s/^\.(.+)\.directory$/$1/;
      $imdir = "$imdir$fspath[$i]/";

   # Create the mdir object, this object contains all the messages from 
   # the filesystem's maildir
   my $mdir = Mail::Box::Maildir->new(folder => $fsdir) 
      or print "Failed to create mdir object from \"$fsdir\"\n";

   # Process this directory only if it contains messages
   if (!$mdir->messages() > 0) {
      #print "Skipping... folder has been detected as empty.\n";

  # Check for the folder on the server (imap) and create if necessary
   if (!$imap->exists($imdir)) {
      # Jump to next folder if creation of this one fails
      if (!$imap->create($imdir)) { 
         print "ERROR: Could not create folder \"$imdir\".\n"; 

   # Now begin to push all messages
   foreach my $msg ($mdir->messages()) {
      my $subject = $msg->subject;
      my $msgdate = $msg->head->get('Date');
      my $msgid   = $msg->messageId;

      # Required date as RFC2060  dd-Mon-yyyy hh:mm:ss +0000
      # Provided format is Tue, 20 Jul 2004 09:55:16 +0200
      my $time = str2time($msgdate);
      (undef, undef, undef, undef, undef, undef, $tz) = parse_date($msgdate);
      my $date = time2str("%d-%h-%Y %H:%M:%S %z",$time,$tz) 
          or print "problem with time: $time and tz: $tz\n";

      # Create a converter object
      my $conv = Mail::Message::Convert::MailInternet->new;

      # Don't understand why the $conv->export($msg)->as_string() has to be called twice!
      my $exp = $conv->export($msg)->as_string();
      if (!$imap->append_string($imdir, $conv->export($msg)->as_string(), undef, $date)) {
         print "Failed to append message!\n"
Jump to: navigation, search