I decided to switch from simply forwarding *@marc-seeger.de to my marc.seeger_at_gmail_dot_com address to actually using "";http://www.google.com/a/help/intl/en/index.html">google apps for domains".

As I didn't want to lose my existing mails, I searched for a way to migrate my old gmail account.

There are some pop based solutions, but they suck as they:


  1. Don't preserve tags (as in "read";/"unread";)

  2. Don't preserve folders (or as they are called in gmail: "labels";

There are basically two solutions atm:
Using imapsync
Using ruby

I don't really trust the imapsync solution. Won't import the sent mail folder and I don't really know what it does.

The ruby solution is pretty awesome and works fine on my dreamhost shell (my DSL is simply too slow to start uploading 10 MB attachments). There are a bunch of patches in the comments. It would be awesome if this modded version would work, but it seems as if tumblr markup destroyed the code :-/


#!/usr/bin/env ruby
require ‘net/imap'

  1. Source server connection info.
    SOURCE_HOST = ‘imap.gmail.com'
    SOURCE_PORT = 993
    SOURCE_SSL = true
    SOURCE_USER = ‘gmail.old'
    SOURCE_PASS = ‘password1'
  2. Destination server connection info.
    DEST_HOST = ‘imap.gmail.com'
    DEST_PORT = 993
    DEST_SSL = true
    DEST_USER = ‘new@google_domain.com'
    DEST_PASS = ‘password2'
  1. Mapping of source folders to destination folders. The key is the name of the
  2. folder on the source server, the value is the name on the destination server.
  3. Any folder not specified here will be ignored. If a destination folder does
  4. not exist, it will be created.

FOLDERS = {

  1. INBOX' => ‘INBOX',
  2. ‘sourcefolder' => ‘gmailfolder'
    ‘[Gmail]/Sent Mail' => ‘[Gmail]/Sent Mail'

}

  1. Utility methods.
    def dd(message)
    puts "[#{DEST_HOST}] #{message}";
    end

def ds(message)
puts "[#{SOURCE_HOST}] #{message}";
end

  1. Connect and log into both servers.
    ds ‘connecting…'
    source = Net::IMAP.new(SOURCE_HOST, SOURCE_PORT, SOURCE_SSL)

ds ‘logging in…'
source.login(SOURCE_USER, SOURCE_PASS)

dd ‘connecting…'
dest = Net::IMAP.new(DEST_HOST, DEST_PORT, DEST_SSL)

dd ‘logging in…'
dest.login(DEST_USER, DEST_PASS)

  1. Loop through folders and copy messages.
    FOLDERS.each do |source_folder, dest_folder|
  2. Open source folder in read-only mode.
    begin
    ds "selecting folder ‘#{source_folder}'…";
    source.examine(source_folder)
    rescue => e
    ds "error: select failed: #{e}";
    next
    end
  1. Open (or create) destination folder in read-write mode.
    begin
    dd "selecting folder ‘#{dest_folder}'…";
    dest.select(dest_folder)
    rescue => e
    begin
    dd "folder not found; creating…";
    dest.create(dest_folder)
    dest.select(dest_folder)
    rescue => ee
    dd "error: could not create folder: #{e}";
    next
    end
    end
  1. Build a lookup hash of all message ids present in the destination folder.
    dest_info = {}

dd ‘analyzing existing messages…'
uids = dest.uid_search([‘ALL'])
if uids.length > 0
dest.uid_fetch(uids, [‘ENVELOPE']).each do |data|
dest_info[data.attr[‘ENVELOPE'].message_id] = true
end
end

  1. Loop through all messages in the source folder.
    uids = source.uid_search([‘ALL'])
    if uids.length > 0
    source.uid_fetch(uids, [‘ENVELOPE']).each do |data|
    mid = data.attr[‘ENVELOPE'].message_id
  1. If this message is already in the destination folder, skip it.
    next if dest_info[mid]
  1. Download the full message body from the source folder.
    ds "downloading message #{mid}…";
    msg = source.uid_fetch(data.attr[‘UID'], [‘RFC822', ‘FLAGS',
    INTERNALDATE']).first
  1. Append the message to the destination folder, preserving flags and
  2. internal timestamp.
  3. Append the message to the destination folder, preserving flags and
  4. internal timestamp.
    dd "storing message #{mid}…";
    success = false
    begin
    dest.append(dest_folder, msg.attr[‘RFC822'], msg.attr[‘FLAGS'], msg.attr[‘INTERNALDATE'])
    success = true
    rescue Net::IMAP::NoResponseError => e
    puts "Got exception: #{e.message}. Retrying…";
    sleep 1
    end until success
    end
    end

source.close
dest.close
end

puts ‘done'

.

Comments