Easy Thunderbird Account Management Using MCD

Ford Thunderbird Console
From myoldpostcards

Continuing my series on Mission Control Desktop, this post covers some enhancements I made for configuring email accounts in Thunderbird.  The mail.* branch is one of the largest group of preferences you can manipulate using autoconfig.  Setting up the right combination of mail.accountmail.server, mail.identity and mail.smtpserver is just a little tricky.

With the exception of the special “local folders” account, an email account in Thunderbird has four components: an account, a server, one or more identities, and an smtp server.  There are a few preference strings gluing each of these elements together. Each account you create is differentiated from the others by a unique label, defaulting to account<num>, with incrementing numbers. You can also use your own, more meaningful labels, such as mail.account.work-email and mail.account.gmail.

Element Branch
Account mail.account.accountN Glues POP/IMAP, SMTP servers and identities together
Server mail.server.serverN Settings for the IMAP or POP server
Identity mail.identity.idN Name, email address, drafts & stationery folders
SMTP Server mail.smtpserver.smtpN Settings for the SMTP server
Glue settings
Accounts list mail.accountmanager.accounts Comma-separated list of mail.account labels
Default account mail.accountmanager.defaultaccount The label of the default mail account
SMTP servers mail.smtpservers Comma-separated list of mail.smtpserver server labels
Default SMTP mail.smtp.defaultserver The label of the default SMTP server

Here is a quick example:

// Identity
defaultPref("mail.identity.id1.fullName", "Dean Brundage");
defaultPref("mail.identity.id1.draft_folder", "imap://dean.brundage@example.com@mail.example.com/Drafts");
defaultPref("mail.identity.id1.smtpServer", "smtp1");
 
// IMAP server settings
defaultPref("mail.server.server1.type", "imap");
defaultPref("mail.server.server1.hostname", "mail.example.com");
// etc
 
// SMTP server settings
defaultPref("mail.smtpserver.smtp1.auth_method", 1);
defaultPref("mail.smtpserver.smtp1.hostname", "smtp.example.com");
// etc
 
// Glue it all together
defaultPref("mail.account.account1.identities", "id1");
defaultPref("mail.account.account1.server", "server1");
 
defaultPref("mail.accountmanager.accounts", "account1");
defaultPref("mail.accountmanager.defaultaccount", "account1");
 
defaultPref("mail.smtp.defaultserver", "smtp1");
defaultPref("mail.smtpservers", "smtp1");

As you add more email accounts the code can get unmanageable when you try to remember to twiddle the right branches so all the accounts, their identities and smtp servers show up. I would like to present an alternative.

This code builds upon the PreferenceFactory prototype covered earlier.  I put together an object prototype for an account, containing a server, one or more identities, and an smtp server.  There is also a singleton for the account manager.  Now you can instantiate an object and call preference setters on it.  The code is more readable and less error prone.

   // Create an email account
   var workAccount = AccountManager.newAccount({ isDefault: true,
                                                 label: "work",
                                                 type: "imap" });
 
   // Lock Preferences on the IMAP server
   workAccount.server.setPrefs({ hostname: "mail.example.com",
                                 type: "imap" }, "lock" );
   // Default Preferences
   workAccount.server.setPrefs({ check_new_mail: true,
                                 name: "Corporate eMail" });
 
  // Lock Preferences on the SMTP server
  workAccount.smtpServer.setPrefs({ auth_method: 1,    /* User/pass */
                                    username: "dean.brundage@example.com" }, "lock" );
  // Default Preferences
  workAccount.smtpServer.setPrefs({ description: "Corporate SMTP server",
                                     hostname: "mail.example.com"});
 
  // And now a personal account
  var myGmailAccount = AccountManager.newAccount({ label: "gmail", type: "imap" });
  // Go about setting preferences on myGmailAccount as before

Download the source

That block of code replaces the tedious series of defaultPref and lockPref required to set up an email account.  It produces preference settings like these:

defaultPref("mail.accountmanager.accounts", "work-account1,gmail-account2");
defaultPref("mail.accountmanager.defaultaccount", "work-account1");
 
defaultPref("mail.account.work-account1.identities", "work-id1");
defaultPref("mail.account.work-account1.server", "work-server1");
lockPref("mail.server.work-server1.type", "imap");
 
defaultPref("mail.server.gmail-server2.type", "imap");
// etc
 
defaultPref("mail.smtp.defaultserver", "work-smtp1");
defaultPref("mail.smtpservers", "work-smtp1");

Download the source

/**
   By Dean Brundage
   Originally published here:
     http://blog.deanandadie.net/2010/06/easy-thunderbird-account-management-using-mcd/
*/
 
 
/* "Inheritance" helper
   http://www.sitepoint.com/blogs/2006/01/17/javascript-inheritance/
   copy all of parent's prototype functions to descendant
*/
function copyPrototype(parent, descendant)
{  var sConstructor = parent.toString(); 
   var aMatch = sConstructor.match( /\s*function (.*)\(/ ); 
   if( aMatch != null )
   {  descendant.prototype[aMatch[1]] = parent;  } 
   for (var m in parent.prototype)
   {  descendant.prototype[m] = parent.prototype[m];  } 
};
 
 
/* "Base" for the Mail.xxxx objects
 
   This requires the PreferenceFactory prototype covered in a previous post:
     http://blog.deanandadie.net/2010/05/manufacturing-user-preferences-for-mcd/
*/
function Mail()
{  this.PreferenceFactory();  // super
   this.addPrefBranch("mail");
   return this;
}
 
copyPrototype( PreferenceFactory, Mail );
 
 
Mail.prototype.joinIds = function(collection,separator)
{  if( ! separator )
      separator = ",";
   if( collection )
   {  ret = collection[0].id
      for( i = 1; i < collection.length; i++ )
         ret = ret + separator + collection[i].id;
   }
   return ret;
};
 
 
/* A thunderbird email "account" consists of
      Mail.Server -- A mail server (IMAP or POP)
      Mail.SMTPServer -- A SMTP server
      One or more Mail.Identities -- Email address and name
 
   A thunderbird local folders "account" has only one Mail.Server, nothing else
      Yup, local folders are "servers"
*/
 
/* Object to manage accounts
      I recommend managing mail accounts with the AccountManager unless you
      know what you are doing.
   var myAccount = AccountManager.newAccount();
   myAccount.doStuff();
 
*/
var AccountManager = new Mail();
 
AccountManager.accounts = [];
AccountManager.addPrefBranch("accountmanager");
 
/* Create a new account
   Alerts Thunderbird to the presence of the new account
   See Mail.Account for valid options
*/
AccountManager.newAccount = function(opts)
{  if( ! opts )
      opts = { };
 
   acct = new Mail.Account(opts);
 
   if( opts.isDefault )
   {  acct.useSMTPServer( new Mail.SMTPServer(opts) );
      this.setDefaultAccount(acct,opts.lockLevel);
   } else
   {  acct.useSMTPServer( Mail.SMTPServer.defaultServer );
   }
 
   this.accounts.push(acct);
   this.setPref( "accounts", this.joinIds(this.accounts) );
   return acct;
}
 
 
AccountManager.setDefaultAccount = function(account,lockLevel)
{  
   this.setPref("defaultaccount", account.id, lockLevel );
 
   /* It's possible to create an account before setting the default
      SMTP server.  Clean them up if this new account is the default
      (Expect Mail.Account to inform Mail.SMTPServer of the new default)
   */
   for( i = 0; i < this.accounts.length; i++ )
      this.accounts[i].useSMTPServer(Mail.SMTPServer.defaultServer);
}
 
 
AccountManager.setLocalFolders = function(folders,lockLevel)
{  this.setPref("localfoldersserver", folders.id, lockLevel);
}
 
 
/* Creates a new generic mail account (identity, server, smtp)
   Arguments
     opts: A hash of options. Valid options are:
           "isDefault": true | false (default false)
           "label": string - A unique label for this account, server & identity
           "lockLevel": "default" | "lock" | "pref" (default "default")
           "type": imap | pop | localFolder
 
   This prototype creates an object representing an email account
   It exposes some objects:
      myAccount.directoryServer  // The (optional) LDAP2Server object
      myAccount.identities       // An array of identites
      myAccount.server           // The IMAP/POP/LocalFolder Mail.Server object
      myAccount.smtpServer       // The Mail.SMTPServer object
 
   (The LDAP2Server object is detailed in a separate post on http://blog.deanandadie.net/)
*/
Mail.Account = function(opts)
{  if( ! opts )
     opts = { };
 
   if( ! opts.type )
      throw("What's my type?");
 
   this.Mail();  // "super"
 
   this.id = "account" + ++Mail.Account.count;
   if( opts.label )
   {  this.id = opts.label + "-" + this.id;  }
 
   // Our preference branch is mail.account.accountN
   this.addPrefBranch( [ "account", this.id ] );
 
   this.identities = [];
 
   this.server = new Mail.Server(opts);  // Need this either way
 
   if( opts.type.match(/^^imap$|^pop$/i) )
   {
      this.server.setPref("type", opts.type, opts.lockLevel);
 
      // Important that addIdentity be before the SMTP server is set up
      this.addIdentity( new Mail.Identity(opts), opts.lockLevel );
 
   } else if( opts.type.match(/^localFolders?$/i) )
   {  this.folders = this.server;
      AccountManager.setLocalFolders(this.folders);
      this.folders.setPref("type", "none", opts.lockLevel );
 
   } else
   {  throw("unrecognized Mail.Account type: " + opts.type);  }
 
   this.useServer(this.server, opts.lockLevel);
 
}
 
copyPrototype( Mail, Mail.Account );
 
Mail.Account.count = 0;                                   // Fake class variable
 
 
// Expects a Mail.Identity object and, optionally, the preference locking level
Mail.Account.prototype.addIdentity = function(identity,lockLevel)
{  if( identity )
   {  if( ! this.identities.contains(identity) )
      {  this.identities.push(identity);
         this.setPref("identities", this.joinIds(this.identities), lockLevel );
      }
   }
}
 
 
/* Helper to set the SMTP server
   Expects a Mail.SMTPServer object & optionally, an options hash
   Valid options:
     lockLevel: lock | default | pref
     force: true | false
*/
Mail.Account.prototype.useDirectory = function(directory, opts)
{  if( ! opts )
      opts = { };
   if( directory )
   {  if( opts.force || typeof(this.directoryServer) == "undefined" )
      {  this.directoryServer = directory;
         for( i = 0; i < this.identities.length; i++ )
            this.identities[i].useDirectory( this.directoryServer, opts );
      }
   }
}
 
/* Helper to set the SMTP server
   Expects a Mail.SMTPServer object & optionally, an options hash
   Valid options:
     lockLevel: lock | default | pref
     force: true | false
*/
Mail.Account.prototype.useSMTPServer = function(smtp,opts)
{  if( ! opts )
      opts = { };
   if( smtp )
   {  if( opts.force || typeof(this.smtpServer) == "undefined" )
      {  this.smtpServer = smtp;
         for( i = 0; i < this.identities.length; i++ )
            this.identities[i].useSMTPServer( this.smtpServer, opts );
      }
   }
}
 
 
// Expects a Mail.Server object and, optionally, the preference locking level
Mail.Account.prototype.useServer = function(server,lockLevel)
{  if( server )
      this.setPref("server", server.id, lockLevel );
}
 
 
/* Creates a new generic mail server
   Arguments
     opts: A hash of options. Valid options are:
           label: string - A unique label for this account, server & identity
*/
Mail.Server = function(opts)
{  if( ! opts )
     opts = { };
 
   this.Mail();
 
   this.id = "server" + ++Mail.Server.count;
   if( opts.label )
   {  this.id = opts.label + "-" + this.id;  }
 
   // Our preference branch is mail.server.serverN
   this.addPrefBranch( [ "server", this.id ] );
}
 
copyPrototype( Mail, Mail.Server );
 
Mail.Server.count = 0;  // Fake class variable
 
 
/* Creates a new mail identity. (name, email address, etc)
   Arguments
     opts: A hash of options. Valid options are:
           "label": string - A unique label for this account, server & identity
*/
Mail.Identity = function(opts)
{  if( ! opts )
     opts = { };
 
   this.Mail();
 
   this.id = "id" + ++Mail.Identity.count;
   if( opts.label )
   {  this.id = opts.label + "-" + this.id;  }
 
   // Our preference branch is mail.identity.idN
   this.addPrefBranch( [ "identity", this.id ] );
   this.hasSMTPServer = false;
};
 
copyPrototype(Mail, Mail.Identity);
 
 
Mail.Identity.count = 0;  // Fake class variable
 
 
/* Expects a LDAP2Server object and a hash of options
   Valid options:
      lockLevel: lock | default | pref
*/
Mail.Identity.prototype.useDirectory = function(ldap2Server,opts)
{  if( ! opts )
      opts = { };
 
   if( ldap2Server )
   {  this.setPref("directoryServer", "ldap_2.servers." + ldap2Server.id, opts.lockLevel);
      this.setPref("overrideGlobal_Pref", true, opts.lockLevel);
   }
};
 
 
/* Expects a SMTPServer object and a hash of options
   Valid options:
      force: true | false
      lockLevel: lock | default | pref
*/
Mail.Identity.prototype.useSMTPServer = function(smtpServer,opts)
{  if( ! opts )
      opts = { }
   if( smtpServer )
   {  if( opts.force || ! this.hasSMTPServer )
      {  this.setPref("smtpServer", smtpServer.id, opts.lockLevel);
         this.hasSMTPServer = true;
      }
   }
};
 
 
/* Creates a new smtp server
   Artuments:
     opts: A hash of options. Valid options are:
       isDefault: true | false (default)
       label: A unique label for this smtp server
   Throws an error if there is already a default smtp server
*/
Mail.SMTPServer = function(opts)
{  if( ! opts )
     opts = { };
 
   this.Mail();
 
   this.id = "smtp" + ++Mail.SMTPServer.count;
   if( opts.label )
   {  this.id = opts.label + "-" + this.id;  }
 
   // Our perference branch is mail.smtpserver.smtpN
   this.addPrefBranch( [ "smtpserver", this.id ] );
 
   if( opts.isDefault && Mail.SMTPServer.defaultServer )
      throw("Default smtp server already configured to " + Mail.SMTPServer.defaultServer);
 
   if( opts.isDefault )
      this.setDefault();
 
   // Register this SMTP server
   if( ! Mail.SMTPServer.servers.contains(this) )
      Mail.SMTPServer.servers.push(this);
 
   // Update mail.smtpservers to include the new one
   defaultPref("mail.smtpservers",  this.joinIds(Mail.SMTPServer.servers) );
 
   return this;
};
 
copyPrototype(Mail, Mail.SMTPServer);
 
// Some "class" variables
Mail.SMTPServer.count = 0;
Mail.SMTPServer.defaultServer = undefined;
Mail.SMTPServer.servers = [];
 
 
Mail.SMTPServer.prototype.setDefault = function()
{  Mail.SMTPServer.defaultServer = this;
   defaultPref("mail.smtp.defaultserver", Mail.SMTPServer.defaultServer.id );
}

Leave a Reply