Merge branch 'master' into exp-nosql

* master:
  Fix all of our pom.xml versions to be 2.1-SNAPSHOT
  Use internal templates to simplify minor formatting commands.
  Convert 3 email classes to file based templates.
  Convert the Abandoned and MergeFail email classes to templates
  Use a template to set the contents of the CommentEmails.
  Use a template to set the footer on ChangeEmails.
  Use a template to set the contents of the MergedEmails.
  Use a template to set the subject line.
  Add framework for using velocity templates in email classes
  Add ability to deactivate a user when they leave the project.

Conflicts:
	gerrit-httpd/src/main/java/com/google/gerrit/httpd/ProjectDigestFilter.java
	gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/SuggestServiceImpl.java
	gerrit-server/src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java
	gerrit-server/src/main/java/com/google/gerrit/server/mail/ChangeEmail.java
	gerrit-server/src/main/java/com/google/gerrit/server/mail/OutgoingEmail.java
	gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaVersion.java

Change-Id: Iac92b56e10f9cc4063fc0ec3cea6c79a5a087994
diff --git a/Documentation/config-mail.txt b/Documentation/config-mail.txt
new file mode 100644
index 0000000..168bbfe
--- /dev/null
+++ b/Documentation/config-mail.txt
@@ -0,0 +1,172 @@
+Gerrit Code Review - Mail Templates
+===================================
+
+Gerrit uses velocity templates for the bulk of the standard mails it sends out.
+There are builtin default templates which are used if they are not overridden.
+These defaults are also provided as examples so that administrators may copy
+them and easily modify them to tweak their contents.
+
+
+Template Locations and Extensions:
+----------------------------------
+
+The default example templates reside under:  `'$site_path'/etc/mail` and are
+terminated with the double extension `.vm.example`. Modifying these example
+files will have no effect on the behavior of Gerrit.  However, copying an
+example template to an equivalently named file without the `.example` extension
+and modifying it will allow an administrator to customize the template.
+
+
+Supported Mail Templates:
+-------------------------
+
+Each mail that Gerrit sends out is controlled by at least one template, these
+are listed below.  Change emails are influenced by two additional templates,
+one to set the subject line, and one to set the footer which gets appended to
+all the change emails (see `ChangeSubject.vm` and `ChangeFooter.vm` below.)
+
+Abandoned.vm
+~~~~~~~~~~~~
+
+The `Abandoned.vm` template will determine the contents of the email related
+to a change being abandoned.  It is a `ChangeEmail`: see `ChangeSubject.vm` and
+`ChangeFooter.vm`.
+
+ChangeFooter.vm
+~~~~~~~~~~~~~~~
+
+The `ChangeFooter.vm` template will determine the contents of the footer
+text that will be appended to emails related to changes (all `ChangeEmails)`.
+
+ChangeSubject.vm
+~~~~~~~~~~~~~~~~
+
+The `ChangeSubject.vm` template will determine the contents of the email
+subject line for ALL emails related to changes.
+
+Comment.vm
+~~~~~~~~~~
+
+The `Comment.vm` template will determine the contents of the email related to
+a user submitting comments on changes.  It is a `ChangeEmail`: see
+
+Merged.vm
+~~~~~~~~~
+
+The `Merged.vm` template will determine the contents of the email related to
+a change successfully merged to the head.  It is a `ChangeEmail`: see
+`ChangeSubject.vm` and `ChangeFooter.vm`.
+
+MergeFail.vm
+~~~~~~~~~~~~
+
+The `MergeFail.vm` template will determine the contents of the email related
+to a failure upon attempting to merge a change to the head.  It is a
+
+NewChange.vm
+~~~~~~~~~~~~
+
+The `NewChange.vm` template will determine the contents of the email related
+to a user submitting a new change for review. It is a `ChangeEmail`: see
+`ChangeSubject.vm` and `ChangeFooter.vm`.
+
+RegisterNewEmail.vm
+~~~~~~~~~~~~~~~~~~~
+
+The `RegisterNewEmail.vm` template will determine the contents of the email
+related to registering new email accounts.
+
+ReplacePatchSet.vm
+~~~~~~~~~~~~~~~~~~
+
+The `ReplacePatchSet.vm` template will determine the contents of the email
+related to a user submitting a new patchset for a change.  It is a
+`ChangeEmail`: see `ChangeSubject.vm` and `ChangeFooter.vm`.
+
+
+Mail Variables and Methods
+--------------------------
+
+Mail templates can access and display objects currently made available to them
+via the velocity context.  While the base objects are documented here, it is
+possible to call public methods on these objects from templates.  Those methods
+are not documented here since they could change with every release.  As these
+templates are meant to be modified only by a qualified sysadmin, it is accepted
+that writing templates for Gerrit emails is likely to require some basic
+knowledge of the class structure to be useful.  Browsing the source code might
+be necessary for anything more than a minor formatting change.
+
+Warning
+~~~~~~~
+
+Be aware that modifying templates can cause them to fail to parse and therefor
+not send out the actual email, or worse, calling methods on the available
+objects could have internal side effects which would adversely affect the
+health of your Gerrit server and/or data.
+
+All OutgoingEmails
+~~~~~~~~~~~~~~~~~~
+
+All outgoing emails have the following variables available to them:
+
+$email::
++
+A reference to the class constructing the current `OutgoingEmail`.  With this
+reference it is possible to call any public method on the OutgoingEmail class
+or the current child class inherited from it.
+
+$messageClass::
++
+A String containing the messageClass
+
+$StringUtils::
++
+A reference to the Apache `StringUtils` class.  This can be very useful for
+formatting strings.
+
+Change Emails
+~~~~~~~~~~~~~
+
+All change related emails have the following additional variables available to them:
+
+$change::
++
+A reference to the current `Change` object
+
+$changeId::
++
+Id of the current change (a `Change.Key`)
+
+$coverLetter::
++
+The text of the `ChangeMessage`
+
+$branch::
++
+A reference to the branch of this change (a `Branch.NameKey`)
+
+$fromName::
++
+The name of the from user
+
+$projectName::
++
+The name of this change's project
+
+$patchSet::
++
+A reference to the current `PatchSet`
+
+$patchSetInfo::
++
+A reference to the current `PatchSetInfo`
+
+
+See Also
+--------
+
+* link:http://velocity.apache.org/[velocity]
+
+GERRIT
+------
+Part of link:index.html[Gerrit Code Review]
diff --git a/Documentation/index.txt b/Documentation/index.txt
index 1e13e8d..e419455 100644
--- a/Documentation/index.txt
+++ b/Documentation/index.txt
@@ -31,6 +31,7 @@
 * link:config-sso.html[Single Sign-On Systems]
 * link:config-apache2.html[Apache 2 Reverse Proxy]
 * link:config-hooks.html[Hooks]
+* link:config-mail.html[Mail Templates]
 
 Developer Documentation
 -----------------------
diff --git a/Documentation/licenses.txt b/Documentation/licenses.txt
index 6e76303..6d543d5 100644
--- a/Documentation/licenses.txt
+++ b/Documentation/licenses.txt
@@ -31,6 +31,7 @@
 Apache MINA                 <<apache2,Apache License 2.0>>
 Apache Tomact Servlet API   <<apache2,Apache License 2.0>>
 Apache SSHD                 <<apache2,Apache License 2.0>>, see also <<sshd,NOTICE>>
+Apache Velocity             <<apache2,Apache License 2.0>>
 Apache Xerces               <<apache2,Apache License 2.0>>
 OpenID4Java                 <<apache2,Apache License 2.0>>
 Neko HTML                   <<apache2,Apache License 2.0>>
diff --git a/gerrit-common/pom.xml b/gerrit-common/pom.xml
index adac3a8..af03191 100644
--- a/gerrit-common/pom.xml
+++ b/gerrit-common/pom.xml
@@ -22,7 +22,7 @@
   <parent>
     <groupId>com.google.gerrit</groupId>
     <artifactId>gerrit-parent</artifactId>
-    <version>2.1.4-SNAPSHOT</version>
+    <version>2.1-SNAPSHOT</version>
   </parent>
 
   <artifactId>gerrit-common</artifactId>
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/ReviewerResult.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/ReviewerResult.java
index 19772fc..678ec79 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/ReviewerResult.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/ReviewerResult.java
@@ -50,6 +50,9 @@
       /** Name supplied does not match to a registered account. */
       ACCOUNT_NOT_FOUND,
 
+      /** The account is inactive. */
+      ACCOUNT_INACTIVE,
+
       /** The account is not permitted to see the change. */
       CHANGE_NOT_VISIBLE,
 
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/SuggestService.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/SuggestService.java
index 164df43..9dae169 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/SuggestService.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/SuggestService.java
@@ -28,7 +28,7 @@
   void suggestProjectNameKey(String query, int limit,
       AsyncCallback<List<Project.NameKey>> callback);
 
-  void suggestAccount(String query, int limit,
+  void suggestAccount(String query, Boolean enabled, int limit,
       AsyncCallback<List<AccountInfo>> callback);
 
   void suggestAccountGroup(String query, int limit,
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/errors/InactiveAccountException.java b/gerrit-common/src/main/java/com/google/gerrit/common/errors/InactiveAccountException.java
new file mode 100644
index 0000000..6ae5eb6
--- /dev/null
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/errors/InactiveAccountException.java
@@ -0,0 +1,26 @@
+// Copyright (C) 2010 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.common.errors;
+
+/** Error indicating the account is currently inactive. */
+public class InactiveAccountException extends Exception {
+  private static final long serialVersionUID = 1L;
+
+  public static final String MESSAGE = "Account Inactive: ";
+
+  public InactiveAccountException(String who) {
+    super(MESSAGE + who);
+  }
+}
diff --git a/gerrit-ehcache/pom.xml b/gerrit-ehcache/pom.xml
index 8e2e2dd..a7c6cf5 100644
--- a/gerrit-ehcache/pom.xml
+++ b/gerrit-ehcache/pom.xml
@@ -22,7 +22,7 @@
   <parent>
     <groupId>com.google.gerrit</groupId>
     <artifactId>gerrit-parent</artifactId>
-    <version>2.1.4-SNAPSHOT</version>
+    <version>2.1-SNAPSHOT</version>
   </parent>
 
   <artifactId>gerrit-ehcache</artifactId>
diff --git a/gerrit-gwtdebug/pom.xml b/gerrit-gwtdebug/pom.xml
index 9557227..d5a67e1 100644
--- a/gerrit-gwtdebug/pom.xml
+++ b/gerrit-gwtdebug/pom.xml
@@ -22,7 +22,7 @@
   <parent>
     <groupId>com.google.gerrit</groupId>
     <artifactId>gerrit-parent</artifactId>
-    <version>2.1.4-SNAPSHOT</version>
+    <version>2.1-SNAPSHOT</version>
   </parent>
 
   <artifactId>gerrit-gwtdebug</artifactId>
diff --git a/gerrit-gwtui/pom.xml b/gerrit-gwtui/pom.xml
index 29c5b01..9a27d12 100644
--- a/gerrit-gwtui/pom.xml
+++ b/gerrit-gwtui/pom.xml
@@ -22,7 +22,7 @@
   <parent>
     <groupId>com.google.gerrit</groupId>
     <artifactId>gerrit-parent</artifactId>
-    <version>2.1.4-SNAPSHOT</version>
+    <version>2.1-SNAPSHOT</version>
   </parent>
 
   <artifactId>gerrit-gwtui</artifactId>
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritConstants.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritConstants.java
index 3f263d2..b32a32d 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritConstants.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritConstants.java
@@ -46,6 +46,8 @@
   String nameAlreadyUsedBody();
   String noSuchAccountTitle();
 
+  String inactiveAccountBody();
+
   String menuAll();
   String menuAllOpen();
   String menuAllMerged();
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritConstants.properties b/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritConstants.properties
index 83a06e4..3363015 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritConstants.properties
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritConstants.properties
@@ -29,6 +29,8 @@
 nameAlreadyUsedBody = The name is already in use.
 noSuchAccountTitle = Code Review - Unknown User
 
+inactiveAccountBody = This user is currently inactive.
+
 menuAll = All
 menuAllOpen = Open
 menuAllMerged = Merged
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ApprovalTable.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ApprovalTable.java
index f850a33..23dd635 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ApprovalTable.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ApprovalTable.java
@@ -188,6 +188,10 @@
                     r.append(Util.M.accountNotFound(e.getName()));
                     break;
 
+                  case ACCOUNT_INACTIVE:
+                    r.append(Util.M.accountInactive(e.getName()));
+                    break;
+
                   case CHANGE_NOT_VISIBLE:
                     r.append(Util.M.changeNotVisibleTo(e.getName()));
                     break;
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeMessages.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeMessages.java
index 51e2bc5..fd7c363 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeMessages.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeMessages.java
@@ -47,6 +47,7 @@
   String changeQueryPageTitle(String query);
 
   String accountNotFound(String who);
+  String accountInactive(String who);
   String changeNotVisibleTo(String who);
 
   String anonymousDownload(String protocol);
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeMessages.properties b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeMessages.properties
index 5d2d3a5..80f9dab 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeMessages.properties
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeMessages.properties
@@ -28,6 +28,7 @@
 changeQueryPageTitle = Search for {0}
 
 accountNotFound = {0} is not a registered user.
+accountInactive = {0} is not an active user.
 changeNotVisibleTo = {0} cannot access the change.
 
 anonymousDownload = Anonymous {0}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/rpc/GerritCallback.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/rpc/GerritCallback.java
index fa028d7..f29742a 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/rpc/GerritCallback.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/rpc/GerritCallback.java
@@ -17,6 +17,7 @@
 import com.google.gerrit.client.ErrorDialog;
 import com.google.gerrit.client.Gerrit;
 import com.google.gerrit.client.NotSignedInDialog;
+import com.google.gerrit.common.errors.InactiveAccountException;
 import com.google.gerrit.common.errors.NameAlreadyUsedException;
 import com.google.gerrit.common.errors.NoSuchAccountException;
 import com.google.gerrit.common.errors.NoSuchEntityException;
@@ -37,6 +38,9 @@
     } else if (isNoSuchEntity(caught)) {
       new ErrorDialog(Gerrit.C.notFoundBody()).center();
 
+    } else if (isInactiveAccount(caught)) {
+      new ErrorDialog(Gerrit.C.inactiveAccountBody()).center();
+
     } else if (isNoSuchAccount(caught)) {
       final String msg = caught.getMessage();
       final String who = msg.substring(NoSuchAccountException.MESSAGE.length());
@@ -71,6 +75,11 @@
         && caught.getMessage().equals(NoSuchEntityException.MESSAGE);
   }
 
+  protected static boolean isInactiveAccount(final Throwable caught) {
+    return caught instanceof RemoteJsonException
+        && caught.getMessage().startsWith(InactiveAccountException.MESSAGE);
+  }
+
   private static boolean isNoSuchAccount(final Throwable caught) {
     return caught instanceof RemoteJsonException
         && caught.getMessage().startsWith(NoSuchAccountException.MESSAGE);
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/AccountSuggestOracle.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/AccountSuggestOracle.java
index 441878f..bcf6438 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/AccountSuggestOracle.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/AccountSuggestOracle.java
@@ -30,7 +30,8 @@
   public void onRequestSuggestions(final Request req, final Callback callback) {
     RpcStatus.hide(new Runnable() {
       public void run() {
-        SuggestUtil.SVC.suggestAccount(req.getQuery(), req.getLimit(),
+        SuggestUtil.SVC.suggestAccount(req.getQuery(), Boolean.TRUE,
+            req.getLimit(),
             new GerritCallback<List<AccountInfo>>() {
               public void onSuccess(final List<AccountInfo> result) {
                 final ArrayList<AccountSuggestion> r =
diff --git a/gerrit-httpd/pom.xml b/gerrit-httpd/pom.xml
index b02b142..39b2788 100644
--- a/gerrit-httpd/pom.xml
+++ b/gerrit-httpd/pom.xml
@@ -22,7 +22,7 @@
   <parent>
     <groupId>com.google.gerrit</groupId>
     <artifactId>gerrit-parent</artifactId>
-    <version>2.1.4-SNAPSHOT</version>
+    <version>2.1-SNAPSHOT</version>
   </parent>
 
   <artifactId>gerrit-httpd</artifactId>
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/ProjectDigestFilter.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/ProjectDigestFilter.java
index de1eb15..2118b9c 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/ProjectDigestFilter.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/ProjectDigestFilter.java
@@ -146,6 +146,11 @@
       return false;
     }
 
+    if (!FutureUtil.get(accountCache.getAccount(who.getAccountId())).isActive()) {
+      rsp.sendError(SC_UNAUTHORIZED);
+      return false;
+    }
+
     final String A1 = username + ":" + realm + ":" + passwd;
     final String A2 = method + ":" + uri;
 
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/SuggestServiceImpl.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/SuggestServiceImpl.java
index dc8fba5..4595520 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/SuggestServiceImpl.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/SuggestServiceImpl.java
@@ -84,8 +84,8 @@
     });
   }
 
-  public void suggestAccount(final String query, final int limit,
-      final AsyncCallback<List<AccountInfo>> callback) {
+  public void suggestAccount(final String query, final Boolean active,
+      final int limit, final AsyncCallback<List<AccountInfo>> callback) {
     run(callback, new Action<List<AccountInfo>>() {
       public List<AccountInfo> run(final ReviewDb db) throws OrmException {
         final String a = query;
@@ -95,12 +95,16 @@
 
         LinkedHashMap<Account.Id, AccountInfo> res = Maps.newLinkedHashMap();
         for (Account p : db.accounts().suggestByFullName(a, b, n)) {
-          res.put(p.getId(), new AccountInfo(p));
+          if (active == null || active == p.isActive()) {
+            res.put(p.getId(), new AccountInfo(p));
+          }
         }
         if (res.size() < n) {
           for (Account p : db.accounts().suggestByPreferredEmail(a, b,
               n - res.size())) {
-            res.put(p.getId(), new AccountInfo(p));
+            if (active == null || active == p.isActive()) {
+              res.put(p.getId(), new AccountInfo(p));
+            }
           }
         }
         if (res.size() < n) {
@@ -115,9 +119,11 @@
 
           for (Map.Entry<String, Future<Account>> ent : want.entrySet()) {
             Account p = FutureUtil.get(ent.getValue());
-            AccountInfo info = new AccountInfo(p);
-            info.setPreferredEmail(ent.getKey());
-            res.put(p.getId(), info);
+            if (active == null || active == p.isActive()) {
+              AccountInfo info = new AccountInfo(p);
+              info.setPreferredEmail(ent.getKey());
+              res.put(p.getId(), info);
+            }
           }
         }
         return new ArrayList<AccountInfo>(res.values());
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/GroupAdminServiceImpl.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/GroupAdminServiceImpl.java
index 5dfc09e..eb12537 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/GroupAdminServiceImpl.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/GroupAdminServiceImpl.java
@@ -17,6 +17,7 @@
 import com.google.common.util.concurrent.ListenableFuture;
 import com.google.gerrit.common.data.GroupAdminService;
 import com.google.gerrit.common.data.GroupDetail;
+import com.google.gerrit.common.errors.InactiveAccountException;
 import com.google.gerrit.common.errors.NameAlreadyUsedException;
 import com.google.gerrit.common.errors.NoSuchAccountException;
 import com.google.gerrit.common.errors.NoSuchEntityException;
@@ -225,6 +226,9 @@
         }
 
         final Account a = findAccount(nameOrEmail);
+        if (!a.isActive()) {
+          throw new Failure(new InactiveAccountException(a.getFullName()));
+        }
         if (!control.canAdd(a.getId())) {
           throw new Failure(new NoSuchEntityException());
         }
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/patch/AddReviewer.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/patch/AddReviewer.java
index ae36388..9d508e2 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/patch/AddReviewer.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/patch/AddReviewer.java
@@ -94,6 +94,11 @@
             ReviewerResult.Error.Type.ACCOUNT_NOT_FOUND, nameOrEmail));
         continue;
       }
+      if (!account.isActive()) {
+        result.addError(new ReviewerResult.Error(
+            ReviewerResult.Error.Type.ACCOUNT_INACTIVE, nameOrEmail));
+        continue;
+      }
 
       final IdentifiedUser user = identifiedUserFactory.create(account.getId());
       if (!control.forUser(user).isVisible()) {
diff --git a/gerrit-launcher/pom.xml b/gerrit-launcher/pom.xml
index 1000777..3c9c979 100644
--- a/gerrit-launcher/pom.xml
+++ b/gerrit-launcher/pom.xml
@@ -22,7 +22,7 @@
   <parent>
     <groupId>com.google.gerrit</groupId>
     <artifactId>gerrit-parent</artifactId>
-    <version>2.1.4-SNAPSHOT</version>
+    <version>2.1-SNAPSHOT</version>
   </parent>
 
   <artifactId>gerrit-launcher</artifactId>
diff --git a/gerrit-main/pom.xml b/gerrit-main/pom.xml
index 10cb14a..a46aa4f 100644
--- a/gerrit-main/pom.xml
+++ b/gerrit-main/pom.xml
@@ -22,7 +22,7 @@
   <parent>
     <groupId>com.google.gerrit</groupId>
     <artifactId>gerrit-parent</artifactId>
-    <version>2.1.4-SNAPSHOT</version>
+    <version>2.1-SNAPSHOT</version>
   </parent>
 
   <artifactId>gerrit-main</artifactId>
diff --git a/gerrit-patch-commonsnet/pom.xml b/gerrit-patch-commonsnet/pom.xml
index df64d13..5bbae03 100644
--- a/gerrit-patch-commonsnet/pom.xml
+++ b/gerrit-patch-commonsnet/pom.xml
@@ -22,7 +22,7 @@
   <parent>
     <groupId>com.google.gerrit</groupId>
     <artifactId>gerrit-parent</artifactId>
-    <version>2.1.4-SNAPSHOT</version>
+    <version>2.1-SNAPSHOT</version>
   </parent>
 
   <artifactId>gerrit-patch-commonsnet</artifactId>
diff --git a/gerrit-patch-jgit/pom.xml b/gerrit-patch-jgit/pom.xml
index 68a08c3..f30eace 100644
--- a/gerrit-patch-jgit/pom.xml
+++ b/gerrit-patch-jgit/pom.xml
@@ -22,7 +22,7 @@
   <parent>
     <groupId>com.google.gerrit</groupId>
     <artifactId>gerrit-parent</artifactId>
-    <version>2.1.4-SNAPSHOT</version>
+    <version>2.1-SNAPSHOT</version>
   </parent>
 
   <artifactId>gerrit-patch-jgit</artifactId>
diff --git a/gerrit-pgm/pom.xml b/gerrit-pgm/pom.xml
index 8dbf324..d3eee42 100644
--- a/gerrit-pgm/pom.xml
+++ b/gerrit-pgm/pom.xml
@@ -22,7 +22,7 @@
   <parent>
     <groupId>com.google.gerrit</groupId>
     <artifactId>gerrit-parent</artifactId>
-    <version>2.1.4-SNAPSHOT</version>
+    <version>2.1-SNAPSHOT</version>
   </parent>
 
   <artifactId>gerrit-pgm</artifactId>
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/SitePathInitializer.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/SitePathInitializer.java
index 74e7548..dae0893 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/SitePathInitializer.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/SitePathInitializer.java
@@ -25,11 +25,13 @@
 import com.google.gerrit.pgm.Init;
 import com.google.gerrit.pgm.util.ConsoleUI;
 import com.google.gerrit.server.config.SitePaths;
+import com.google.gerrit.server.mail.OutgoingEmail;
 import com.google.inject.Binding;
 import com.google.inject.Inject;
 import com.google.inject.Injector;
 import com.google.inject.TypeLiteral;
 
+import java.io.File;
 import java.util.ArrayList;
 import java.util.List;
 
@@ -66,6 +68,7 @@
     mkdir(site.etc_dir);
     mkdir(site.lib_dir);
     mkdir(site.logs_dir);
+    mkdir(site.mail_dir);
     mkdir(site.static_dir);
 
     for (InitStep step : steps) {
@@ -82,11 +85,27 @@
     extract(site.gerrit_sh, Init.class, "gerrit.sh");
     chmod(0755, site.gerrit_sh);
 
+    extractMailExample("Abandoned.vm");
+    extractMailExample("ChangeFooter.vm");
+    extractMailExample("ChangeSubject.vm");
+    extractMailExample("Comment.vm");
+    extractMailExample("Merged.vm");
+    extractMailExample("MergeFail.vm");
+    extractMailExample("NewChange.vm");
+    extractMailExample("RegisterNewEmail.vm");
+    extractMailExample("ReplacePatchSet.vm");
+
     if (!ui.isBatch()) {
       System.err.println();
     }
   }
 
+  private void extractMailExample(String orig) throws Exception {
+    File ex = new File(site.mail_dir, orig + ".example");
+    extract(ex, OutgoingEmail.class, orig);
+    chmod(0444, ex);
+  }
+
   private static List<InitStep> stepsOf(final Injector injector) {
     final ArrayList<InitStep> r = new ArrayList<InitStep>();
     for (Binding<InitStep> b : all(injector)) {
diff --git a/gerrit-prettify/pom.xml b/gerrit-prettify/pom.xml
index 053d399..18ba674 100644
--- a/gerrit-prettify/pom.xml
+++ b/gerrit-prettify/pom.xml
@@ -22,7 +22,7 @@
   <parent>
     <groupId>com.google.gerrit</groupId>
     <artifactId>gerrit-parent</artifactId>
-    <version>2.1.4-SNAPSHOT</version>
+    <version>2.1-SNAPSHOT</version>
   </parent>
 
   <artifactId>gerrit-prettify</artifactId>
diff --git a/gerrit-reviewdb/pom.xml b/gerrit-reviewdb/pom.xml
index 5626739..d81b068 100644
--- a/gerrit-reviewdb/pom.xml
+++ b/gerrit-reviewdb/pom.xml
@@ -22,7 +22,7 @@
   <parent>
     <groupId>com.google.gerrit</groupId>
     <artifactId>gerrit-parent</artifactId>
-    <version>2.1.4-SNAPSHOT</version>
+    <version>2.1-SNAPSHOT</version>
   </parent>
 
   <artifactId>gerrit-reviewdb</artifactId>
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/Account.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/Account.java
index 8468834..ee65c55 100644
--- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/Account.java
+++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/Account.java
@@ -130,6 +130,10 @@
   @Column(id = 6, name = Column.NONE)
   protected AccountGeneralPreferences generalPreferences;
 
+  /** Is this user active */
+  @Column(id = 7)
+  protected boolean inactive;
+
   /** <i>computed</i> the username selected from the identities. */
   protected String userName;
 
@@ -199,6 +203,14 @@
     contactFiledOn = new Timestamp(System.currentTimeMillis());
   }
 
+  public boolean isActive() {
+    return ! inactive;
+  }
+
+  public void setActive(boolean active) {
+    inactive = ! active;
+  }
+
   /** @return the computed user name for this account */
   public String getUserName() {
     return userName;
diff --git a/gerrit-server/pom.xml b/gerrit-server/pom.xml
index 947930c..d056ab3 100644
--- a/gerrit-server/pom.xml
+++ b/gerrit-server/pom.xml
@@ -22,7 +22,7 @@
   <parent>
     <groupId>com.google.gerrit</groupId>
     <artifactId>gerrit-parent</artifactId>
-    <version>2.1.4-SNAPSHOT</version>
+    <version>2.1-SNAPSHOT</version>
   </parent>
 
   <artifactId>gerrit-server</artifactId>
@@ -34,6 +34,16 @@
 
   <dependencies>
     <dependency>
+      <groupId>log4j</groupId>
+      <artifactId>log4j</artifactId>
+    </dependency>
+
+    <dependency>
+      <groupId>org.apache.velocity</groupId>
+      <artifactId>velocity</artifactId>
+    </dependency>
+
+    <dependency>
       <groupId>org.eclipse.jgit</groupId>
       <artifactId>org.eclipse.jgit</artifactId>
     </dependency>
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountManager.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountManager.java
index fcef20c..3a241d3 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountManager.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountManager.java
@@ -97,7 +97,7 @@
    * @param who identity of the user, with any details we received about them.
    * @return the result of authenticating the user.
    * @throws AccountException the account does not exist, and cannot be created,
-   *         or exists, but cannot be located.
+   *         or exists, but cannot be located, or is inactive.
    */
   public AuthResult authenticate(AuthRequest who) throws AccountException {
     who = realm.authenticate(who);
@@ -111,9 +111,14 @@
           //
           return create(db, who);
 
-        } else {
-          // Account exists, return the identity to the caller.
-          //
+        } else { // Account exists
+
+          Account act = db.accounts().get(id.getAccountId());
+          if (act == null || !act.isActive()) {
+            throw new AccountException("Authentication error, account inactive");
+          }
+
+          // return the identity to the caller.
           update(db, who, id);
           return new AuthResult(id.getAccountId(), key, false);
         }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java
index e98e297..5ebaada 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java
@@ -17,6 +17,7 @@
 import static com.google.inject.Scopes.SINGLETON;
 
 import com.google.gerrit.common.data.ApprovalTypes;
+import com.google.gerrit.lifecycle.LifecycleListener;
 import com.google.gerrit.lifecycle.LifecycleModule;
 import com.google.gerrit.reviewdb.AccountGroup;
 import com.google.gerrit.reviewdb.AuthType;
@@ -63,15 +64,53 @@
 import com.google.inject.Inject;
 import com.google.inject.TypeLiteral;
 
+import org.apache.velocity.app.Velocity;
+import org.apache.velocity.runtime.RuntimeConstants;
 import org.eclipse.jgit.lib.Config;
 import org.eclipse.jgit.lib.PersonIdent;
 
+import java.util.Properties;
 import java.util.Set;
 
+
 /** Starts global state with standard dependencies. */
 public class GerritGlobalModule extends FactoryModule {
   private final AuthType loginType;
 
+  public static class VelocityLifecycle implements LifecycleListener {
+    private final SitePaths site;
+
+    @Inject
+    VelocityLifecycle(final SitePaths site) {
+      this.site = site;
+    }
+
+    @Override
+    public void start() {
+      String rl = "resource.loader";
+      String pkg = "org.apache.velocity.runtime.resource.loader";
+      Properties p = new Properties();
+
+      p.setProperty(rl, "file, class");
+      p.setProperty("file." + rl + ".class", pkg + ".FileResourceLoader");
+      p.setProperty("file." + rl + ".path", site.mail_dir.getAbsolutePath());
+      p.setProperty("class." + rl + ".class", pkg + ".ClasspathResourceLoader");
+      p.setProperty(RuntimeConstants.RUNTIME_LOG_LOGSYSTEM_CLASS,
+              "org.apache.velocity.runtime.log.SimpleLog4JLogSystem" );
+      p.setProperty("runtime.log.logsystem.log4j.category", "velocity");
+
+      try {
+        Velocity.init(p);
+      } catch(Exception e) {
+        throw new RuntimeException(e);
+      }
+    }
+
+    @Override
+    public void stop() {
+    }
+  }
+
   @Inject
   GerritGlobalModule(final AuthConfig authConfig,
       @GerritServerConfig final Config config) {
@@ -147,6 +186,7 @@
       @Override
       protected void configure() {
         listener().to(WorkQueue.Lifecycle.class);
+        listener().to(VelocityLifecycle.class);
       }
     });
   }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/SitePaths.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/SitePaths.java
index 1faa672..c3a5fb7 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/config/SitePaths.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/SitePaths.java
@@ -28,6 +28,7 @@
   public final File etc_dir;
   public final File lib_dir;
   public final File logs_dir;
+  public final File mail_dir;
   public final File hooks_dir;
   public final File static_dir;
 
@@ -61,6 +62,7 @@
     etc_dir = new File(site_path, "etc");
     lib_dir = new File(site_path, "lib");
     logs_dir = new File(site_path, "logs");
+    mail_dir = new File(etc_dir, "mail");
     hooks_dir = new File(site_path, "hooks");
     static_dir = new File(site_path, "static");
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/AbandonedSender.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/AbandonedSender.java
index d2b5c29..e679849 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/AbandonedSender.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/AbandonedSender.java
@@ -30,7 +30,7 @@
   }
 
   @Override
-  protected void init() {
+  protected void init() throws EmailException {
     super.init();
 
     ccAllApprovals();
@@ -39,10 +39,7 @@
   }
 
   @Override
-  protected void formatChange() {
-    appendText(getNameFor(fromId));
-    appendText(" has abandoned change " + change.getKey().abbreviate() + ":\n");
-    appendText("\n");
-    formatCoverLetter();
+  protected void formatChange() throws EmailException {
+    appendText(velocifyFile("Abandoned.vm"));
   }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/AddReviewerSender.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/AddReviewerSender.java
index be62ba0..e5437cf 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/AddReviewerSender.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/AddReviewerSender.java
@@ -32,7 +32,7 @@
   }
 
   @Override
-  protected void init() {
+  protected void init() throws EmailException {
     super.init();
 
     ccExistingReviewers();
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/ChangeEmail.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/ChangeEmail.java
index e3a0f72..931fc37 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/ChangeEmail.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/ChangeEmail.java
@@ -53,7 +53,6 @@
 
   private ProjectState projectState;
   protected ChangeData changeData;
-  private boolean inFooter;
 
   protected ChangeEmail(EmailArguments ea, final Change c, final String mc) {
     super(ea, mc);
@@ -75,31 +74,9 @@
   }
 
   /** Format the message body by calling {@link #appendText(String)}. */
-  protected void format() {
+  protected void format() throws EmailException {
     formatChange();
-    if (getChangeUrl() != null) {
-      openFooter();
-      appendText("To view visit ");
-      appendText(getChangeUrl());
-      appendText("\n");
-    }
-    if (getSettingsUrl() != null) {
-      openFooter();
-      appendText("To unsubscribe, visit ");
-      appendText(getSettingsUrl());
-      appendText("\n");
-    }
-
-    if (inFooter) {
-      appendText("\n");
-    } else {
-      openFooter();
-    }
-    appendText("Gerrit-MessageType: " + messageClass + "\n");
-    appendText("Gerrit-Project: " + projectName + "\n");
-    appendText("Gerrit-Branch: " + change.getDest().getShortName() + "\n");
-    appendText("Gerrit-Owner: " + getNameEmailFor(change.getOwner()) + "\n");
-
+    appendText(velocifyFile("ChangeFooter.vm"));
     try {
       HashSet<Account.Id> reviewers = new HashSet<Account.Id>();
       for (PatchSetApproval p : args.db.get().patchSetApprovals().byChange(
@@ -120,13 +97,11 @@
   }
 
   /** Format the message body by calling {@link #appendText(String)}. */
-  protected abstract void formatChange();
+  protected abstract void formatChange() throws EmailException;
 
   /** Setup the message headers and envelope (TO, CC, BCC). */
   @Override
-  protected void init() {
-    super.init();
-
+  protected void init() throws EmailException {
     projectState = FutureUtil.get(args.projectCache.get(change.getProject()));
     projectName = projectState != null //
         ? projectState.getProject().getName() //
@@ -148,6 +123,8 @@
       }
     }
 
+    super.init();
+
     if (changeMessage != null && changeMessage.getWrittenOn() != null) {
       setHeader("Date", new Date(changeMessage.getWrittenOn().getTime()));
     }
@@ -156,27 +133,21 @@
     setListIdHeader();
     setChangeUrlHeader();
     setCommitIdHeader();
-
-    inFooter = false;
   }
 
-  private void setListIdHeader() {
+  private void setListIdHeader() throws EmailException {
     // Set a reasonable list id so that filters can be used to sort messages
-    //
-    final StringBuilder listid = new StringBuilder();
-    listid.append("gerrit-");
-    listid.append(projectName.replace('/', '-'));
-    listid.append("@");
-    listid.append(getGerritHost());
-
-    final String listidStr = listid.toString();
-    setHeader("Mailing-List", "list " + listidStr);
-    setHeader("List-Id", "<" + listidStr.replace('@', '.') + ">");
+    setVHeader("Mailing-List", "list $email.listId");
+    setVHeader("List-Id", "<$email.listId.replace('@', '.')>");
     if (getSettingsUrl() != null) {
-      setHeader("List-Unsubscribe", "<" + getSettingsUrl() + ">");
+      setVHeader("List-Unsubscribe", "<$email.settingsUrl>");
     }
   }
 
+  public String getListId() throws EmailException {
+    return velocify("gerrit-$projectName.replace('/', '-')@$email.gerritHost");
+  }
+
   private void setChangeUrlHeader() {
     final String u = getChangeUrl();
     if (u != null) {
@@ -192,27 +163,12 @@
     }
   }
 
-  private void setChangeSubjectHeader() {
-    final StringBuilder subj = new StringBuilder();
-    subj.append("[");
-    subj.append(change.getDest().getShortName());
-    subj.append("] ");
-    subj.append("Change ");
-    subj.append(change.getKey().abbreviate());
-    subj.append(": (");
-    subj.append(projectName);
-    subj.append(") ");
-    if (change.getSubject().length() > 60) {
-      subj.append(change.getSubject().substring(0, 60));
-      subj.append("...");
-    } else {
-      subj.append(change.getSubject());
-    }
-    setHeader("Subject", subj.toString());
+  private void setChangeSubjectHeader() throws EmailException {
+    setHeader("Subject", velocifyFile("ChangeSubject.vm"));
   }
 
   /** Get a link to the change; null if the server doesn't know its own address. */
-  protected String getChangeUrl() {
+  public String getChangeUrl() {
     if (change != null && getGerritUrl() != null) {
       final StringBuilder r = new StringBuilder();
       r.append(getGerritUrl());
@@ -222,25 +178,9 @@
     return null;
   }
 
-  protected String getChangeMessageThreadId() {
-    final StringBuilder r = new StringBuilder();
-    r.append('<');
-    r.append("gerrit");
-    r.append('.');
-    r.append(change.getCreatedOn().getTime());
-    r.append('.');
-    r.append(change.getKey().get());
-    r.append('@');
-    r.append(getGerritHost());
-    r.append('>');
-    return r.toString();
-  }
-
-  private void openFooter() {
-    if (!inFooter) {
-      inFooter = true;
-      appendText("-- \n");
-    }
+  public String getChangeMessageThreadId() throws EmailException {
+    return velocify("<gerrit.${change.createdOn.time}.$change.key.get()" +
+                    "@$email.gerritHost>");
   }
 
   /** Format the sender's "cover letter", {@link #getCoverLetter()}. */
@@ -253,7 +193,7 @@
   }
 
   /** Get the text of the "cover letter", from {@link ChangeMessage}. */
-  protected String getCoverLetter() {
+  public String getCoverLetter() {
     if (changeMessage != null) {
       final String txt = changeMessage.getMessage();
       if (txt != null) {
@@ -265,23 +205,30 @@
 
   /** Format the change message and the affected file list. */
   protected void formatChangeDetail() {
+    appendText(getChangeDetail());
+  }
+
+  /** Create the change message and the affected file list. */
+  public String getChangeDetail() {
+    StringBuilder detail = new StringBuilder();
+
     if (patchSetInfo != null) {
-      appendText(patchSetInfo.getMessage().trim());
-      appendText("\n");
+      detail.append(patchSetInfo.getMessage().trim() + "\n");
     } else {
-      appendText(change.getSubject().trim());
-      appendText("\n");
+      detail.append(change.getSubject().trim() + "\n");
     }
 
     if (patchSet != null) {
-      appendText("---\n");
+      detail.append("---\n");
       for (PatchListEntry p : getPatchList().getPatches()) {
-        appendText(p.getChangeType().getCode() + " " + p.getNewName() + "\n");
+        detail.append(p.getChangeType().getCode() + " " + p.getNewName() + "\n");
       }
-      appendText("\n");
+      detail.append("\n");
     }
+    return detail.toString();
   }
 
+
   /** Get the patch list corresponding to this patch set. */
   protected PatchList getPatchList() {
     if (patchSet != null) {
@@ -423,4 +370,17 @@
         || projectState.controlFor(args.identifiedUserFactory.create(to))
             .controlFor(change).isVisible();
   }
+
+  @Override
+  protected void setupVelocityContext() {
+    super.setupVelocityContext();
+    velocityContext.put("change", change);
+    velocityContext.put("changeId", change.getKey());
+    velocityContext.put("coverLetter", getCoverLetter());
+    velocityContext.put("branch", change.getDest());
+    velocityContext.put("fromName", getNameFor(fromId));
+    velocityContext.put("projectName", projectName);
+    velocityContext.put("patchSet", patchSet);
+    velocityContext.put("patchSetInfo", patchSetInfo);
+  }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/CommentSender.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/CommentSender.java
index be6360f..d3c3b7a 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/CommentSender.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/CommentSender.java
@@ -56,7 +56,7 @@
   }
 
   @Override
-  protected void init() {
+  protected void init() throws EmailException {
     super.init();
 
     ccAllApprovals();
@@ -65,20 +65,13 @@
   }
 
   @Override
-  protected void formatChange() {
-    if (!"".equals(getCoverLetter()) || !inlineComments.isEmpty()) {
-      appendText("Comments on Patch Set " + patchSet.getPatchSetId() + ":\n");
-      appendText("\n");
-      formatCoverLetter();
-      formatInlineComments();
-      if (getChangeUrl() != null) {
-        appendText("To respond, visit " + getChangeUrl() + "\n");
-        appendText("\n");
-      }
-    }
+  protected void formatChange() throws EmailException {
+    appendText(velocifyFile("Comment.vm"));
   }
 
-  private void formatInlineComments() {
+  public String getInlineComments() {
+    StringBuilder  cmts = new StringBuilder();
+
     final Repository repo = getRepository();
     try {
       final PatchList patchList = repo != null ? getPatchList() : null;
@@ -91,10 +84,10 @@
         final short side = c.getSide();
 
         if (!pk.equals(currentFileKey)) {
-          appendText("....................................................\n");
-          appendText("File ");
-          appendText(pk.get());
-          appendText("\n");
+          cmts.append("....................................................\n");
+          cmts.append("File ");
+          cmts.append(pk.get());
+          cmts.append("\n");
           currentFileKey = pk;
 
           if (patchList != null) {
@@ -109,26 +102,27 @@
           }
         }
 
-        appendText("Line " + lineNbr);
+        cmts.append("Line " + lineNbr);
         if (currentFileData != null) {
           try {
             final String lineStr = currentFileData.getLine(side, lineNbr);
-            appendText(": ");
-            appendText(lineStr);
+            cmts.append(": ");
+            cmts.append(lineStr);
           } catch (Throwable cce) {
             // Don't quote the line if we can't safely convert it.
           }
         }
-        appendText("\n");
+        cmts.append("\n");
 
-        appendText(c.getMessage().trim());
-        appendText("\n\n");
+        cmts.append(c.getMessage().trim());
+        cmts.append("\n\n");
       }
     } finally {
       if (repo != null) {
         repo.close();
       }
     }
+    return cmts.toString();
   }
 
   private Repository getRepository() {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/CreateChangeSender.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/CreateChangeSender.java
index 5ea9e20..be830b9 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/CreateChangeSender.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/CreateChangeSender.java
@@ -43,7 +43,7 @@
   }
 
   @Override
-  protected void init() {
+  protected void init() throws EmailException {
     super.init();
 
     bccWatchers();
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/EmailArguments.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/EmailArguments.java
index e9b1a5b..196db4b 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/EmailArguments.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/EmailArguments.java
@@ -22,6 +22,7 @@
 import com.google.gerrit.server.account.AccountCache;
 import com.google.gerrit.server.account.AccountProjectWatchCache;
 import com.google.gerrit.server.config.CanonicalWebUrl;
+import com.google.gerrit.server.config.SitePaths;
 import com.google.gerrit.server.config.WildProjectName;
 import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.gerrit.server.patch.PatchListCache;
@@ -51,6 +52,7 @@
   final ChangeQueryBuilder.Factory queryBuilder;
   final Provider<ChangeQueryRewriter> queryRewriter;
   final Provider<ReviewDb> db;
+  final SitePaths site;
 
   @Inject
   EmailArguments(GitRepositoryManager server, ProjectCache projectCache,
@@ -63,7 +65,8 @@
       @CanonicalWebUrl @Nullable Provider<String> urlProvider,
       @WildProjectName Project.NameKey wildProject,
       ChangeQueryBuilder.Factory queryBuilder,
-      Provider<ChangeQueryRewriter> queryRewriter, Provider<ReviewDb> db) {
+      Provider<ChangeQueryRewriter> queryRewriter, Provider<ReviewDb> db,
+      SitePaths site) {
     this.server = server;
     this.projectCache = projectCache;
     this.accountCache = accountCache;
@@ -79,5 +82,6 @@
     this.queryBuilder = queryBuilder;
     this.queryRewriter = queryRewriter;
     this.db = db;
+    this.site = site;
   }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/MergeFailSender.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/MergeFailSender.java
index 00750ef..cf8f590 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/MergeFailSender.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/MergeFailSender.java
@@ -30,23 +30,14 @@
   }
 
   @Override
-  protected void init() {
+  protected void init() throws EmailException {
     super.init();
 
     ccExistingReviewers();
   }
 
   @Override
-  protected void formatChange() {
-    appendText("Change " + change.getKey().abbreviate());
-    if (patchSetInfo != null && patchSetInfo.getAuthor() != null
-        && patchSetInfo.getAuthor().getName() != null) {
-      appendText(" by ");
-      appendText(patchSetInfo.getAuthor().getName());
-    }
-    appendText(" FAILED to submit to ");
-    appendText(change.getDest().getShortName());
-    appendText(".\n\n");
-    formatCoverLetter();
+  protected void formatChange() throws EmailException {
+    appendText(velocifyFile("MergeFail.vm"));
   }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/MergedSender.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/MergedSender.java
index caf19e4..40f4790 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/MergedSender.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/MergedSender.java
@@ -51,7 +51,7 @@
   }
 
   @Override
-  protected void init() {
+  protected void init() throws EmailException {
     super.init();
 
     ccAllApprovals();
@@ -61,58 +61,47 @@
   }
 
   @Override
-  protected void formatChange() {
-    appendText("Change " + change.getKey().abbreviate());
-    if (patchSetInfo != null && patchSetInfo.getAuthor() != null
-        && patchSetInfo.getAuthor().getName() != null) {
-      appendText(" by ");
-      appendText(patchSetInfo.getAuthor().getName());
-    }
-    appendText(" submitted to ");
-    appendText(dest.getShortName());
-    appendText(":\n\n");
-    formatChangeDetail();
-    formatApprovals();
+  protected void formatChange() throws EmailException {
+    appendText(velocifyFile("Merged.vm"));
   }
 
-  private void formatApprovals() {
-    if (patchSet != null) {
-      try {
-        final Map<Account.Id, Map<ApprovalCategory.Id, PatchSetApproval>> pos =
-            new HashMap<Account.Id, Map<ApprovalCategory.Id, PatchSetApproval>>();
+  public String getApprovals() {
+    try {
+      final Map<Account.Id, Map<ApprovalCategory.Id, PatchSetApproval>> pos =
+          new HashMap<Account.Id, Map<ApprovalCategory.Id, PatchSetApproval>>();
 
-        final Map<Account.Id, Map<ApprovalCategory.Id, PatchSetApproval>> neg =
-            new HashMap<Account.Id, Map<ApprovalCategory.Id, PatchSetApproval>>();
+      final Map<Account.Id, Map<ApprovalCategory.Id, PatchSetApproval>> neg =
+          new HashMap<Account.Id, Map<ApprovalCategory.Id, PatchSetApproval>>();
 
-        for (PatchSetApproval ca : args.db.get().patchSetApprovals()
-            .byPatchSet(patchSet.getId())) {
-          if (ca.getValue() > 0) {
-            insert(pos, ca);
-          } else if (ca.getValue() < 0) {
-            insert(neg, ca);
-          }
+      for (PatchSetApproval ca : args.db.get().patchSetApprovals()
+          .byPatchSet(patchSet.getId())) {
+        if (ca.getValue() > 0) {
+          insert(pos, ca);
+        } else if (ca.getValue() < 0) {
+          insert(neg, ca);
         }
-
-        format("Approvals", pos);
-        format("Objections", neg);
-      } catch (OrmException err) {
-        // Don't list the approvals
       }
+
+      return format("Approvals", pos) + format("Objections", neg);
+    } catch (OrmException err) {
+      // Don't list the approvals
     }
+    return "";
   }
 
-  private void format(final String type,
+  private String format(final String type,
       final Map<Account.Id, Map<ApprovalCategory.Id, PatchSetApproval>> list) {
+    StringBuilder txt = new StringBuilder();
     if (list.isEmpty()) {
-      return;
+      return "";
     }
-    appendText(type + ":\n");
+    txt.append(type + ":\n");
     for (final Map.Entry<Account.Id, Map<ApprovalCategory.Id, PatchSetApproval>> ent : list
         .entrySet()) {
       final Map<ApprovalCategory.Id, PatchSetApproval> l = ent.getValue();
-      appendText("  ");
-      appendText(getNameFor(ent.getKey()));
-      appendText(": ");
+      txt.append("  ");
+      txt.append(getNameFor(ent.getKey()));
+      txt.append(": ");
       boolean first = true;
       for (ApprovalType at : approvalTypes.getApprovalTypes()) {
         final PatchSetApproval ca = l.get(at.getCategory().getId());
@@ -123,24 +112,25 @@
         if (first) {
           first = false;
         } else {
-          appendText("; ");
+          txt.append("; ");
         }
 
         final ApprovalCategoryValue v = at.getValue(ca);
         if (v != null) {
-          appendText(v.getName());
+          txt.append(v.getName());
         } else {
-          appendText(at.getCategory().getName());
-          appendText("=");
+          txt.append(at.getCategory().getName());
+          txt.append("=");
           if (ca.getValue() > 0) {
-            appendText("+");
+            txt.append("+");
           }
-          appendText("" + ca.getValue());
+          txt.append("" + ca.getValue());
         }
       }
-      appendText("\n");
+      txt.append("\n");
     }
-    appendText("\n");
+    txt.append("\n");
+    return txt.toString();
   }
 
   private void insert(
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/NewChangeSender.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/NewChangeSender.java
index dc8c2c2..9e78cab 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/NewChangeSender.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/NewChangeSender.java
@@ -20,6 +20,7 @@
 
 import com.jcraft.jsch.HostKey;
 
+import java.util.ArrayList;
 import java.util.Collection;
 import java.util.HashSet;
 import java.util.Iterator;
@@ -46,7 +47,7 @@
   }
 
   @Override
-  protected void init() {
+  protected void init() throws EmailException {
     super.init();
 
     setHeader("Message-ID", getChangeMessageThreadId());
@@ -57,74 +58,19 @@
   }
 
   @Override
-  protected void formatChange() {
-    formatSalutation();
-    formatChangeDetail();
-
-    appendText("\n");
-    appendText("  " + getPullUrl() + "\n");
+  protected void formatChange() throws EmailException {
+    appendText(velocifyFile("NewChange.vm"));
   }
 
-  private void formatSalutation() {
-    final String changeUrl = getChangeUrl();
-
+  public List<String> getReviewerNames() {
     if (reviewers.isEmpty()) {
-      formatDest();
-      if (changeUrl != null) {
-        appendText("\n");
-        appendText("    " + changeUrl + "\n");
-        appendText("\n");
-      }
-      appendText("\n");
-
-    } else {
-      appendText("Hello");
-      for (final Iterator<Account.Id> i = reviewers.iterator(); i.hasNext();) {
-        appendText(" ");
-        appendText(getNameFor(i.next()));
-        appendText(",");
-      }
-      appendText("\n");
-      appendText("\n");
-
-      appendText("I'd like you to do a code review.");
-      if (changeUrl != null) {
-        appendText("  Please visit\n");
-        appendText("\n");
-        appendText("    " + changeUrl + "\n");
-        appendText("\n");
-        appendText("to review the following change:\n");
-      }
-      appendText("\n");
-
-      formatDest();
-      appendText("\n");
+      return null;
     }
-  }
-
-  private void formatDest() {
-    appendText("Change " + change.getKey().abbreviate());
-    appendText(" for ");
-    appendText(change.getDest().getShortName());
-    appendText(" in ");
-    appendText(projectName);
-    appendText(":\n");
-  }
-
-  private String getPullUrl() {
-    final String host = getSshHost();
-    if (host == null) {
-      return "";
+    List<String> names = new ArrayList<String>();
+    for (Account.Id id : reviewers) {
+      names.add(getNameFor(id));
     }
-
-    final StringBuilder r = new StringBuilder();
-    r.append("git pull ssh://");
-    r.append(host);
-    r.append("/");
-    r.append(projectName);
-    r.append(" ");
-    r.append(patchSet.getRefName());
-    return r.toString();
+    return names;
   }
 
   public String getSshHost() {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/OutgoingEmail.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/OutgoingEmail.java
index ea57735..587aeca 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/OutgoingEmail.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/OutgoingEmail.java
@@ -18,11 +18,19 @@
 import com.google.gerrit.reviewdb.UserIdentity;
 import com.google.gerrit.server.mail.EmailHeader.AddressList;
 import com.google.gerrit.server.util.FutureUtil;
+import com.google.gerrit.server.account.AccountState;
+import com.google.gerrit.server.mail.EmailHeader.AddressList;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.velocity.app.Velocity;
+import org.apache.velocity.exception.ResourceNotFoundException;
+import org.apache.velocity.VelocityContext;
 
 import org.eclipse.jgit.util.SystemReader;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import java.io.StringWriter;
 import java.net.MalformedURLException;
 import java.net.URL;
 import java.util.ArrayList;
@@ -47,6 +55,7 @@
   private final List<Address> smtpRcptTo = new ArrayList<Address>();
   private Address smtpFromAddress;
   private StringBuilder body;
+  protected VelocityContext velocityContext;
 
   protected final EmailArguments args;
   protected Account.Id fromId;
@@ -114,10 +123,12 @@
   }
 
   /** Format the message body by calling {@link #appendText(String)}. */
-  protected abstract void format();
+  protected abstract void format() throws EmailException;
 
   /** Setup the message headers and envelope (TO, CC, BCC). */
-  protected void init() {
+  protected void init() throws EmailException {
+    setupVelocityContext();
+
     smtpFromAddress = args.fromAddressGenerator.from(fromId);
     setHeader("Date", new Date());
     headers.put("From", new EmailHeader.AddressList(smtpFromAddress));
@@ -141,26 +152,32 @@
     body = new StringBuilder();
 
     if (fromId != null && args.fromAddressGenerator.isGenericAddress(fromId)) {
-      final Account account =
-          FutureUtil.get(args.accountCache.getAccount(fromId));
-      final String name = account.getFullName();
-      final String email = account.getPreferredEmail();
-
-      if ((name != null && !name.isEmpty())
-          || (email != null && !email.isEmpty())) {
-        body.append("From");
-        if (name != null && !name.isEmpty()) {
-          body.append(" ").append(name);
-        }
-        if (email != null && !email.isEmpty()) {
-          body.append(" <").append(email).append(">");
-        }
-        body.append(":\n\n");
-      }
+      appendText(getFromLine());
     }
   }
 
-  protected String getGerritHost() {
+  protected String getFromLine() {
+    final Account account =
+	    FutureUtil.get(args.accountCache.getAccount(fromId));
+    final String name = account.getFullName();
+    final String email = account.getPreferredEmail();
+    StringBuilder f = new StringBuilder();
+
+    if ((name != null && !name.isEmpty())
+        || (email != null && !email.isEmpty())) {
+      f.append("From");
+      if (name != null && !name.isEmpty()) {
+        f.append(" ").append(name);
+      }
+      if (email != null && !email.isEmpty()) {
+        f.append(" <").append(email).append(">");
+      }
+      f.append(":\n\n");
+    }
+    return f.toString();
+  }
+
+  public String getGerritHost() {
     if (getGerritUrl() != null) {
       try {
         return new URL(getGerritUrl()).getHost();
@@ -186,10 +203,16 @@
     return null;
   }
 
-  protected String getGerritUrl() {
+  public String getGerritUrl() {
     return args.urlProvider.get();
   }
 
+  /** Set a header in the outgoing message using a template. */
+  protected void setVHeader(final String name, final String value) throws
+      EmailException {
+    setHeader(name, velocify(value));
+  }
+
   /** Set a header in the outgoing message. */
   protected void setHeader(final String name, final String value) {
     headers.put(name, new EmailHeader.String(value));
@@ -224,7 +247,7 @@
     return name;
   }
 
-  protected String getNameEmailFor(Account.Id accountId) {
+  public String getNameEmailFor(Account.Id accountId) {
     Account who = FutureUtil.get(args.accountCache.getAccount(accountId));
     String name = who.getFullName();
     String email = who.getPreferredEmail();
@@ -313,9 +336,46 @@
   private Address toAddress(final Account.Id id) {
     final Account a = FutureUtil.get(args.accountCache.getAccount(id));
     final String e = a.getPreferredEmail();
-    if (e == null) {
+    if (!a.isActive() || e == null) {
       return null;
     }
     return new Address(a.getFullName(), e);
   }
+
+  protected void setupVelocityContext() {
+    velocityContext = new VelocityContext();
+
+    velocityContext.put("email", this);
+    velocityContext.put("messageClass", messageClass);
+    velocityContext.put("StringUtils", StringUtils.class);
+  }
+
+  protected String velocify(String tpl) throws EmailException {
+    try {
+      StringWriter w = new StringWriter();
+      Velocity.evaluate(velocityContext, w, "OutgoingEmail", tpl);
+      return w.toString();
+    } catch(Exception e) {
+      throw new EmailException("Velocity template "+ tpl.toString(), e);
+    }
+  }
+
+  protected String velocifyFile(String name) throws EmailException {
+    try {
+      StringWriter w = new StringWriter();
+      Velocity.mergeTemplate(name, velocityContext, w);
+      return w.toString();
+    } catch(ResourceNotFoundException e) {
+      try {
+        StringWriter w = new StringWriter();
+        String pkg = "com/google/gerrit/server/mail/";
+        Velocity.mergeTemplate(pkg + name, velocityContext, w);
+        return w.toString();
+      } catch(Exception e2) {
+        throw new EmailException("Velocity WAR template" + name + ".\n", e2);
+      }
+    } catch(Exception e) {
+      throw new EmailException("Velocity template " + name + ".\n", e);
+    }
+  }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/RegisterNewEmailSender.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/RegisterNewEmailSender.java
index 9b201fd..2c77999 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/RegisterNewEmailSender.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/RegisterNewEmailSender.java
@@ -40,7 +40,7 @@
   }
 
   @Override
-  protected void init() {
+  protected void init() throws EmailException {
     super.init();
     setHeader("Subject", "[Gerrit Code Review] Email Verification");
     add(RecipientType.TO, new Address(addr));
@@ -52,39 +52,8 @@
   }
 
   @Override
-  protected void format() {
-    final StringBuilder url = new StringBuilder();
-    url.append(getGerritUrl());
-    url.append("#VE,");
-    url.append(getEmailRegistrationToken());
-
-    appendText("Welcome to Gerrit Code Review at ");
-    appendText(getGerritHost());
-    appendText(".\n");
-
-    appendText("\n");
-    appendText("To add a verified email address to your user account, please\n");
-    appendText("click on the following link:\n");
-    appendText("\n");
-    appendText(url.toString());
-    appendText("\n");
-
-    appendText("\n");
-    appendText("If you have received this mail in error,"
-        + " you do not need to take any\n");
-    appendText("action to cancel the account."
-        + " The account will not be activated, and\n");
-    appendText("you will not receive any further emails.\n");
-
-    appendText("\n");
-    appendText("If clicking the link above does not work,"
-        + " copy and paste the URL in a\n");
-    appendText("new browser window instead.\n");
-
-    appendText("\n");
-    appendText("This is a send-only email address."
-        + "  Replies to this message will not\n");
-    appendText("be read or answered.\n");
+  protected void format() throws EmailException {
+    appendText(velocifyFile("RegisterNewEmail.vm"));
   }
 
   public String getEmailRegistrationToken() {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/ReplacePatchSetSender.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/ReplacePatchSetSender.java
index 841aa35..0c8ca9a 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/ReplacePatchSetSender.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/ReplacePatchSetSender.java
@@ -22,6 +22,7 @@
 
 import com.jcraft.jsch.HostKey;
 
+import java.util.ArrayList;
 import java.util.Collection;
 import java.util.HashSet;
 import java.util.Iterator;
@@ -53,7 +54,7 @@
   }
 
   @Override
-  protected void init() {
+  protected void init() throws EmailException {
     super.init();
 
     if (fromId != null) {
@@ -67,77 +68,19 @@
   }
 
   @Override
-  protected void formatChange() {
-    formatSalutation();
-    formatChangeDetail();
-
-    appendText("\n");
-    appendText("  " + getPullUrl() + "\n");
+  protected void formatChange() throws EmailException {
+    appendText(velocifyFile("ReplacePatchSet.vm"));
   }
 
-  private void formatSalutation() {
-    final String changeUrl = getChangeUrl();
-
+  public List<String> getReviewerNames() {
     if (reviewers.isEmpty()) {
-      formatDest();
-      if (changeUrl != null) {
-        appendText("\n");
-        appendText("    " + changeUrl + "\n");
-        appendText("\n");
-      }
-      appendText("\n");
-
-    } else {
-      appendText("Hello");
-      for (final Iterator<Account.Id> i = reviewers.iterator(); i.hasNext();) {
-        appendText(" ");
-        appendText(getNameFor(i.next()));
-        appendText(",");
-      }
-      appendText("\n");
-      appendText("\n");
-
-      appendText("I'd like you to reexamine change "
-          + change.getKey().abbreviate() + ".");
-      if (changeUrl != null) {
-        appendText("  Please visit\n");
-        appendText("\n");
-        appendText("    " + changeUrl + "\n");
-        appendText("\n");
-        appendText("to look at patch set " + patchSet.getPatchSetId());
-        appendText(":\n");
-      }
-      appendText("\n");
-
-      formatDest();
-      appendText("\n");
+      return null;
     }
-  }
-
-  private void formatDest() {
-    appendText("Change " + change.getKey().abbreviate());
-    appendText(" (patch set " + patchSet.getPatchSetId() + ")");
-    appendText(" for ");
-    appendText(change.getDest().getShortName());
-    appendText(" in ");
-    appendText(projectName);
-    appendText(":\n");
-  }
-
-  private String getPullUrl() {
-    final String host = getSshHost();
-    if (host == null) {
-      return "";
+    List<String> names = new ArrayList<String>();
+    for (Account.Id id : reviewers) {
+      names.add(getNameFor(id));
     }
-
-    final StringBuilder r = new StringBuilder();
-    r.append("git pull ssh://");
-    r.append(host);
-    r.append("/");
-    r.append(projectName);
-    r.append(" ");
-    r.append(patchSet.getRefName());
-    return r.toString();
+    return names;
   }
 
   public String getSshHost() {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/ReplyToChangeSender.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/ReplyToChangeSender.java
index 05d2753..4c3ed76 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/ReplyToChangeSender.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/ReplyToChangeSender.java
@@ -23,7 +23,7 @@
   }
 
   @Override
-  protected void init() {
+  protected void init() throws EmailException {
     super.init();
 
     final String threadId = getChangeMessageThreadId();
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_103.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_103.java
index 46c3434..5474638 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_103.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_103.java
@@ -33,7 +33,7 @@
   private static final int MAX_SCAN_SIZE = 1000;
 
   @Inject
-  Schema_103(Provider<Schema_40> prior) {
+  Schema_103(Provider<Schema_41> prior) {
     super(prior);
   }
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_41.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_41.java
new file mode 100644
index 0000000..508db43
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_41.java
@@ -0,0 +1,25 @@
+// Copyright (C) 2010 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.schema;
+
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+
+public class Schema_41 extends SchemaVersion {
+  @Inject
+  Schema_41(Provider<Schema_40> prior) {
+    super(prior);
+  }
+}
diff --git a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/Abandoned.vm b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/Abandoned.vm
new file mode 100644
index 0000000..557ee60
--- /dev/null
+++ b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/Abandoned.vm
@@ -0,0 +1,40 @@
+## Copyright (C) 2010 The Android Open Source Project
+##
+## Licensed under the Apache License, Version 2.0 (the "License");
+## you may not use this file except in compliance with the License.
+## You may obtain a copy of the License at
+##
+## http://www.apache.org/licenses/LICENSE-2.0
+##
+## Unless required by applicable law or agreed to in writing, software
+## distributed under the License is distributed on an "AS IS" BASIS,
+## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+## See the License for the specific language governing permissions and
+## limitations under the License.
+##
+##
+## Template Type:
+## -------------
+## This is a velocity mail template, see: http://velocity.apache.org and the
+## gerrit-docs:config-mail.txt for more info on modifying gerrit mail templates.
+##
+## Template File Names and extensions:
+## ----------------------------------
+## Gerrit will use templates ending in ".vm" but will ignore templates ending
+## in ".vm.example".  If a .vm template does not exist, the default internal
+## gerrit template which is the same as the .vm.example will be used.  If you
+## want to override the default template, copy the .vm.exmaple file to a .vm
+## file and edit it appropriately.
+##
+## This Template:
+## --------------
+## The Abandoned.vm template will determine the contents of the email related
+## to a change being abandoned.   It is a ChangeEmail: see ChangeSubject.vm and
+## ChangeFooter.vm.
+##
+$fromName has abandoned change $changeId.abbreviate():
+
+#if ($coverLetter)
+$coverLetter
+
+#end
diff --git a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/ChangeFooter.vm b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/ChangeFooter.vm
new file mode 100644
index 0000000..c15d6ef
--- /dev/null
+++ b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/ChangeFooter.vm
@@ -0,0 +1,49 @@
+## Copyright (C) 2010 The Android Open Source Project
+##
+## Licensed under the Apache License, Version 2.0 (the "License");
+## you may not use this file except in compliance with the License.
+## You may obtain a copy of the License at
+##
+## http://www.apache.org/licenses/LICENSE-2.0
+##
+## Unless required by applicable law or agreed to in writing, software
+## distributed under the License is distributed on an "AS IS" BASIS,
+## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+## See the License for the specific language governing permissions and
+## limitations under the License.
+##
+##
+## Template Type:
+## -------------
+## This is a velocity mail template, see: http://velocity.apache.org and the
+## gerrit-docs:config-mail.txt for more info on modifying gerrit mail templates.
+##
+## Template File Names and extensions:
+## ----------------------------------
+## Gerrit will use templates ending in ".vm" but will ignore templates ending
+## in ".vm.example".  If a .vm template does not exist, the default internal
+## gerrit template which is the same as the .vm.example will be used.  If you
+## want to override the default template, copy the .vm.exmaple file to a .vm
+## file and edit it appropriately.
+##
+## This Template:
+## --------------
+## The ChangeFooter.vm template will determine the contents of the footer
+## text that will be appended to ALL emails related to changes.
+##
+--
+#if ($email.changeUrl)
+To view, visit $email.changeUrl
+#set ($notblank = 1)
+#end
+#if ($email.settingsUrl)
+To unsubscribe, visit $email.settingsUrl
+#set ($notblank = 1)
+#end
+#if ($notblank == 1)
+
+#end
+Gerrit-MessageType: $messageClass
+Gerrit-Project: $projectName
+Gerrit-Branch: $branch.shortName
+Gerrit-Owner: $email.getNameEmailFor($change.owner)
diff --git a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/ChangeSubject.vm b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/ChangeSubject.vm
new file mode 100644
index 0000000..2132c63
--- /dev/null
+++ b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/ChangeSubject.vm
@@ -0,0 +1,37 @@
+## Copyright (C) 2010 The Android Open Source Project
+##
+## Licensed under the Apache License, Version 2.0 (the "License");
+## you may not use this file except in compliance with the License.
+## You may obtain a copy of the License at
+##
+## http://www.apache.org/licenses/LICENSE-2.0
+##
+## Unless required by applicable law or agreed to in writing, software
+## distributed under the License is distributed on an "AS IS" BASIS,
+## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+## See the License for the specific language governing permissions and
+## limitations under the License.
+##
+##
+## Template Type:
+## -------------
+## This is a velocity mail template, see: http://velocity.apache.org and the
+## gerrit-docs:config-mail.txt for more info on modifying gerrit mail templates.
+##
+## Template File Names and extensions:
+## ----------------------------------
+## Gerrit will use templates ending in ".vm" but will ignore templates ending
+## in ".vm.example".  If a .vm template does not exist, the default internal
+## gerrit template which is the same as the .vm.example will be used.  If you
+## want to override the default template, copy the .vm.exmaple file to a .vm
+## file and edit it appropriately.
+##
+## This Template:
+## --------------
+## The ChangeSubject.vm template will determine the contents of the email
+## subject line for ALL emails related to changes.
+##
+#macro(elipses $length $str)
+#if($str.length() > $length)${str.substring(0,$length)}...#else$str#end
+#end
+[$branch.shortName] Change $changeId.abbreviate(): ($projectName) #elipses(60, $change.subject)
diff --git a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/Comment.vm b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/Comment.vm
new file mode 100644
index 0000000..b7ba89e
--- /dev/null
+++ b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/Comment.vm
@@ -0,0 +1,47 @@
+## Copyright (C) 2010 The Android Open Source Project
+##
+## Licensed under the Apache License, Version 2.0 (the "License");
+## you may not use this file except in compliance with the License.
+## You may obtain a copy of the License at
+##
+## http://www.apache.org/licenses/LICENSE-2.0
+##
+## Unless required by applicable law or agreed to in writing, software
+## distributed under the License is distributed on an "AS IS" BASIS,
+## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+## See the License for the specific language governing permissions and
+## limitations under the License.
+##
+##
+## Template Type:
+## -------------
+## This is a velocity mail template, see: http://velocity.apache.org and the
+## gerrit-docs:config-mail.txt for more info on modifying gerrit mail templates.
+##
+## Template File Names and extensions:
+## ----------------------------------
+## Gerrit will use templates ending in ".vm" but will ignore templates ending
+## in ".vm.example".  If a .vm template does not exist, the default internal
+## gerrit template which is the same as the .vm.example will be used.  If you
+## want to override the default template, copy the .vm.exmaple file to a .vm
+## file and edit it appropriately.
+##
+## This Template:
+## --------------
+## The Comment.vm template will determine the contents of the email related to
+## a user submitting comments on changes.  It is a ChangeEmail: see
+## ChangeSubject.vm and ChangeFooter.vm.
+##
+#if ($email.coverLetter || $email.inlineComments)
+Comments on Patch Set $patchSet.patchSetId:
+
+#if ($email.coverLetter)
+$email.coverLetter
+
+#end
+#if($email.inlineComments)$email.inlineComments#end
+#if ($email.changeUrl)
+To respond, visit $email.changeUrl
+
+#end
+#end
diff --git a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/MergeFail.vm b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/MergeFail.vm
new file mode 100644
index 0000000..17036a1
--- /dev/null
+++ b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/MergeFail.vm
@@ -0,0 +1,41 @@
+## Copyright (C) 2010 The Android Open Source Project
+##
+## Licensed under the Apache License, Version 2.0 (the "License");
+## you may not use this file except in compliance with the License.
+## You may obtain a copy of the License at
+##
+## http://www.apache.org/licenses/LICENSE-2.0
+##
+## Unless required by applicable law or agreed to in writing, software
+## distributed under the License is distributed on an "AS IS" BASIS,
+## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+## See the License for the specific language governing permissions and
+## limitations under the License.
+##
+##
+## Template Type:
+## -------------
+## This is a velocity mail template, see: http://velocity.apache.org and the
+## gerrit-docs:config-mail.txt for more info on modifying gerrit mail templates.
+##
+## Template File Names and extensions:
+## ----------------------------------
+## Gerrit will use templates ending in ".vm" but will ignore templates ending
+## in ".vm.example".  If a .vm template does not exist, the default internal
+## gerrit template which is the same as the .vm.example will be used.  If you
+## want to override the default template, copy the .vm.exmaple file to a .vm
+## file and edit it appropriately.
+##
+## This Template:
+## --------------
+## The MergeFail.vm template will determine the contents of the email related
+## to a failure upon attempting to merge a change to the head.  It is a
+## ChangeEmail: see ChangeSubject.vm and ChangeFooter.vm.
+##
+Change $change.key.abbreviate()#if ($patchSetInfo.author.name)
+ by $patchSetInfo.author.name#end FAILED to submit to ${change.dest.shortName}.
+
+#if ($email.coverLetter)
+$email.coverLetter
+
+#end
diff --git a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/Merged.vm b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/Merged.vm
new file mode 100644
index 0000000..c459c16
--- /dev/null
+++ b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/Merged.vm
@@ -0,0 +1,38 @@
+## Copyright (C) 2010 The Android Open Source Project
+##
+## Licensed under the Apache License, Version 2.0 (the "License");
+## you may not use this file except in compliance with the License.
+## You may obtain a copy of the License at
+##
+## http://www.apache.org/licenses/LICENSE-2.0
+##
+## Unless required by applicable law or agreed to in writing, software
+## distributed under the License is distributed on an "AS IS" BASIS,
+## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+## See the License for the specific language governing permissions and
+## limitations under the License.
+##
+##
+## Template Type:
+## -------------
+## This is a velocity mail template, see: http://velocity.apache.org and the
+## gerrit-docs:config-mail.txt for more info on modifying gerrit mail templates.
+##
+## Template File Names and extensions:
+## ----------------------------------
+## Gerrit will use templates ending in ".vm" but will ignore templates ending
+## in ".vm.example".  If a .vm template does not exist, the default internal
+## gerrit template which is the same as the .vm.example will be used.  If you
+## want to override the default template, copy the .vm.exmaple file to a .vm
+## file and edit it appropriately.
+##
+## This Template:
+## --------------
+## The Merged.vm template will determine the contents of the email related to
+## a change successfully merged to the head.  It is a ChangeEmail: see
+## ChangeSubject.vm and ChangeFooter.vm.
+##
+Change $changeId.abbreviate()#if ($patchSetInfo.author.name)
+ by $patchSetInfo.author.name#end submitted to $change.dest.shortName:
+
+$email.changeDetail$email.approvals
diff --git a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/NewChange.vm b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/NewChange.vm
new file mode 100644
index 0000000..7f61039
--- /dev/null
+++ b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/NewChange.vm
@@ -0,0 +1,57 @@
+## Copyright (C) 2010 The Android Open Source Project
+##
+## Licensed under the Apache License, Version 2.0 (the "License");
+## you may not use this file except in compliance with the License.
+## You may obtain a copy of the License at
+##
+## http://www.apache.org/licenses/LICENSE-2.0
+##
+## Unless required by applicable law or agreed to in writing, software
+## distributed under the License is distributed on an "AS IS" BASIS,
+## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+## See the License for the specific language governing permissions and
+## limitations under the License.
+##
+##
+## Template Type:
+## -------------
+## This is a velocity mail template, see: http://velocity.apache.org and the
+## gerrit-docs:config-mail.txt for more info on modifying gerrit mail templates.
+##
+## Template File Names and extensions:
+## ----------------------------------
+## Gerrit will use templates ending in ".vm" but will ignore templates ending
+## in ".vm.example".  If a .vm template does not exist, the default internal
+## gerrit template which is the same as the .vm.example will be used.  If you
+## want to override the default template, copy the .vm.exmaple file to a .vm
+## file and edit it appropriately.
+##
+## This Template:
+## --------------
+## The NewChange.vm template will determine the contents of the email related
+## to a user submitting a new change for review. It is a ChangeEmail: see
+## ChangeSubject.vm and ChangeFooter.vm.
+##
+#set ($destination = "Change $changeId.abbreviate() for $branch.shortName in $projectName:")
+#if($email.reviewerNames)
+Hello $StringUtils.join($email.reviewerNames, ' ,'),
+
+I'd like you to do a code review.#if($email.changeUrl)  Please visit
+
+    $email.changeUrl
+
+to review the following change:
+#end
+
+$destination
+#else
+$destination
+#if($email.changeUrl)
+
+    $email.changeUrl
+
+#end
+#end
+
+$email.changeDetail
+  git pull ssh://$email.sshHost/$projectName $patchSet.refName
diff --git a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/RegisterNewEmail.vm b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/RegisterNewEmail.vm
new file mode 100644
index 0000000..c1de87e
--- /dev/null
+++ b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/RegisterNewEmail.vm
@@ -0,0 +1,49 @@
+## Copyright (C) 2010 The Android Open Source Project
+##
+## Licensed under the Apache License, Version 2.0 (the "License");
+## you may not use this file except in compliance with the License.
+## You may obtain a copy of the License at
+##
+## http://www.apache.org/licenses/LICENSE-2.0
+##
+## Unless required by applicable law or agreed to in writing, software
+## distributed under the License is distributed on an "AS IS" BASIS,
+## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+## See the License for the specific language governing permissions and
+## limitations under the License.
+##
+##
+## Template Type:
+## -------------
+## This is a velocity mail template, see: http://velocity.apache.org and the
+## gerrit-docs:config-mail.txt for more info on modifying gerrit mail templates.
+##
+## Template File Names and extensions:
+## ----------------------------------
+## Gerrit will use templates ending in ".vm" but will ignore templates ending
+## in ".vm.example".  If a .vm template does not exist, the default internal
+## gerrit template which is the same as the .vm.example will be used.  If you
+## want to override the default template, copy the .vm.exmaple file to a .vm
+## file and edit it appropriately.
+##
+## This Template:
+## --------------
+## The RegisterNewEmail.vm template will determine the contents of the email
+## related to registering new email accounts.
+##
+Welcome to Gerrit Code Review at ${email.gerritHost}.
+
+To add a verified email address to your user account, please
+click on the following link:
+
+$email.gerritUrl#VE,$email.emailRegistrationToken
+
+If you have received this mail in error, you do not need to take any
+action to cancel the account.  The account will not be activated, and
+you will not receive any further emails.
+
+If clicking the link above does not work, copy and paste the URL in a
+new browser window instead.
+
+This is a send-only email address.  Replies to this message will not
+be read or answered.
diff --git a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/ReplacePatchSet.vm b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/ReplacePatchSet.vm
new file mode 100644
index 0000000..d13b6af
--- /dev/null
+++ b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/ReplacePatchSet.vm
@@ -0,0 +1,57 @@
+## Copyright (C) 2010 The Android Open Source Project
+##
+## Licensed under the Apache License, Version 2.0 (the "License");
+## you may not use this file except in compliance with the License.
+## You may obtain a copy of the License at
+##
+## http://www.apache.org/licenses/LICENSE-2.0
+##
+## Unless required by applicable law or agreed to in writing, software
+## distributed under the License is distributed on an "AS IS" BASIS,
+## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+## See the License for the specific language governing permissions and
+## limitations under the License.
+##
+##
+## Template Type:
+## -------------
+## This is a velocity mail template, see: http://velocity.apache.org and the
+## gerrit-docs:config-mail.txt for more info on modifying gerrit mail templates.
+##
+## Template File Names and extensions:
+## ----------------------------------
+## Gerrit will use templates ending in ".vm" but will ignore templates ending
+## in ".vm.example".  If a .vm template does not exist, the default internal
+## gerrit template which is the same as the .vm.example will be used.  If you
+## want to override the default template, copy the .vm.exmaple file to a .vm
+## file and edit it appropriately.
+##
+## This Template:
+## --------------
+## The ReplacePatchSet.vm template will determine the contents of the email
+## related to a user submitting a new patchset for a change.  It is a
+## ChangeEmail: see ChangeSubject.vm and ChangeFooter.vm.
+##
+#set ($destination = "Change $changeId.abbreviate() (patch set $patchSet.patchSetId) for $branch.shortName in $projectName:")
+#if($email.reviewerNames)
+Hello $StringUtils.join($email.reviewerNames, ' ,'),
+
+I'd like you to reexamine change ${changeId.abbreviate()}.#if($email.changeUrl)  Please visit
+
+    $email.changeUrl
+
+to look at patch set $patchSet.patchSetId
+#end
+
+$destination
+#else
+$destination
+#if($email.changeUrl)
+
+    $email.changeUrl
+
+#end
+#end
+
+$email.changeDetail
+  git pull ssh://$email.sshHost/$projectName $patchSet.refName
diff --git a/gerrit-sshd/pom.xml b/gerrit-sshd/pom.xml
index d1adb77..d48972f 100644
--- a/gerrit-sshd/pom.xml
+++ b/gerrit-sshd/pom.xml
@@ -22,7 +22,7 @@
   <parent>
     <groupId>com.google.gerrit</groupId>
     <artifactId>gerrit-parent</artifactId>
-    <version>2.1.4-SNAPSHOT</version>
+    <version>2.1-SNAPSHOT</version>
   </parent>
 
   <artifactId>gerrit-sshd</artifactId>
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/DatabasePubKeyAuth.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/DatabasePubKeyAuth.java
index ee6a72a..e2a37f5 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/DatabasePubKeyAuth.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/DatabasePubKeyAuth.java
@@ -135,6 +135,11 @@
       }
     }
 
+    if (!createUser(sd, key).getAccount().isActive()) {
+      sd.authenticationError(username, "inactive-account");
+      return false;
+    }
+
     return success(username, session, sd, createUser(sd, key));
   }
 
diff --git a/gerrit-util-cli/pom.xml b/gerrit-util-cli/pom.xml
index 20f28e1..6db5241 100644
--- a/gerrit-util-cli/pom.xml
+++ b/gerrit-util-cli/pom.xml
@@ -22,7 +22,7 @@
   <parent>
     <groupId>com.google.gerrit</groupId>
     <artifactId>gerrit-parent</artifactId>
-    <version>2.1.4-SNAPSHOT</version>
+    <version>2.1-SNAPSHOT</version>
   </parent>
 
   <artifactId>gerrit-util-cli</artifactId>
diff --git a/gerrit-util-ssl/pom.xml b/gerrit-util-ssl/pom.xml
index 220ae47..39d3ce0 100644
--- a/gerrit-util-ssl/pom.xml
+++ b/gerrit-util-ssl/pom.xml
@@ -22,7 +22,7 @@
   <parent>
     <groupId>com.google.gerrit</groupId>
     <artifactId>gerrit-parent</artifactId>
-    <version>2.1.4-SNAPSHOT</version>
+    <version>2.1-SNAPSHOT</version>
   </parent>
 
   <artifactId>gerrit-util-ssl</artifactId>
diff --git a/gerrit-war/pom.xml b/gerrit-war/pom.xml
index 4bfa17e..87cfaa5 100644
--- a/gerrit-war/pom.xml
+++ b/gerrit-war/pom.xml
@@ -22,7 +22,7 @@
   <parent>
     <groupId>com.google.gerrit</groupId>
     <artifactId>gerrit-parent</artifactId>
-    <version>2.1.4-SNAPSHOT</version>
+    <version>2.1-SNAPSHOT</version>
   </parent>
 
   <artifactId>gerrit-war</artifactId>
diff --git a/pom.xml b/pom.xml
index d6b4cdb..93ee2be 100644
--- a/pom.xml
+++ b/pom.xml
@@ -22,7 +22,7 @@
   <groupId>com.google.gerrit</groupId>
   <artifactId>gerrit-parent</artifactId>
   <packaging>pom</packaging>
-  <version>2.1.4-SNAPSHOT</version>
+  <version>2.1-SNAPSHOT</version>
 
   <name>Gerrit Code Review - Parent</name>
   <url>http://code.google.com/p/gerrit/</url>
@@ -483,6 +483,12 @@
       </dependency>
 
       <dependency>
+        <groupId>org.apache.velocity</groupId>
+        <artifactId>velocity</artifactId>
+        <version>1.6.4</version>
+      </dependency>
+
+      <dependency>
         <groupId>net.sf.ehcache</groupId>
         <artifactId>ehcache-core</artifactId>
         <version>1.7.2</version>