killbill-memoizeit

Merged with latest changes

5/3/2012 8:47:44 PM

Changes

account/src/main/resources/com/ning/billing/account/dao/AccountHistorySqlDao.sql.stg 15(+0 -15)

invoice/pom.xml 7(+0 -7)

pom.xml 20(+18 -2)

util/pom.xml 16(+16 -0)

Details

diff --git a/account/src/main/java/com/ning/billing/account/api/DefaultAccount.java b/account/src/main/java/com/ning/billing/account/api/DefaultAccount.java
index 6eab3f3..7fc3a32 100644
--- a/account/src/main/java/com/ning/billing/account/api/DefaultAccount.java
+++ b/account/src/main/java/com/ning/billing/account/api/DefaultAccount.java
@@ -48,6 +48,8 @@ public class DefaultAccount extends ExtendedEntityBase implements Account {
 	private final String country;
 	private final String postalCode;
 	private final String phone;
+    private final boolean isMigrated;
+    private final boolean isNotifiedForInvoices;
     private final String updatedBy;
     private final DateTime updatedDate;
     
@@ -81,33 +83,14 @@ public class DefaultAccount extends ExtendedEntityBase implements Account {
 				data.getTimeZone(), data.getLocale(),
 				data.getAddress1(), data.getAddress2(), data.getCompanyName(),
 				data.getCity(), data.getStateOrProvince(), data.getCountry(),
-				data.getPostalCode(), data.getPhone(), createdBy, createdDate,
+				data.getPostalCode(), data.getPhone(), data.isMigrated(), data.isNotifiedForInvoices(),
+                createdBy, createdDate,
                 updatedBy, updatedDate);
 	}
 
-	/**
+	/*
 	 * This call is used for testing and update from an existing account
-	 * @param id
-	 * @param externalKey
-	 * @param email
-	 * @param name
-	 * @param firstNameLength
-	 * @param currency
-	 * @param billCycleDay
-	 * @param paymentProviderName
-	 * @param timeZone
-	 * @param locale
-	 * @param address1
-	 * @param address2
-	 * @param companyName
-	 * @param city
-	 * @param stateOrProvince
-	 * @param country
-	 * @param postalCode
-	 * @param phone
-	 * @param createdDate
-	 * @param updatedDate
-	 */
+     */
 	public DefaultAccount(final UUID id, final String externalKey, final String email,
                           final String name, final int firstNameLength,
                           final Currency currency, final int billCycleDay, final String paymentProviderName,
@@ -115,6 +98,7 @@ public class DefaultAccount extends ExtendedEntityBase implements Account {
                           final String address1, final String address2, final String companyName,
                           final String city, final String stateOrProvince, final String country,
                           final String postalCode, final String phone,
+                          final boolean isMigrated, final boolean isNotifiedForInvoices,
                           final String createdBy, final DateTime createdDate,
                           final String updatedBy, final DateTime updatedDate) {
 
@@ -136,6 +120,8 @@ public class DefaultAccount extends ExtendedEntityBase implements Account {
 		this.postalCode = postalCode;
 		this.country = country;
 		this.phone = phone;
+        this.isMigrated = isMigrated;
+        this.isNotifiedForInvoices = isNotifiedForInvoices;
         this.updatedBy = updatedBy;
         this.updatedDate = updatedDate;
 	}
@@ -241,6 +227,16 @@ public class DefaultAccount extends ExtendedEntityBase implements Account {
 	}
 
     @Override
+    public boolean isMigrated() {
+        return this.isMigrated;
+    }
+
+    @Override
+    public boolean isNotifiedForInvoices() {
+        return isNotifiedForInvoices;
+    }
+
+    @Override
     public String getUpdatedBy() {
         return updatedBy;
     }
@@ -279,7 +275,7 @@ public class DefaultAccount extends ExtendedEntityBase implements Account {
 				", stateOrProvince=" + stateOrProvince +
 				", postalCode=" + postalCode +
 				", country=" + country +
-				", tags=" + tags +
+				", tags=" + tagStore +
 				", fields=" + fields +
 				"]";
 	}
diff --git a/account/src/main/java/com/ning/billing/account/api/DefaultAccountEmail.java b/account/src/main/java/com/ning/billing/account/api/DefaultAccountEmail.java
new file mode 100644
index 0000000..dd9323d
--- /dev/null
+++ b/account/src/main/java/com/ning/billing/account/api/DefaultAccountEmail.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you 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.ning.billing.account.api;
+
+import com.ning.billing.util.entity.UpdatableEntityBase;
+import org.joda.time.DateTime;
+
+import java.util.UUID;
+
+public class DefaultAccountEmail extends UpdatableEntityBase implements AccountEmail {
+    private final UUID accountId;
+    private final String email;
+
+    public DefaultAccountEmail(UUID accountId, String email) {
+        super();
+        this.accountId = accountId;
+        this.email = email;
+    }
+
+    public DefaultAccountEmail(AccountEmail source, String newEmail) {
+        this(source.getId(), source.getAccountId(), newEmail,
+             source.getCreatedBy(), source.getCreatedDate(), source.getUpdatedBy(), source.getUpdatedDate());
+    }
+
+    public DefaultAccountEmail(UUID id, UUID accountId, String email, String createdBy, DateTime createdDate, String updatedBy, DateTime updatedDate) {
+        super(id, createdBy, createdDate, updatedBy, updatedDate);
+        this.accountId = accountId;
+        this.email = email;
+    }
+
+    @Override
+    public UUID getAccountId() {
+        return accountId;
+    }
+
+    @Override
+    public String getEmail() {
+        return email;
+    }
+}
diff --git a/account/src/main/java/com/ning/billing/account/api/DefaultMutableAccountData.java b/account/src/main/java/com/ning/billing/account/api/DefaultMutableAccountData.java
index 4476130..0f72d41 100644
--- a/account/src/main/java/com/ning/billing/account/api/DefaultMutableAccountData.java
+++ b/account/src/main/java/com/ning/billing/account/api/DefaultMutableAccountData.java
@@ -16,13 +16,12 @@
 
 package com.ning.billing.account.api;
 
-import org.joda.time.DateTime;
 import org.joda.time.DateTimeZone;
 
 import com.ning.billing.catalog.api.Currency;
 import com.ning.billing.util.tag.TagStore;
 
-public class DefaultMutableAccountData implements AccountData, MutableAccountData {
+public class DefaultMutableAccountData implements MutableAccountData {
     private String externalKey;
     private String email;
     private String name;
@@ -40,6 +39,8 @@ public class DefaultMutableAccountData implements AccountData, MutableAccountDat
     private String country;
     private String postalCode;
     private String phone;
+    private boolean isMigrated;
+    private boolean isNotifiedForInvoices;
     
     public DefaultMutableAccountData(String externalKey, String email, String name,
             int firstNameLength, Currency currency, int billCycleDay,
@@ -47,7 +48,7 @@ public class DefaultMutableAccountData implements AccountData, MutableAccountDat
             String locale, String address1, String address2,
             String companyName, String city, String stateOrProvince,
             String country, String postalCode, String phone,
-            DateTime createdDate, DateTime updatedDate) {
+            boolean isMigrated, boolean isNotifiedForInvoices) {
         super();
         this.externalKey = externalKey;
         this.email = email;
@@ -66,6 +67,8 @@ public class DefaultMutableAccountData implements AccountData, MutableAccountDat
         this.country = country;
         this.postalCode = postalCode;
         this.phone = phone;
+        this.isMigrated = isMigrated;
+        this.isNotifiedForInvoices = isNotifiedForInvoices;
     }
     
     public DefaultMutableAccountData(AccountData accountData) {
@@ -87,6 +90,8 @@ public class DefaultMutableAccountData implements AccountData, MutableAccountDat
         this.country = accountData.getCountry();
         this.postalCode = accountData.getPostalCode();
         this.phone = accountData.getPhone();
+        this.isMigrated = accountData.isMigrated();
+        this.isNotifiedForInvoices = accountData.isNotifiedForInvoices();
     }
 
     /* (non-Javadoc)
@@ -208,7 +213,20 @@ public class DefaultMutableAccountData implements AccountData, MutableAccountDat
     public String getPhone() {
         return phone;
     }
-    
+    /* (non-Javadoc)
+     * @see com.ning.billing.account.api.MutableAccountData#isMigrated()
+     */
+    @Override
+    public boolean isMigrated() {
+        return isMigrated;
+    }
+    /* (non-Javadoc)
+     * @see com.ning.billing.account.api.MutableAccountData#getSendInvoiceEmails()
+     */
+    @Override
+    public boolean isNotifiedForInvoices() {
+        return isNotifiedForInvoices;
+    }
     /* (non-Javadoc)
      * @see com.ning.billing.account.api.MutableAccountData#setExternalKey(java.lang.String)
      */
@@ -329,5 +347,15 @@ public class DefaultMutableAccountData implements AccountData, MutableAccountDat
         this.phone = phone;
     }
 
+    @Override
+    public void setIsMigrated(boolean isMigrated) {
+        this.isMigrated = isMigrated;
+    }
+
+    @Override
+    public void setIsNotifiedForInvoices(boolean isNotifiedForInvoices) {
+        this.isNotifiedForInvoices = isNotifiedForInvoices;
+    }
+
 
 }
diff --git a/account/src/main/java/com/ning/billing/account/api/user/AccountBuilder.java b/account/src/main/java/com/ning/billing/account/api/user/AccountBuilder.java
index ed18ffb..a6d3840 100644
--- a/account/src/main/java/com/ning/billing/account/api/user/AccountBuilder.java
+++ b/account/src/main/java/com/ning/billing/account/api/user/AccountBuilder.java
@@ -43,6 +43,8 @@ public class AccountBuilder {
     private String country;
     private String postalCode;
     private String phone;
+    private boolean migrated;
+    private boolean isNotifiedForInvoices;
     private String createdBy;
     private DateTime createdDate;
     private String updatedBy;
@@ -141,6 +143,16 @@ public class AccountBuilder {
         return this;
     }
 
+    public AccountBuilder migrated(final boolean migrated) {
+        this.migrated = migrated;
+        return this;
+    }
+
+    public AccountBuilder isNotifiedForInvoices(final boolean isNotifiedForInvoices) {
+        this.isNotifiedForInvoices = isNotifiedForInvoices;
+        return this;
+    }
+
     public AccountBuilder createdBy(final String createdBy) {
         this.createdBy = createdBy;
         return this;
@@ -166,6 +178,7 @@ public class AccountBuilder {
                                   currency, billingCycleDay, paymentProviderName,
                                   timeZone, locale,
                                   address1, address2, companyName, city, stateOrProvince, country,
-                                  postalCode, phone, createdBy, createdDate, updatedBy, updatedDate);
+                                  postalCode, phone, migrated, isNotifiedForInvoices,
+                                  createdBy, createdDate, updatedBy, updatedDate);
     }
 }
diff --git a/account/src/main/java/com/ning/billing/account/api/user/DefaultAccountCreationEvent.java b/account/src/main/java/com/ning/billing/account/api/user/DefaultAccountCreationEvent.java
index 2a1b54a..f944ccb 100644
--- a/account/src/main/java/com/ning/billing/account/api/user/DefaultAccountCreationEvent.java
+++ b/account/src/main/java/com/ning/billing/account/api/user/DefaultAccountCreationEvent.java
@@ -20,7 +20,6 @@ import com.ning.billing.account.api.Account;
 import com.ning.billing.account.api.AccountCreationEvent;
 import com.ning.billing.account.api.AccountData;
 import com.ning.billing.catalog.api.Currency;
-import com.ning.billing.util.bus.BusEvent.BusEventType;
 
 import java.util.UUID;
 
@@ -129,6 +128,8 @@ public class DefaultAccountCreationEvent implements AccountCreationEvent {
         private final String postalCode;
         private final String country;
         private final String phone;
+        private final boolean isMigrated;
+        private final boolean isNotifiedForInvoices;
         
         @JsonCreator
         public DefaultAccountData(@JsonProperty("externalKey") String externalKey,
@@ -147,7 +148,9 @@ public class DefaultAccountCreationEvent implements AccountCreationEvent {
                 @JsonProperty("stateOrProvince") String stateOrProvince,
                 @JsonProperty("postalCode") String postalCode,
                 @JsonProperty("country") String country,
-                @JsonProperty("phone") String phone) {
+                @JsonProperty("phone") String phone,
+                @JsonProperty("isMigrated") boolean isMigrated,
+                @JsonProperty("isNotifiedForInvoices") boolean isNotifiedForInvoices) {
             super();
             this.externalKey = externalKey;
             this.name = name;
@@ -166,6 +169,8 @@ public class DefaultAccountCreationEvent implements AccountCreationEvent {
             this.postalCode = postalCode;
             this.country = country;
             this.phone = phone;
+            this.isMigrated = isMigrated;
+            this.isNotifiedForInvoices = isNotifiedForInvoices;
         }
 
         @Override
@@ -214,7 +219,6 @@ public class DefaultAccountCreationEvent implements AccountCreationEvent {
             return timeZone;
         }
 
-
         @Override
         public String getLocale() {
             return locale;
@@ -261,6 +265,18 @@ public class DefaultAccountCreationEvent implements AccountCreationEvent {
         }
 
         @Override
+        @JsonIgnore
+        public boolean isMigrated() {
+            return isMigrated;
+        }
+
+        @Override
+        @JsonIgnore
+        public boolean isNotifiedForInvoices() {
+            return isNotifiedForInvoices;
+        }
+
+        @Override
         public int hashCode() {
             final int prime = 31;
             int result = 1;
diff --git a/account/src/main/java/com/ning/billing/account/api/user/DefaultAccountUserApi.java b/account/src/main/java/com/ning/billing/account/api/user/DefaultAccountUserApi.java
index 1e5a8a6..e8df191 100644
--- a/account/src/main/java/com/ning/billing/account/api/user/DefaultAccountUserApi.java
+++ b/account/src/main/java/com/ning/billing/account/api/user/DefaultAccountUserApi.java
@@ -26,6 +26,8 @@ import com.ning.billing.ErrorCode;
 import com.ning.billing.account.api.Account;
 import com.ning.billing.account.api.AccountApiException;
 import com.ning.billing.account.api.AccountData;
+import com.ning.billing.account.api.AccountEmail;
+import com.ning.billing.account.api.AccountUserApi;
 import com.ning.billing.account.api.DefaultAccount;
 import com.ning.billing.account.api.MigrationAccountData;
 import com.ning.billing.account.dao.AccountDao;
@@ -33,9 +35,9 @@ import com.ning.billing.util.callcontext.CallContext;
 import com.ning.billing.util.callcontext.CallContextFactory;
 import com.ning.billing.util.customfield.CustomField;
 import com.ning.billing.util.entity.EntityPersistenceException;
-import com.ning.billing.util.tag.Tag;
+import com.ning.billing.util.tag.TagDefinition;
 
-public class DefaultAccountUserApi implements com.ning.billing.account.api.AccountUserApi {
+public class DefaultAccountUserApi implements AccountUserApi {
     private final CallContextFactory factory;
     private final AccountDao dao;
 
@@ -47,10 +49,10 @@ public class DefaultAccountUserApi implements com.ning.billing.account.api.Accou
 
     @Override
     public Account createAccount(final AccountData data, final List<CustomField> fields,
-                                 final List<Tag> tags, final CallContext context) throws AccountApiException {
+                                 final List<TagDefinition> tagDefinitions, final CallContext context) throws AccountApiException {
         Account account = new DefaultAccount(data);
         account.setFields(fields);
-        account.addTags(tags);
+        account.addTagsFromDefinitions(tagDefinitions);
 
         try {
             dao.create(account, context);
@@ -108,7 +110,6 @@ public class DefaultAccountUserApi implements com.ning.billing.account.api.Accou
         } catch (EntityPersistenceException e) {
             throw new AccountApiException(e, e.getCode(), e.getMessage());
         }
-  
     }
 
     @Override
@@ -122,14 +123,14 @@ public class DefaultAccountUserApi implements com.ning.billing.account.api.Accou
 
 	@Override
 	public Account migrateAccount(final MigrationAccountData data, final List<CustomField> fields,
-                                  final List<Tag> tags, final CallContext context)
+                                  final List<TagDefinition> tagDefinitions, final CallContext context)
             throws AccountApiException {
         DateTime createdDate = data.getCreatedDate() == null ? context.getCreatedDate() : data.getCreatedDate();
         DateTime updatedDate = data.getUpdatedDate() == null ? context.getUpdatedDate() : data.getUpdatedDate();
         CallContext migrationContext = factory.toMigrationCallContext(context, createdDate, updatedDate);
 		Account account = new DefaultAccount(data);
         account.setFields(fields);
-        account.addTags(tags);
+        account.addTagsFromDefinitions(tagDefinitions);
 
         try {
             dao.create(account, migrationContext);
@@ -140,5 +141,13 @@ public class DefaultAccountUserApi implements com.ning.billing.account.api.Accou
         return account;
 	}
 
+    @Override
+    public List<AccountEmail> getEmails(final UUID accountId) {
+        return dao.getEmails(accountId);
+    }
 
+    @Override
+    public void saveEmails(final UUID accountId, final List<AccountEmail> newEmails, final CallContext context) {
+        dao.saveEmails(accountId, newEmails, context);
+    }
 }
diff --git a/account/src/main/java/com/ning/billing/account/dao/AccountBinder.java b/account/src/main/java/com/ning/billing/account/dao/AccountBinder.java
index a4a00a8..57b61c2 100644
--- a/account/src/main/java/com/ning/billing/account/dao/AccountBinder.java
+++ b/account/src/main/java/com/ning/billing/account/dao/AccountBinder.java
@@ -60,6 +60,8 @@ public @interface AccountBinder {
                     q.bind("country", account.getCountry());
                     q.bind("postalCode", account.getPostalCode());
                     q.bind("phone", account.getPhone());
+                    q.bind("migrated", account.isMigrated());
+                    q.bind("isNotifiedForInvoices", account.isNotifiedForInvoices());
                 }
             };
         }
diff --git a/account/src/main/java/com/ning/billing/account/dao/AccountDao.java b/account/src/main/java/com/ning/billing/account/dao/AccountDao.java
index 4be1058..00f785f 100644
--- a/account/src/main/java/com/ning/billing/account/dao/AccountDao.java
+++ b/account/src/main/java/com/ning/billing/account/dao/AccountDao.java
@@ -16,12 +16,16 @@
 
 package com.ning.billing.account.dao;
 
+import java.util.List;
 import java.util.UUID;
 import com.ning.billing.account.api.Account;
 import com.ning.billing.account.api.AccountApiException;
+import com.ning.billing.account.api.AccountEmail;
 import com.ning.billing.util.callcontext.CallContext;
 import com.ning.billing.util.entity.UpdatableEntityDao;
 
+import javax.annotation.Nullable;
+
 public interface AccountDao extends UpdatableEntityDao<Account> {
     public Account getAccountByKey(String key);
 
@@ -32,4 +36,8 @@ public interface AccountDao extends UpdatableEntityDao<Account> {
      * @throws AccountApiException when externalKey is null
      */
     public UUID getIdFromKey(String externalKey) throws AccountApiException;
+
+    public List<AccountEmail> getEmails(UUID accountId);
+
+    public void saveEmails(UUID accountId, List<AccountEmail> emails, CallContext context);
 }
\ No newline at end of file
diff --git a/account/src/main/java/com/ning/billing/account/dao/AccountEmailBinder.java b/account/src/main/java/com/ning/billing/account/dao/AccountEmailBinder.java
new file mode 100644
index 0000000..bd71364
--- /dev/null
+++ b/account/src/main/java/com/ning/billing/account/dao/AccountEmailBinder.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you 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.ning.billing.account.dao;
+
+import com.ning.billing.account.api.AccountEmail;
+import org.skife.jdbi.v2.SQLStatement;
+import org.skife.jdbi.v2.sqlobject.Binder;
+import org.skife.jdbi.v2.sqlobject.BinderFactory;
+import org.skife.jdbi.v2.sqlobject.BindingAnnotation;
+
+import java.lang.annotation.Annotation;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@BindingAnnotation(AccountEmailBinder.AccountEmailBinderFactory.class)
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.PARAMETER})
+public @interface AccountEmailBinder {
+    public static class AccountEmailBinderFactory implements BinderFactory {
+        @Override
+        public Binder<AccountEmailBinder, AccountEmail> build(Annotation annotation) {
+            return new Binder<AccountEmailBinder, AccountEmail>() {
+                @Override
+                public void bind(@SuppressWarnings("rawtypes") SQLStatement q, AccountEmailBinder bind, AccountEmail accountEmail) {
+                    q.bind("id", accountEmail.getId().toString());
+                    q.bind("accountId", accountEmail.getAccountId().toString());
+                    q.bind("email", accountEmail.getEmail());
+                }
+            };
+        }
+    }
+}
diff --git a/account/src/main/java/com/ning/billing/account/dao/AccountEmailMapper.java b/account/src/main/java/com/ning/billing/account/dao/AccountEmailMapper.java
new file mode 100644
index 0000000..9444941
--- /dev/null
+++ b/account/src/main/java/com/ning/billing/account/dao/AccountEmailMapper.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you 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.ning.billing.account.dao;
+
+import com.ning.billing.account.api.AccountEmail;
+import com.ning.billing.account.api.DefaultAccountEmail;
+import com.ning.billing.util.dao.MapperBase;
+import org.joda.time.DateTime;
+import org.skife.jdbi.v2.StatementContext;
+import org.skife.jdbi.v2.tweak.ResultSetMapper;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.UUID;
+
+public class AccountEmailMapper extends MapperBase implements ResultSetMapper<AccountEmail> {
+    @Override
+    public AccountEmail map(int index, ResultSet result, StatementContext context) throws SQLException {
+        UUID id = UUID.fromString(result.getString("id"));
+        UUID accountId = UUID.fromString(result.getString("account_id"));
+        String email = result.getString("email");
+
+        String createdBy = result.getString("created_by");
+        DateTime createdDate = getDate(result, "created_date");
+        String updatedBy = result.getString("updated_by");
+        DateTime updatedDate = getDate(result, "updated_date");
+
+        return new DefaultAccountEmail(id, accountId, email, createdBy, createdDate, updatedBy, updatedDate);
+    }
+}
diff --git a/account/src/main/java/com/ning/billing/account/dao/AccountEmailSqlDao.java b/account/src/main/java/com/ning/billing/account/dao/AccountEmailSqlDao.java
new file mode 100644
index 0000000..1902154
--- /dev/null
+++ b/account/src/main/java/com/ning/billing/account/dao/AccountEmailSqlDao.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you 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.ning.billing.account.dao;
+
+import com.ning.billing.account.api.AccountEmail;
+import com.ning.billing.util.ChangeType;
+import com.ning.billing.util.callcontext.CallContext;
+import com.ning.billing.util.callcontext.CallContextBinder;
+import com.ning.billing.util.dao.ChangeTypeBinder;
+import com.ning.billing.util.entity.UpdatableEntityDao;
+import org.skife.jdbi.v2.sqlobject.Bind;
+import org.skife.jdbi.v2.sqlobject.SqlBatch;
+import org.skife.jdbi.v2.sqlobject.SqlQuery;
+import org.skife.jdbi.v2.sqlobject.SqlUpdate;
+import org.skife.jdbi.v2.sqlobject.customizers.RegisterMapper;
+import org.skife.jdbi.v2.sqlobject.mixins.Transactional;
+import org.skife.jdbi.v2.sqlobject.mixins.Transmogrifier;
+import org.skife.jdbi.v2.sqlobject.stringtemplate.ExternalizedSqlViaStringTemplate3;
+
+import java.util.List;
+
+@ExternalizedSqlViaStringTemplate3
+@RegisterMapper(AccountEmailMapper.class)
+public interface AccountEmailSqlDao extends UpdatableEntityDao<AccountEmail>, Transactional<AccountEmailSqlDao>, Transmogrifier {
+    @Override
+    @SqlUpdate
+    public void create(@AccountEmailBinder final AccountEmail accountEmail,
+                       @CallContextBinder final CallContext context);
+
+    @SqlBatch(transactional = false)
+    public void create(@AccountEmailBinder final List<AccountEmail> accountEmailList,
+                       @CallContextBinder final CallContext context);
+
+    @Override
+    @SqlUpdate
+    public void update(@AccountEmailBinder final AccountEmail accountEmail,
+                       @CallContextBinder final CallContext context);
+
+    @SqlBatch(transactional = false)
+    public void update(@AccountEmailBinder final List<AccountEmail> accountEmailList,
+                       @CallContextBinder final CallContext context);
+
+    @SqlUpdate
+    public void delete(@AccountEmailBinder final AccountEmail accountEmail,
+                       @CallContextBinder final CallContext context);
+
+    @SqlBatch(transactional = false)
+    public void delete(@AccountEmailBinder final List<AccountEmail> accountEmailList,
+                       @CallContextBinder final CallContext context);
+
+    @SqlBatch(transactional=false)
+    public void insertAccountEmailHistoryFromTransaction(@Bind("historyRecordId") final List<String> historyRecordIdList,
+                                                         @AccountEmailBinder final List<AccountEmail> accountEmail,
+                                                         @ChangeTypeBinder final ChangeType changeType,
+                                                         @CallContextBinder final CallContext context);
+
+    @SqlQuery
+    public List<AccountEmail> getByAccountId(@Bind("accountId") final String accountId);
+}
diff --git a/account/src/main/java/com/ning/billing/account/dao/AccountMapper.java b/account/src/main/java/com/ning/billing/account/dao/AccountMapper.java
new file mode 100644
index 0000000..af8be67
--- /dev/null
+++ b/account/src/main/java/com/ning/billing/account/dao/AccountMapper.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you 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.ning.billing.account.dao;
+
+import com.ning.billing.account.api.Account;
+import com.ning.billing.account.api.user.AccountBuilder;
+import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.util.dao.MapperBase;
+import org.joda.time.DateTime;
+import org.joda.time.DateTimeZone;
+import org.skife.jdbi.v2.StatementContext;
+import org.skife.jdbi.v2.tweak.ResultSetMapper;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.UUID;
+
+public class AccountMapper extends MapperBase implements ResultSetMapper<Account> {
+    @Override
+    public Account map(int index, ResultSet result, StatementContext context) throws SQLException {
+        UUID id = UUID.fromString(result.getString("id"));
+        String externalKey = result.getString("external_key");
+        String email = result.getString("email");
+        String name = result.getString("name");
+        int firstNameLength = result.getInt("first_name_length");
+        int billingCycleDay = result.getInt("billing_cycle_day");
+
+        String currencyString = result.getString("currency");
+        Currency currency = (currencyString == null) ? null : Currency.valueOf(currencyString);
+
+        String paymentProviderName = result.getString("payment_provider_name");
+
+        String timeZoneId = result.getString("time_zone");
+        DateTimeZone timeZone = (timeZoneId == null) ? null : DateTimeZone.forID(timeZoneId);
+
+        String locale = result.getString("locale");
+
+        String address1 = result.getString("address1");
+        String address2 = result.getString("address2");
+        String companyName = result.getString("company_name");
+        String city = result.getString("city");
+        String stateOrProvince = result.getString("state_or_province");
+        String postalCode = result.getString("postal_code");
+        String country = result.getString("country");
+        String phone = result.getString("phone");
+
+        Boolean migrated = result.getBoolean("migrated");
+        Boolean isNotifiedForInvoices = result.getBoolean("is_notified_for_invoices");
+
+        String createdBy = result.getString("created_by");
+        DateTime createdDate = getDate(result, "created_date");
+        String updatedBy = result.getString("updated_by");
+        DateTime updatedDate = getDate(result, "updated_date");
+
+        return new AccountBuilder(id).externalKey(externalKey).email(email)
+                                     .name(name).firstNameLength(firstNameLength)
+                                     .phone(phone).currency(currency)
+                                     .billingCycleDay(billingCycleDay)
+                                     .paymentProviderName(paymentProviderName)
+                                     .timeZone(timeZone).locale(locale)
+                                     .address1(address1).address2(address2)
+                                     .companyName(companyName)
+                                     .city(city).stateOrProvince(stateOrProvince)
+                                     .postalCode(postalCode).country(country)
+                                     .migrated(migrated).isNotifiedForInvoices(isNotifiedForInvoices)
+                                     .createdBy(createdBy).createdDate(createdDate)
+                                     .updatedBy(updatedBy).updatedDate(updatedDate)
+                                     .build();
+    }
+}
\ No newline at end of file
diff --git a/account/src/main/java/com/ning/billing/account/dao/AccountSqlDao.java b/account/src/main/java/com/ning/billing/account/dao/AccountSqlDao.java
index 8c09af4..298ed72 100644
--- a/account/src/main/java/com/ning/billing/account/dao/AccountSqlDao.java
+++ b/account/src/main/java/com/ning/billing/account/dao/AccountSqlDao.java
@@ -16,17 +16,13 @@
 
 package com.ning.billing.account.dao;
 
-import java.sql.ResultSet;
-import java.sql.SQLException;
 import java.util.UUID;
 
+import com.ning.billing.util.ChangeType;
 import com.ning.billing.util.callcontext.CallContext;
 import com.ning.billing.util.callcontext.CallContextBinder;
-import com.ning.billing.util.dao.MapperBase;
+import com.ning.billing.util.dao.ChangeTypeBinder;
 import com.ning.billing.util.entity.UpdatableEntityDao;
-import org.joda.time.DateTime;
-import org.joda.time.DateTimeZone;
-import org.skife.jdbi.v2.StatementContext;
 import org.skife.jdbi.v2.sqlobject.Bind;
 import org.skife.jdbi.v2.sqlobject.SqlQuery;
 import org.skife.jdbi.v2.sqlobject.SqlUpdate;
@@ -34,15 +30,12 @@ import org.skife.jdbi.v2.sqlobject.customizers.RegisterMapper;
 import org.skife.jdbi.v2.sqlobject.mixins.Transactional;
 import org.skife.jdbi.v2.sqlobject.mixins.Transmogrifier;
 import org.skife.jdbi.v2.sqlobject.stringtemplate.ExternalizedSqlViaStringTemplate3;
-import org.skife.jdbi.v2.tweak.ResultSetMapper;
 
 import com.ning.billing.account.api.Account;
-import com.ning.billing.account.api.user.AccountBuilder;
-import com.ning.billing.catalog.api.Currency;
 import com.ning.billing.util.UuidMapper;
 
 @ExternalizedSqlViaStringTemplate3
-@RegisterMapper({UuidMapper.class, AccountSqlDao.AccountMapper.class})
+@RegisterMapper({UuidMapper.class, AccountMapper.class})
 public interface AccountSqlDao extends UpdatableEntityDao<Account>, Transactional<AccountSqlDao>, Transmogrifier {
     @SqlQuery
     public Account getAccountByKey(@Bind("externalKey") final String key);
@@ -58,53 +51,9 @@ public interface AccountSqlDao extends UpdatableEntityDao<Account>, Transactiona
     @SqlUpdate
     public void update(@AccountBinder Account account, @CallContextBinder final CallContext context);
 
-    public static class AccountMapper extends MapperBase implements ResultSetMapper<Account> {
-        @Override
-        public Account map(int index, ResultSet result, StatementContext context) throws SQLException {
-            UUID id = UUID.fromString(result.getString("id"));
-            String externalKey = result.getString("external_key");
-            String email = result.getString("email");
-            String name = result.getString("name");
-            int firstNameLength = result.getInt("first_name_length");
-            int billingCycleDay = result.getInt("billing_cycle_day");
-
-            String currencyString = result.getString("currency");
-            Currency currency = (currencyString == null) ? null : Currency.valueOf(currencyString);
-
-            String paymentProviderName = result.getString("payment_provider_name");
-
-            String timeZoneId = result.getString("time_zone");
-            DateTimeZone timeZone = (timeZoneId == null) ? null : DateTimeZone.forID(timeZoneId);
-
-            String locale = result.getString("locale");
-
-            String address1 = result.getString("address1");
-            String address2 = result.getString("address2");
-            String companyName = result.getString("company_name");
-            String city = result.getString("city");
-            String stateOrProvince = result.getString("state_or_province");
-            String postalCode = result.getString("postal_code");
-            String country = result.getString("country");
-            String phone = result.getString("phone");
-
-            String createdBy = result.getString("created_by");
-            DateTime createdDate = getDate(result, "created_date");
-            String updatedBy = result.getString("updated_by");
-            DateTime updatedDate = getDate(result, "updated_date");
-
-            return new AccountBuilder(id).externalKey(externalKey).email(email)
-                                         .name(name).firstNameLength(firstNameLength)
-                                         .phone(phone).currency(currency)
-                                         .billingCycleDay(billingCycleDay)
-                                         .paymentProviderName(paymentProviderName)
-                                         .timeZone(timeZone).locale(locale)
-                                         .address1(address1).address2(address2)
-                                         .companyName(companyName)
-                                         .city(city).stateOrProvince(stateOrProvince)
-                                         .postalCode(postalCode).country(country)
-                                         .createdBy(createdBy).createdDate(createdDate)
-                                         .updatedBy(updatedBy).updatedDate(updatedDate)
-                                         .build();
-        }
-    }
+    @SqlUpdate
+    public void insertAccountHistoryFromTransaction(@AccountBinder final Account account,
+                                                    @Bind("historyRecordId") final String historyRecordId,
+                                                    @ChangeTypeBinder ChangeType changeType,
+                                                    @CallContextBinder CallContext context);
 }
diff --git a/account/src/main/java/com/ning/billing/account/dao/AuditedAccountDao.java b/account/src/main/java/com/ning/billing/account/dao/AuditedAccountDao.java
index aa628ca..b68ae6f 100644
--- a/account/src/main/java/com/ning/billing/account/dao/AuditedAccountDao.java
+++ b/account/src/main/java/com/ning/billing/account/dao/AuditedAccountDao.java
@@ -17,13 +17,17 @@
 package com.ning.billing.account.dao;
 
 import java.sql.DataTruncation;
+import java.util.ArrayList;
+import java.util.Iterator;
 import java.util.List;
 import java.util.UUID;
 
+import com.ning.billing.account.api.AccountEmail;
 import com.ning.billing.util.ChangeType;
 import com.ning.billing.util.audit.dao.AuditSqlDao;
 import com.ning.billing.util.callcontext.CallContext;
 import com.ning.billing.util.customfield.dao.CustomFieldDao;
+import com.ning.billing.util.dao.AuditedDaoBase;
 import com.ning.billing.util.entity.EntityPersistenceException;
 import com.ning.billing.util.tag.dao.TagDao;
 import org.skife.jdbi.v2.IDBI;
@@ -42,8 +46,10 @@ import com.ning.billing.util.customfield.dao.CustomFieldSqlDao;
 import com.ning.billing.util.bus.Bus;
 import com.ning.billing.util.tag.Tag;
 
-public class AuditedAccountDao implements AccountDao {
+public class AuditedAccountDao extends AuditedDaoBase implements AccountDao {
+    private static final String ACCOUNT_EMAIL_HISTORY_TABLE = "account_email_history";
     private final AccountSqlDao accountSqlDao;
+    private final AccountEmailSqlDao accountEmailSqlDao;
     private final TagDao tagDao;
     private final CustomFieldDao customFieldDao;
     private final Bus eventBus;
@@ -52,6 +58,7 @@ public class AuditedAccountDao implements AccountDao {
     public AuditedAccountDao(IDBI dbi, Bus eventBus, TagDao tagDao, CustomFieldDao customFieldDao) {
         this.eventBus = eventBus;
         this.accountSqlDao = dbi.onDemand(AccountSqlDao.class);
+        this.accountEmailSqlDao = dbi.onDemand(AccountEmailSqlDao.class);
         this.tagDao = tagDao;
         this.customFieldDao = customFieldDao;
     }
@@ -124,9 +131,7 @@ public class AuditedAccountDao implements AccountDao {
                     transactionalDao.create(account, context);
                     UUID historyId = UUID.randomUUID();
 
-                    AccountHistorySqlDao historyDao = accountSqlDao.become(AccountHistorySqlDao.class);
-                    historyDao.insertAccountHistoryFromTransaction(account, historyId.toString(),
-                            ChangeType.INSERT.toString(), context);
+                    accountSqlDao.insertAccountHistoryFromTransaction(account, historyId.toString(), ChangeType.INSERT, context);
 
                     AuditSqlDao auditDao = accountSqlDao.become(AuditSqlDao.class);
                     auditDao.insertAuditFromTransaction("account_history", historyId.toString(),
@@ -170,12 +175,10 @@ public class AuditedAccountDao implements AccountDao {
                     accountSqlDao.update(account, context);
 
                     UUID historyId = UUID.randomUUID();
-                    AccountHistorySqlDao historyDao = accountSqlDao.become(AccountHistorySqlDao.class);
-                    historyDao.insertAccountHistoryFromTransaction(account, historyId.toString(), ChangeType.UPDATE.toString(), context);
+                    accountSqlDao.insertAccountHistoryFromTransaction(account, historyId.toString(), ChangeType.UPDATE, context);
 
                     AuditSqlDao auditDao = accountSqlDao.become(AuditSqlDao.class);
-                    auditDao.insertAuditFromTransaction("account_history" ,historyId.toString(),
-                                                        ChangeType.INSERT, context);
+                    auditDao.insertAuditFromTransaction("account_history" ,historyId.toString(), ChangeType.INSERT, context);
 
                     saveTagsFromWithinTransaction(account, accountSqlDao, context);
                     saveCustomFieldsFromWithinTransaction(account, accountSqlDao, context);
@@ -197,6 +200,64 @@ public class AuditedAccountDao implements AccountDao {
     }
 
     @Override
+    public List<AccountEmail> getEmails(final UUID accountId) {
+        return accountEmailSqlDao.getByAccountId(accountId.toString());
+    }
+
+    @Override
+    public void saveEmails(final UUID accountId, final List<AccountEmail> emails, final CallContext context) {
+        final List<AccountEmail> existingEmails = accountEmailSqlDao.getByAccountId(accountId.toString());
+        final List<AccountEmail> updatedEmails = new ArrayList<AccountEmail>();
+
+        Iterator<AccountEmail> existingEmailIterator = existingEmails.iterator();
+        while (existingEmailIterator.hasNext()) {
+            AccountEmail existingEmail = existingEmailIterator.next();
+
+            Iterator<AccountEmail> newEmailIterator = emails.iterator();
+            while (newEmailIterator.hasNext()) {
+                AccountEmail newEmail = newEmailIterator.next();
+                if (newEmail.getId().equals(existingEmail.getId())) {
+                    // check equality; if not equal, add to updated
+                    if (!newEmail.equals(existingEmail)) {
+                        updatedEmails.add(newEmail);
+                    }
+
+                    // remove from both
+                    newEmailIterator.remove();
+                    existingEmailIterator.remove();
+                }
+            }
+        }
+
+        // remaining emails in newEmail are inserts; remaining emails in existingEmail are deletes
+        accountEmailSqlDao.inTransaction(new Transaction<Void, AccountEmailSqlDao>() {
+            @Override
+            public Void inTransaction(AccountEmailSqlDao dao, TransactionStatus transactionStatus) throws Exception {
+                dao.create(emails, context);
+                dao.update(updatedEmails, context);
+                dao.delete(existingEmails, context);
+
+                List<String> insertHistoryIdList = getIdList(emails.size());
+                List<String> updateHistoryIdList = getIdList(updatedEmails.size());
+                List<String> deleteHistoryIdList = getIdList(existingEmails.size());
+
+                // insert histories
+                dao.insertAccountEmailHistoryFromTransaction(insertHistoryIdList, emails, ChangeType.INSERT, context);
+                dao.insertAccountEmailHistoryFromTransaction(updateHistoryIdList, updatedEmails, ChangeType.UPDATE, context);
+                dao.insertAccountEmailHistoryFromTransaction(deleteHistoryIdList, existingEmails, ChangeType.DELETE, context);
+
+                // insert audits
+                AuditSqlDao auditSqlDao = dao.become(AuditSqlDao.class);
+                auditSqlDao.insertAuditFromTransaction(ACCOUNT_EMAIL_HISTORY_TABLE, insertHistoryIdList, ChangeType.INSERT, context);
+                auditSqlDao.insertAuditFromTransaction(ACCOUNT_EMAIL_HISTORY_TABLE, updateHistoryIdList, ChangeType.UPDATE, context);
+                auditSqlDao.insertAuditFromTransaction(ACCOUNT_EMAIL_HISTORY_TABLE, deleteHistoryIdList, ChangeType.DELETE, context);
+
+                return null;
+            }
+        });
+    }
+
+    @Override
     public void test() {
         accountSqlDao.test();
     }
@@ -229,4 +290,6 @@ public class AuditedAccountDao implements AccountDao {
                                                        final CallContext context) {
         customFieldDao.saveFields(transactionalDao, account.getId(), account.getObjectName(), account.getFieldList(), context);
     }
+
+
 }
diff --git a/account/src/main/resources/com/ning/billing/account/dao/AccountEmailSqlDao.sql.stg b/account/src/main/resources/com/ning/billing/account/dao/AccountEmailSqlDao.sql.stg
new file mode 100644
index 0000000..d287c08
--- /dev/null
+++ b/account/src/main/resources/com/ning/billing/account/dao/AccountEmailSqlDao.sql.stg
@@ -0,0 +1,48 @@
+group account_emails;
+
+fields(prefix) ::= <<
+    <prefix>id,
+    <prefix>account_id,
+    <prefix>email,
+    <prefix>created_by,
+    <prefix>created_date,
+    <prefix>updated_by,
+    <prefix>updated_date
+>>
+
+create() ::= <<
+    INSERT INTO account_emails(<fields()>)
+    VALUES
+    (:id, :accountId, :email, :userName, :createdDate, :userName, :updatedDate);
+>>
+
+update() ::= <<
+    UPDATE account_emails
+    SET email = :email, updated_by = :userName, updated_date = :updatedDate;
+>>
+
+delete() ::= <<
+    DELETE FROM account_emails
+    WHERE id = :id;
+>>
+
+insertAccountEmailHistoryFromTransaction() ::= <<
+    INSERT INTO account_email_history(history_record_id, id, account_id, email, change_type, updated_by, date)
+    VALUES (:historyRecordId, :id, :accountId, :email, :changeType, :userName, :updatedDate);
+>>
+
+getById() ::= <<
+    SELECT <fields()> FROM account_emails WHERE id = :id;
+>>
+
+get() ::= <<
+    SELECT <fields()> FROM account_emails;
+>>
+
+getByAccountId() ::= <<
+    SELECT <fields()> FROM account_emails WHERE account_id = :accountId;
+>>
+
+test() ::= <<
+    SELECT 1 FROM account_emails;
+>>
\ No newline at end of file
diff --git a/account/src/main/resources/com/ning/billing/account/dao/AccountSqlDao.sql.stg b/account/src/main/resources/com/ning/billing/account/dao/AccountSqlDao.sql.stg
index eda3949..6bd5e79 100644
--- a/account/src/main/resources/com/ning/billing/account/dao/AccountSqlDao.sql.stg
+++ b/account/src/main/resources/com/ning/billing/account/dao/AccountSqlDao.sql.stg
@@ -19,6 +19,8 @@ accountFields(prefix) ::= <<
     <prefix>country, 
     <prefix>postal_code,
     <prefix>phone,
+    <prefix>migrated,
+    <prefix>is_notified_for_invoices,
     <prefix>created_by,
     <prefix>created_date,
     <prefix>updated_by,
@@ -32,7 +34,7 @@ create() ::= <<
       (:id, :externalKey, :email, :name, :firstNameLength, :currency, :billingCycleDay,
       :paymentProviderName, :timeZone, :locale,
       :address1, :address2, :companyName, :city, :stateOrProvince, :country, :postalCode, :phone,
-      :userName, :createdDate, :userName, :updatedDate);
+      :migrated, :isNotifiedForInvoices, :userName, :createdDate, :userName, :updatedDate);
 >>
 
 update() ::= <<
@@ -42,10 +44,23 @@ update() ::= <<
         time_zone = :timeZone, locale = :locale,
         address1 = :address1, address2 = :address2, company_name = :companyName, city = :city, state_or_province = :stateOrProvince,
         country = :country, postal_code = :postalCode, phone = :phone,
-        updated_date = :updatedDate, updated_by = :userName
+        is_notified_for_invoices = :isNotifiedForInvoices, updated_date = :updatedDate, updated_by = :userName
     WHERE id = :id;
 >>
 
+insertAccountHistoryFromTransaction() ::= <<
+    INSERT INTO account_history
+    (history_record_id, id, external_key, email, name, first_name_length, currency,
+    billing_cycle_day, payment_provider_name, time_zone, locale,
+    address1, address2, company_name, city, state_or_province,
+    country, postal_code, phone, migrated, is_notified_for_invoices, change_type, updated_by, date)
+    VALUES
+    (:historyRecordId, :id, :externalKey, :email, :name, :firstNameLength, :currency,
+     :billingCycleDay, :paymentProviderName, :timeZone, :locale,
+     :address1, :address2, :companyName, :city, :stateOrProvince,
+     :country, :postalCode, :phone, :migrated, :isNotifiedForInvoices, :changeType, :userName, :createdDate);
+>>
+
 getAccountByKey() ::= <<
     select <accountFields()>
     from accounts
diff --git a/account/src/main/resources/com/ning/billing/account/ddl.sql b/account/src/main/resources/com/ning/billing/account/ddl.sql
index ff9a41c..266215d 100644
--- a/account/src/main/resources/com/ning/billing/account/ddl.sql
+++ b/account/src/main/resources/com/ning/billing/account/ddl.sql
@@ -19,6 +19,7 @@ CREATE TABLE accounts (
     postal_code varchar(11) DEFAULT NULL,
     phone varchar(25) DEFAULT NULL,
     migrated bool DEFAULT false,
+    is_notified_for_invoices boolean NOT NULL,
     created_date datetime NOT NULL,
     created_by varchar(50) NOT NULL,
     updated_date datetime DEFAULT NULL,
@@ -30,7 +31,7 @@ CREATE UNIQUE INDEX accounts_email ON accounts(email);
 
 DROP TABLE IF EXISTS account_history;
 CREATE TABLE account_history (
-    history_id char(36) NOT NULL,
+    history_record_id char(36) NOT NULL,
     id char(36) NOT NULL,
     external_key varchar(128) NULL,
     email varchar(50) NOT NULL,
@@ -49,8 +50,35 @@ CREATE TABLE account_history (
     country varchar(50) DEFAULT NULL,
     postal_code varchar(11) DEFAULT NULL,
     phone varchar(25) DEFAULT NULL,
+    migrated bool DEFAULT false,
+    is_notified_for_invoices boolean NOT NULL,
     change_type char(6) NOT NULL,
     updated_by varchar(50) NOT NULL,
-    date datetime
+    date datetime NOT NULL
+) ENGINE=innodb;
+CREATE INDEX account_id ON account_history(id);
+
+DROP TABLE IF EXISTS account_emails;
+CREATE TABLE account_emails (
+    id char(36) NOT NULL,
+    account_id char(36) NOT NULL,
+    email varchar(50) NOT NULL,
+    created_by varchar(50) NOT NULL,
+    created_date datetime NOT NULL,
+    updated_by varchar(50) NOT NULL,
+    updated_date datetime NOT NULL,
+    PRIMARY KEY(id)
 ) ENGINE=innodb;
-CREATE INDEX account_id ON account_history(id);
\ No newline at end of file
+CREATE INDEX account_email_account_id ON account_emails(account_id);
+
+DROP TABLE IF EXISTS account_email_history;
+CREATE TABLE account_email_history (
+    history_record_id char(36) NOT NULL,
+    id char(36) NOT NULL,
+    account_id char(36) NOT NULL,
+    email varchar(50) NOT NULL,
+    change_type char(6) NOT NULL,
+    updated_by varchar(50) NOT NULL,
+    date datetime NOT NULL,
+    PRIMARY KEY(history_record_id)
+) ENGINE=innodb;
\ No newline at end of file
diff --git a/account/src/test/java/com/ning/billing/account/api/MockAccountUserApi.java b/account/src/test/java/com/ning/billing/account/api/MockAccountUserApi.java
index d2fea9e..9396541 100644
--- a/account/src/test/java/com/ning/billing/account/api/MockAccountUserApi.java
+++ b/account/src/test/java/com/ning/billing/account/api/MockAccountUserApi.java
@@ -22,11 +22,11 @@ import java.util.UUID;
 import java.util.concurrent.CopyOnWriteArrayList;
 
 import com.ning.billing.util.callcontext.CallContext;
+import com.ning.billing.util.tag.TagDefinition;
 import org.joda.time.DateTimeZone;
 
 import com.ning.billing.catalog.api.Currency;
 import com.ning.billing.util.customfield.CustomField;
-import com.ning.billing.util.tag.Tag;
 
 public class MockAccountUserApi implements AccountUserApi {
     private final CopyOnWriteArrayList<Account> accounts = new CopyOnWriteArrayList<Account>();
@@ -53,14 +53,15 @@ public class MockAccountUserApi implements AccountUserApi {
 		Account result = new DefaultAccount(id, externalKey, email, name,
 				firstNameLength, currency, billCycleDay, paymentProviderName,
 				timeZone, locale, address1, address2, companyName, city,
-				stateOrProvince, country, postalCode, phone, null, null, null, null);
+				stateOrProvince, country, postalCode, phone, false, false,
+                null, null, null, null);
 		accounts.add(result);
 		return result;
 	}
 
     @Override
     public Account createAccount(final AccountData data, final List<CustomField> fields,
-                                 final List<Tag> tags, final CallContext context) throws AccountApiException {
+                                 final List<TagDefinition> tagDefinitions, final CallContext context) throws AccountApiException {
         Account result = new DefaultAccount(data);
         accounts.add(result);
         return result;
@@ -102,13 +103,23 @@ public class MockAccountUserApi implements AccountUserApi {
     }
 
     @Override
+    public List<AccountEmail> getEmails(UUID accountId) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void saveEmails(UUID accountId, List<AccountEmail> emails, CallContext context) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
     public void updateAccount(final Account account, final CallContext context) {
         throw new UnsupportedOperationException();
     }
 
 	@Override
 	public Account migrateAccount(final MigrationAccountData data,
-			final List<CustomField> fields, final List<Tag> tags, final CallContext context)
+			final List<CustomField> fields, final List<TagDefinition> tagDefinitions, final CallContext context)
 			throws AccountApiException {
 		Account result = new DefaultAccount(data);
         accounts.add(result);
diff --git a/account/src/test/java/com/ning/billing/account/api/user/TestEventJson.java b/account/src/test/java/com/ning/billing/account/api/user/TestEventJson.java
index d6f9f96..6518355 100644
--- a/account/src/test/java/com/ning/billing/account/api/user/TestEventJson.java
+++ b/account/src/test/java/com/ning/billing/account/api/user/TestEventJson.java
@@ -59,7 +59,7 @@ public class TestEventJson {
     public void testAccountCreationEvent() throws Exception {
         
         DefaultAccountData data = new DefaultAccountData("dsfdsf", "bobo", 3, "bobo@yahoo.com", 12, "USD", "paypal", 
-                "UTC", "US", "21 avenue", "", "Gling", "San Franciso", "CA", "94110", "USA", "4126789887");
+                "UTC", "US", "21 avenue", "", "Gling", "San Franciso", "CA", "94110", "USA", "4126789887", false, false);
         DefaultAccountCreationEvent e = new DefaultAccountCreationEvent(data, UUID.randomUUID(), UUID.randomUUID());
         
         String json = mapper.writeValueAsString(e);
diff --git a/account/src/test/java/com/ning/billing/account/dao/AccountDaoTestBase.java b/account/src/test/java/com/ning/billing/account/dao/AccountDaoTestBase.java
index 3a51bcc..66e49bb 100644
--- a/account/src/test/java/com/ning/billing/account/dao/AccountDaoTestBase.java
+++ b/account/src/test/java/com/ning/billing/account/dao/AccountDaoTestBase.java
@@ -67,7 +67,7 @@ public abstract class AccountDaoTestBase {
             accountDao.test();
 
             Clock clock = injector.getInstance(Clock.class);
-            context = new DefaultCallContextFactory(clock).createCallContext("Vizzini", CallOrigin.TEST, UserType.TEST);
+            context = new DefaultCallContextFactory(clock).createCallContext("Account Dao Tests", CallOrigin.TEST, UserType.TEST);
 
 
             BusService busService = injector.getInstance(BusService.class);
diff --git a/account/src/test/java/com/ning/billing/account/dao/MockAccountDao.java b/account/src/test/java/com/ning/billing/account/dao/MockAccountDao.java
index 38e2a6e..6c8b169 100644
--- a/account/src/test/java/com/ning/billing/account/dao/MockAccountDao.java
+++ b/account/src/test/java/com/ning/billing/account/dao/MockAccountDao.java
@@ -24,7 +24,7 @@ import java.util.concurrent.ConcurrentHashMap;
 
 import com.google.inject.Inject;
 import com.ning.billing.account.api.Account;
-import com.ning.billing.account.api.AccountApiException;
+import com.ning.billing.account.api.AccountEmail;
 import com.ning.billing.account.api.AccountChangeEvent;
 import com.ning.billing.account.api.user.DefaultAccountChangeEvent;
 import com.ning.billing.account.api.user.DefaultAccountCreationEvent;
@@ -84,6 +84,16 @@ public class MockAccountDao implements AccountDao {
     }
 
     @Override
+    public List<AccountEmail> getEmails(UUID accountId) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void saveEmails(UUID accountId, List<AccountEmail> emails, CallContext context) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
     public void update(Account account, CallContext context) {
         Account currentAccount = accounts.put(account.getId().toString(), account);
 
diff --git a/analytics/src/test/java/com/ning/billing/analytics/AnalyticsTestModule.java b/analytics/src/test/java/com/ning/billing/analytics/AnalyticsTestModule.java
index ba48be8..ef7d265 100644
--- a/analytics/src/test/java/com/ning/billing/analytics/AnalyticsTestModule.java
+++ b/analytics/src/test/java/com/ning/billing/analytics/AnalyticsTestModule.java
@@ -16,6 +16,8 @@
 
 package com.ning.billing.analytics;
 
+import com.ning.billing.util.email.EmailModule;
+import com.ning.billing.util.glue.GlobalLockerModule;
 import org.skife.jdbi.v2.IDBI;
 
 import com.ning.billing.account.glue.AccountModule;
@@ -42,6 +44,8 @@ public class AnalyticsTestModule extends AnalyticsModule
         super.configure();
 
         // Need to configure a few more things for the EventBus
+        install(new EmailModule());
+        install(new GlobalLockerModule());
         install(new ClockModule());
         install(new CallContextModule());
         install(new FieldStoreModule());
diff --git a/analytics/src/test/java/com/ning/billing/analytics/api/TestAnalyticsService.java b/analytics/src/test/java/com/ning/billing/analytics/api/TestAnalyticsService.java
index 3591582..a7091f8 100644
--- a/analytics/src/test/java/com/ning/billing/analytics/api/TestAnalyticsService.java
+++ b/analytics/src/test/java/com/ning/billing/analytics/api/TestAnalyticsService.java
@@ -26,6 +26,7 @@ import java.util.Arrays;
 import java.util.List;
 import java.util.UUID;
 
+import com.ning.billing.util.tag.TagDefinition;
 import org.apache.commons.io.IOUtils;
 import org.joda.time.DateTime;
 import org.testng.Assert;
@@ -92,8 +93,6 @@ import com.ning.billing.util.callcontext.UserType;
 import com.ning.billing.util.clock.Clock;
 import com.ning.billing.util.clock.DefaultClock;
 import com.ning.billing.util.tag.DefaultTagDefinition;
-import com.ning.billing.util.tag.DescriptiveTag;
-import com.ning.billing.util.tag.Tag;
 import com.ning.billing.util.tag.dao.TagDefinitionSqlDao;
 
 @Guice(modules = {AnalyticsTestModule.class, MockCatalogModule.class})
@@ -182,11 +181,11 @@ public class TestAnalyticsService {
 
         final MockAccount account = new MockAccount(UUID.randomUUID(), ACCOUNT_KEY, ACCOUNT_CURRENCY);
         try {
-            final List<Tag> tags = new ArrayList<Tag>();
-            tags.add(new DescriptiveTag(TAG_ONE));
-            tags.add(new DescriptiveTag(TAG_TWO));
+            final List<TagDefinition> tagDefinitions = new ArrayList<TagDefinition>();
+            tagDefinitions.add(TAG_ONE);
+            tagDefinitions.add(TAG_TWO);
 
-            final Account storedAccount = accountApi.createAccount(account, null, tags, context);
+            final Account storedAccount = accountApi.createAccount(account, null, tagDefinitions, context);
 
             // Create events for the bus and expected results
             createSubscriptionTransitionEvent(storedAccount);
diff --git a/analytics/src/test/java/com/ning/billing/analytics/MockAccount.java b/analytics/src/test/java/com/ning/billing/analytics/MockAccount.java
index 0efe360..02dbb08 100644
--- a/analytics/src/test/java/com/ning/billing/analytics/MockAccount.java
+++ b/analytics/src/test/java/com/ning/billing/analytics/MockAccount.java
@@ -19,6 +19,7 @@ package com.ning.billing.analytics;
 import java.util.List;
 import java.util.UUID;
 
+import com.ning.billing.util.tag.ControlTagType;
 import org.joda.time.DateTime;
 import org.joda.time.DateTimeZone;
 
@@ -62,6 +63,16 @@ public class MockAccount implements Account
     }
 
     @Override
+    public boolean isMigrated() {
+        return false;
+    }
+
+    @Override
+    public boolean isNotifiedForInvoices() {
+        return false;
+    }
+
+    @Override
     public String getExternalKey()
     {
         return accountKey;
@@ -201,7 +212,12 @@ public class MockAccount implements Account
     }
 
     @Override
-    public boolean hasTag(String tagName) {
+    public boolean hasTag(TagDefinition tagDefinition) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public boolean hasTag(ControlTagType controlTagType) {
         throw new UnsupportedOperationException();
     }
 
@@ -216,6 +232,11 @@ public class MockAccount implements Account
     }
 
     @Override
+    public void addTagsFromDefinitions(List<TagDefinition> tagDefinitions) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
     public void clearTags() {
         throw new UnsupportedOperationException();
     }
diff --git a/analytics/src/test/java/com/ning/billing/analytics/MockDuration.java b/analytics/src/test/java/com/ning/billing/analytics/MockDuration.java
index f10ff2f..e2fa1a2 100644
--- a/analytics/src/test/java/com/ning/billing/analytics/MockDuration.java
+++ b/analytics/src/test/java/com/ning/billing/analytics/MockDuration.java
@@ -20,6 +20,7 @@ import com.ning.billing.catalog.api.Duration;
 import com.ning.billing.catalog.api.TimeUnit;
 import org.apache.commons.lang.NotImplementedException;
 import org.joda.time.DateTime;
+import org.joda.time.Period;
 
 public class MockDuration
 {
@@ -43,6 +44,10 @@ public class MockDuration
             public DateTime addToDateTime(DateTime dateTime) {
                 throw new NotImplementedException();
             }
+            @Override
+            public Period toJodaPeriod() {
+                throw new UnsupportedOperationException();
+            }
         };
     }
 
@@ -66,6 +71,10 @@ public class MockDuration
             public DateTime addToDateTime(DateTime dateTime) {
                 throw new NotImplementedException();
             }
+            @Override
+            public Period toJodaPeriod() {
+                throw new UnsupportedOperationException();
+            }
         };
     }
 
@@ -89,6 +98,10 @@ public class MockDuration
             public DateTime addToDateTime(DateTime dateTime) {
                 throw new NotImplementedException();
             }
+            @Override
+            public Period toJodaPeriod() {
+                throw new UnsupportedOperationException();
+            }
         };
     }
 }
diff --git a/analytics/src/test/java/com/ning/billing/analytics/MockSubscription.java b/analytics/src/test/java/com/ning/billing/analytics/MockSubscription.java
index 8632938..cb786a0 100644
--- a/analytics/src/test/java/com/ning/billing/analytics/MockSubscription.java
+++ b/analytics/src/test/java/com/ning/billing/analytics/MockSubscription.java
@@ -19,6 +19,7 @@ package com.ning.billing.analytics;
 import java.util.List;
 import java.util.UUID;
 
+import com.ning.billing.util.tag.ControlTagType;
 import org.joda.time.DateTime;
 import org.joda.time.DateTimeZone;
 
@@ -37,7 +38,6 @@ import com.ning.billing.util.customfield.CustomField;
 import com.ning.billing.util.tag.Tag;
 import com.ning.billing.util.tag.TagDefinition;
 
-
 public class MockSubscription implements Subscription
 {
     private static final UUID ID = UUID.randomUUID();
@@ -213,7 +213,12 @@ public class MockSubscription implements Subscription
     }
 
     @Override
-    public boolean hasTag(String tagName) {
+    public boolean hasTag(TagDefinition tagDefinition) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public boolean hasTag(ControlTagType controlTagType) {
         throw new UnsupportedOperationException();
     }
 
@@ -228,6 +233,11 @@ public class MockSubscription implements Subscription
     }
 
     @Override
+    public void addTagsFromDefinitions(List<TagDefinition> tagDefinitions) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
     public void clearTags() {
         throw new UnsupportedOperationException();
     }
diff --git a/analytics/src/test/java/com/ning/billing/analytics/TestAnalyticsListener.java b/analytics/src/test/java/com/ning/billing/analytics/TestAnalyticsListener.java
index e51711a..72aa146 100644
--- a/analytics/src/test/java/com/ning/billing/analytics/TestAnalyticsListener.java
+++ b/analytics/src/test/java/com/ning/billing/analytics/TestAnalyticsListener.java
@@ -74,7 +74,7 @@ public class TestAnalyticsListener
     @BeforeMethod(alwaysRun = true)
     public void setUp() throws Exception
     {
-        final BusinessSubscriptionTransitionRecorder recorder = new BusinessSubscriptionTransitionRecorder(dao, catalogService, new MockEntitlementUserApi(bundleUUID, KEY), new MockIAccountUserApi(ACCOUNT_KEY, CURRENCY));
+        final BusinessSubscriptionTransitionRecorder recorder = new BusinessSubscriptionTransitionRecorder(dao, catalogService, new MockEntitlementUserApi(bundleUUID, KEY), new MockAccountUserApi(ACCOUNT_KEY, CURRENCY));
         listener = new AnalyticsListener(recorder, null);
     }
 
diff --git a/api/src/main/java/com/ning/billing/account/api/AccountData.java b/api/src/main/java/com/ning/billing/account/api/AccountData.java
index fa78b52..3220971 100644
--- a/api/src/main/java/com/ning/billing/account/api/AccountData.java
+++ b/api/src/main/java/com/ning/billing/account/api/AccountData.java
@@ -19,7 +19,6 @@ package com.ning.billing.account.api;
 import org.joda.time.DateTimeZone;
 
 import com.ning.billing.catalog.api.Currency;
-import com.ning.billing.junction.api.BlockingState;
 
 public interface AccountData {
 
@@ -57,4 +56,7 @@ public interface AccountData {
 
     public String getPhone();
 
+    public boolean isMigrated();
+
+    public boolean isNotifiedForInvoices();
 }
diff --git a/api/src/main/java/com/ning/billing/account/api/AccountEmail.java b/api/src/main/java/com/ning/billing/account/api/AccountEmail.java
new file mode 100644
index 0000000..88ec469
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/account/api/AccountEmail.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you 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.ning.billing.account.api;
+
+import com.ning.billing.util.entity.UpdatableEntity;
+
+import java.util.UUID;
+
+public interface AccountEmail extends UpdatableEntity {
+    UUID getAccountId();
+    String getEmail();
+}
diff --git a/api/src/main/java/com/ning/billing/account/api/AccountUserApi.java b/api/src/main/java/com/ning/billing/account/api/AccountUserApi.java
index eabee0f..89e5f70 100644
--- a/api/src/main/java/com/ning/billing/account/api/AccountUserApi.java
+++ b/api/src/main/java/com/ning/billing/account/api/AccountUserApi.java
@@ -21,22 +21,23 @@ import java.util.UUID;
 
 import com.ning.billing.util.callcontext.CallContext;
 import com.ning.billing.util.customfield.CustomField;
-import com.ning.billing.util.tag.Tag;
+import com.ning.billing.util.tag.TagDefinition;
 
 import javax.annotation.Nullable;
 
 public interface AccountUserApi {
     public Account createAccount(AccountData data, @Nullable List<CustomField> fields,
-                                 @Nullable List<Tag> tags, CallContext context) throws AccountApiException;
+                                 @Nullable List<TagDefinition> tagDefinitions, CallContext context) throws AccountApiException;
 
     public Account migrateAccount(MigrationAccountData data, @Nullable List<CustomField> fields,
-                                  @Nullable List<Tag> tags, CallContext context) throws AccountApiException;
+                                  @Nullable List<TagDefinition> tagDefinitions, CallContext context) throws AccountApiException;
 
     /***
      *
      * Note: does not update the external key
-     * @param account
-     * @throws AccountApiException
+     * @param account account to be updated
+     * @param context contains specific information about the call
+     * @throws AccountApiException if a failure occurs
      */
     public void updateAccount(Account account, CallContext context) throws AccountApiException;
 
@@ -51,4 +52,8 @@ public interface AccountUserApi {
     public List<Account> getAccounts();
 
     public UUID getIdFromKey(String externalKey) throws AccountApiException;
+
+    public List<AccountEmail> getEmails(UUID accountId);
+
+    public void saveEmails(UUID accountId, List<AccountEmail> emails, CallContext context);
 }
diff --git a/api/src/main/java/com/ning/billing/account/api/MutableAccountData.java b/api/src/main/java/com/ning/billing/account/api/MutableAccountData.java
index 78dad92..6ea638d 100644
--- a/api/src/main/java/com/ning/billing/account/api/MutableAccountData.java
+++ b/api/src/main/java/com/ning/billing/account/api/MutableAccountData.java
@@ -21,41 +21,6 @@ import org.joda.time.DateTimeZone;
 import com.ning.billing.catalog.api.Currency;
 
 public interface MutableAccountData extends AccountData {
-
-    public String getExternalKey();
-
-    public String getEmail();
-
-    public String getName();
-
-    public int getFirstNameLength();
-
-    public Currency getCurrency();
-
-    public int getBillCycleDay();
-
-    public String getPaymentProviderName();
-
-    public DateTimeZone getTimeZone();
-
-    public String getLocale();
-
-    public String getAddress1();
-
-    public String getAddress2();
-
-    public String getCompanyName();
-
-    public String getCity();
-
-    public String getStateOrProvince();
-
-    public String getCountry();
-
-    public String getPostalCode();
-
-    public String getPhone();
-
     public void setExternalKey(String externalKey);
 
     public void setEmail(String email);
@@ -90,4 +55,8 @@ public interface MutableAccountData extends AccountData {
 
     public void setPhone(String phone);
 
+    public void setIsMigrated(boolean isMigrated);
+
+    public void setIsNotifiedForInvoices(boolean isNotifiedForInvoices);
+
 }
\ No newline at end of file
diff --git a/api/src/main/java/com/ning/billing/catalog/api/Duration.java b/api/src/main/java/com/ning/billing/catalog/api/Duration.java
index 9025367..90ea5c2 100644
--- a/api/src/main/java/com/ning/billing/catalog/api/Duration.java
+++ b/api/src/main/java/com/ning/billing/catalog/api/Duration.java
@@ -17,6 +17,7 @@
 package com.ning.billing.catalog.api;
 
 import org.joda.time.DateTime;
+import org.joda.time.Period;
 
 public interface Duration {
 
@@ -25,4 +26,6 @@ public interface Duration {
 	public abstract int getNumber();
 
     public DateTime addToDateTime(DateTime dateTime);
+
+    public Period toJodaPeriod();
 }
\ No newline at end of file
diff --git a/api/src/main/java/com/ning/billing/entitlement/api/billing/BillingEvent.java b/api/src/main/java/com/ning/billing/entitlement/api/billing/BillingEvent.java
index 8681d59..eb8410f 100644
--- a/api/src/main/java/com/ning/billing/entitlement/api/billing/BillingEvent.java
+++ b/api/src/main/java/com/ning/billing/entitlement/api/billing/BillingEvent.java
@@ -113,5 +113,4 @@ public interface BillingEvent extends Comparable<BillingEvent> {
 	 * @return a unique long indicating the ordering on which events got inserted on disk-- used for sorting only
 	 */
 	public Long getTotalOrdering();
-
  }
diff --git a/api/src/main/java/com/ning/billing/ErrorCode.java b/api/src/main/java/com/ning/billing/ErrorCode.java
index dd22ea6..f6856eb 100644
--- a/api/src/main/java/com/ning/billing/ErrorCode.java
+++ b/api/src/main/java/com/ning/billing/ErrorCode.java
@@ -194,10 +194,17 @@ public enum ErrorCode {
      * 
      */
     BLOCK_BLOCKED_ACTION(6000, "The action %s is block on this %s with id=%s"),
-    BLOCK_TYPE_NOT_SUPPORTED(6001, "The Blockable type '%s' is not supported") 
+    BLOCK_TYPE_NOT_SUPPORTED(6001, "The Blockable type '%s' is not supported"),
     
-    
-    ;
+   /*
+    *
+    * Range 9000: Miscellaneous
+    *
+    */
+    EMAIL_SENDING_FAILED(9000, "Sending email failed"),
+    EMAIL_PROPERTIES_FILE_MISSING(9001, "The properties file for email configuration could not be found."),
+    MISSING_TRANSLATION_RESOURCE(9010, "The resources for %s translation could not be found."),
+    MISSING_DEFAULT_TRANSLATION_RESOURCE(9011, "The default resource for %s translation could not be found.");
 
     
     private int code;
diff --git a/api/src/main/java/com/ning/billing/invoice/api/formatters/InvoiceFormatterFactory.java b/api/src/main/java/com/ning/billing/invoice/api/formatters/InvoiceFormatterFactory.java
new file mode 100644
index 0000000..af4af92
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/invoice/api/formatters/InvoiceFormatterFactory.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you 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.ning.billing.invoice.api.formatters;
+
+import com.ning.billing.invoice.api.Invoice;
+import com.ning.billing.util.template.translation.TranslatorConfig;
+
+import java.util.Locale;
+
+public interface InvoiceFormatterFactory {
+    public InvoiceFormatter createInvoiceFormatter(TranslatorConfig config, Invoice invoice, Locale locale);
+}
diff --git a/api/src/main/java/com/ning/billing/invoice/api/formatters/InvoiceItemFormatter.java b/api/src/main/java/com/ning/billing/invoice/api/formatters/InvoiceItemFormatter.java
new file mode 100644
index 0000000..c14e621
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/invoice/api/formatters/InvoiceItemFormatter.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you 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.ning.billing.invoice.api.formatters;
+
+import com.ning.billing.invoice.api.InvoiceItem;
+
+public interface InvoiceItemFormatter extends InvoiceItem {
+    public String getFormattedStartDate();
+    public String getFormattedEndDate();
+}
diff --git a/api/src/main/java/com/ning/billing/invoice/api/InvoiceItem.java b/api/src/main/java/com/ning/billing/invoice/api/InvoiceItem.java
index b7ab8b1..cd7decd 100644
--- a/api/src/main/java/com/ning/billing/invoice/api/InvoiceItem.java
+++ b/api/src/main/java/com/ning/billing/invoice/api/InvoiceItem.java
@@ -46,5 +46,5 @@ public interface InvoiceItem extends Entity, Comparable<InvoiceItem> {
 
     Currency getCurrency();
 
-    InvoiceItem asCredit();
+    InvoiceItem asReversingItem();
 }
diff --git a/api/src/main/java/com/ning/billing/util/api/TagUserApi.java b/api/src/main/java/com/ning/billing/util/api/TagUserApi.java
index d5cb8f2..5d05bb0 100644
--- a/api/src/main/java/com/ning/billing/util/api/TagUserApi.java
+++ b/api/src/main/java/com/ning/billing/util/api/TagUserApi.java
@@ -57,7 +57,6 @@ public interface TagUserApi {
      */
     public void deleteTagDefinition(String definitionName, CallContext context) throws TagDefinitionApiException;
 
-    
 	/**
 	 * 
 	 * @param name
diff --git a/api/src/main/java/com/ning/billing/util/email/EmailApiException.java b/api/src/main/java/com/ning/billing/util/email/EmailApiException.java
new file mode 100644
index 0000000..9b5d6ba
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/util/email/EmailApiException.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you 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.ning.billing.util.email;
+
+import com.ning.billing.BillingExceptionBase;
+import com.ning.billing.ErrorCode;
+
+public class EmailApiException extends BillingExceptionBase {
+    private static final long serialVersionUID = 1L;
+
+    public EmailApiException(Throwable cause, int code, final String msg) {
+        super(cause, code, msg);
+    }
+
+    public EmailApiException(Throwable cause, ErrorCode code, final Object... args) {
+        super(cause, code, args);
+    }
+
+    public EmailApiException(ErrorCode code, final Object... args) {
+        super(code, args);
+    }
+}
diff --git a/api/src/main/java/com/ning/billing/util/email/EmailSender.java b/api/src/main/java/com/ning/billing/util/email/EmailSender.java
new file mode 100644
index 0000000..198a2eb
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/util/email/EmailSender.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you 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.ning.billing.util.email;
+
+import java.io.IOException;
+import java.util.List;
+
+public interface EmailSender {
+    public void sendSecureEmail(List<String> to, List<String> cc, String subject, String htmlBody) throws IOException, EmailApiException;
+}
diff --git a/api/src/main/java/com/ning/billing/util/tag/Taggable.java b/api/src/main/java/com/ning/billing/util/tag/Taggable.java
index 3b2e8b0..37d15bf 100644
--- a/api/src/main/java/com/ning/billing/util/tag/Taggable.java
+++ b/api/src/main/java/com/ning/billing/util/tag/Taggable.java
@@ -21,11 +21,16 @@ import org.joda.time.DateTime;
 
 public interface Taggable {
     public List<Tag> getTagList();
-    public boolean hasTag(String tagName);
+
+    public boolean hasTag(TagDefinition tagDefinition);
+    public boolean hasTag(ControlTagType controlTagType);
+
     public void addTag(TagDefinition definition);
     public void addTags(List<Tag> tags);
+    public void addTagsFromDefinitions(List<TagDefinition> tagDefinitions);
     public void clearTags();
     public void removeTag(TagDefinition definition);
+
     public boolean generateInvoice();
     public boolean processPayment();
 }
diff --git a/api/src/main/java/com/ning/billing/util/tag/TagStore.java b/api/src/main/java/com/ning/billing/util/tag/TagStore.java
index 705e203..22749b9 100644
--- a/api/src/main/java/com/ning/billing/util/tag/TagStore.java
+++ b/api/src/main/java/com/ning/billing/util/tag/TagStore.java
@@ -23,7 +23,9 @@ public interface TagStore extends EntityCollection<Tag> {
 
     public boolean generateInvoice();
 
-    public void remove(String tagName);
+    public boolean containsTagForDefinition(TagDefinition definition);
 
-    public boolean containsTag(String tagName);
+    public boolean containsTagForControlTagType(ControlTagType controlTagType);
+
+    public Tag remove(TagDefinition tagDefinition);
 }
diff --git a/api/src/main/java/com/ning/billing/util/template/translation/Translator.java b/api/src/main/java/com/ning/billing/util/template/translation/Translator.java
new file mode 100644
index 0000000..f50f4f6
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/util/template/translation/Translator.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you 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.ning.billing.util.template.translation;
+
+import java.util.Locale;
+
+public interface Translator {
+    public String getTranslation(Locale locale, String originalText);
+}
diff --git a/api/src/main/java/com/ning/billing/util/template/translation/TranslatorConfig.java b/api/src/main/java/com/ning/billing/util/template/translation/TranslatorConfig.java
new file mode 100644
index 0000000..506c8bc
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/util/template/translation/TranslatorConfig.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you 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.ning.billing.util.template.translation;
+
+import org.skife.config.Config;
+import org.skife.config.Default;
+
+public interface TranslatorConfig {
+    @Config("email.default.locale")
+    @Default("en_US")
+    public String getDefaultLocale();
+}
diff --git a/beatrix/src/main/java/com/ning/billing/beatrix/lifecycle/ServiceFinder.java b/beatrix/src/main/java/com/ning/billing/beatrix/lifecycle/ServiceFinder.java
index 83115de..e27dff9 100644
--- a/beatrix/src/main/java/com/ning/billing/beatrix/lifecycle/ServiceFinder.java
+++ b/beatrix/src/main/java/com/ning/billing/beatrix/lifecycle/ServiceFinder.java
@@ -60,7 +60,7 @@ public class ServiceFinder {
 		    final Set<String> packageFilter = new HashSet<String>();
 		    packageFilter.add("com.ning.billing");
 		    final String jarFilter = "killbill";
-			return findClasses(loader, KillbillService.class.getName().toString(), jarFilter, packageFilter);
+			return findClasses(loader, KillbillService.class.getName(), jarFilter, packageFilter);
 		} catch (ClassNotFoundException nfe) {
 			throw new RuntimeException("Failed to initialize ClassFinder", nfe);
 		}
diff --git a/beatrix/src/test/java/com/ning/billing/beatrix/integration/MockModule.java b/beatrix/src/test/java/com/ning/billing/beatrix/integration/MockModule.java
index 60434e1..4bae656 100644
--- a/beatrix/src/test/java/com/ning/billing/beatrix/integration/MockModule.java
+++ b/beatrix/src/test/java/com/ning/billing/beatrix/integration/MockModule.java
@@ -22,6 +22,8 @@ import java.io.IOException;
 import java.net.URL;
 import java.util.Set;
 
+import com.ning.billing.util.email.EmailConfig;
+import com.ning.billing.util.email.EmailModule;
 import org.skife.config.ConfigurationObjectFactory;
 import org.skife.jdbi.v2.IDBI;
 
@@ -83,6 +85,7 @@ public class MockModule extends AbstractModule {
             bind(IDBI.class).toInstance(dbi);
         }
 
+        install(new EmailModule());
         install(new CallContextModule());
         install(new GlobalLockerModule());
         install(new BusModule());
diff --git a/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestIntegration.java b/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestIntegration.java
index c6d37bb..3e8dfb4 100644
--- a/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestIntegration.java
+++ b/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestIntegration.java
@@ -47,7 +47,6 @@ import com.ning.billing.invoice.api.Invoice;
 @Test(groups = "slow")
 @Guice(modules = {MockModule.class})
 public class TestIntegration extends TestIntegrationBase {
- 
     @Test(groups = "slow", enabled = true)
     public void testBasePlanCompleteWithBillingDayInPast() throws Exception {
         DateTime startDate = new DateTime(2012, 2, 1, 0, 3, 42, 0);
@@ -96,8 +95,8 @@ public class TestIntegration extends TestIntegrationBase {
         }
     }
 
-   @Test(groups = "slow", enabled = true) 
-   public void testRepairChangeBPWithAddonIncluded() throws Exception {
+    @Test(groups = "slow", enabled = true)
+    public void testRepairChangeBPWithAddonIncluded() throws Exception {
         
         DateTime initialDate = new DateTime(2012, 4, 25, 0, 3, 42, 0);
         clock.setDeltaFromReality(initialDate.getMillis() - clock.getUTCNow().getMillis());
@@ -147,10 +146,40 @@ public class TestIntegration extends TestIntegrationBase {
         busHandler.pushExpectedEvent(NextEvent.PAYMENT);
         clock.addDeltaFromReality(it.toDurationMillis());
         assertTrue(busHandler.isCompleted(DELAY));
-   }
-   
+    }
    
-    @Test(groups = "slow", enabled = true)
+    @Test(groups = {"slow"})
+    public void testRepairForInvoicing() throws AccountApiException, EntitlementUserApiException {
+        log.info("Starting testRepairForInvoicing");
+
+        Account account = accountUserApi.createAccount(getAccountData(1), null, null, context);
+        UUID accountId = account.getId();
+        assertNotNull(account);
+
+        DateTime initialDate = new DateTime(2012, 4, 25, 0, 3, 42, 0);
+        clock.setDeltaFromReality(initialDate.getMillis() - clock.getUTCNow().getMillis());
+
+        SubscriptionBundle bundle = entitlementUserApi.createBundleForAccount(account.getId(), "someBundle", context);
+        assertNotNull(bundle);
+
+        String productName = "Shotgun";
+        BillingPeriod term = BillingPeriod.MONTHLY;
+        String planSetName = PriceListSet.DEFAULT_PRICELIST_NAME;
+        entitlementUserApi.createSubscription(bundle.getId(),
+                new PlanPhaseSpecifier(productName, ProductCategory.BASE, term, planSetName, null), null, context);
+
+        busHandler.reset();
+        busHandler.pushExpectedEvent(NextEvent.CREATE);
+        busHandler.pushExpectedEvent(NextEvent.INVOICE);
+        assertTrue(busHandler.isCompleted(DELAY));
+
+        List<Invoice> invoices = invoiceUserApi.getInvoicesByAccount(accountId);
+        assertEquals(invoices.size(), 1);
+
+        // TODO: Jeff implement repair
+    }
+
+    @Test(groups = "slow", enabled = false)
     public void testWithRecreatePlan() throws Exception {
 
         DateTime initialDate = new DateTime(2012, 2, 1, 0, 3, 42, 0);
diff --git a/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestIntegrationBase.java b/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestIntegrationBase.java
index 941df3c..a70bce9 100644
--- a/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestIntegrationBase.java
+++ b/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestIntegrationBase.java
@@ -292,6 +292,17 @@ public class TestIntegrationBase implements TestFailure {
             public String getPhone() {
                 return "4152876341";
             }
+
+            @Override
+            public boolean isMigrated() {
+                return false;
+            }
+
+            @Override
+            public boolean isNotifiedForInvoices() {
+                return false;
+            }
+
             @Override
             public String getExternalKey() {
                 return someRandomKey;
diff --git a/catalog/src/main/java/com/ning/billing/catalog/DefaultDuration.java b/catalog/src/main/java/com/ning/billing/catalog/DefaultDuration.java
index 0e821ef..71bfe50 100644
--- a/catalog/src/main/java/com/ning/billing/catalog/DefaultDuration.java
+++ b/catalog/src/main/java/com/ning/billing/catalog/DefaultDuration.java
@@ -21,6 +21,7 @@ import com.ning.billing.catalog.api.TimeUnit;
 import com.ning.billing.util.config.ValidatingConfig;
 import com.ning.billing.util.config.ValidationErrors;
 import org.joda.time.DateTime;
+import org.joda.time.Period;
 
 import javax.xml.bind.annotation.XmlAccessType;
 import javax.xml.bind.annotation.XmlAccessorType;
@@ -52,7 +53,7 @@ public class DefaultDuration extends ValidatingConfig<StandaloneCatalog> impleme
 
     @Override
     public DateTime addToDateTime(DateTime dateTime) {
-        if (number == null) {return dateTime;}
+        if ((number == null) && (unit != TimeUnit.UNLIMITED)) {return dateTime;}
 
         switch (unit) {
             case DAYS:
@@ -84,5 +85,21 @@ public class DefaultDuration extends ValidatingConfig<StandaloneCatalog> impleme
 		return this;
 	}
 	
-	
+    @Override
+    public Period toJodaPeriod() {
+        if ((number == null) && (unit != TimeUnit.UNLIMITED)) {return new Period();}
+
+        switch (unit) {
+            case DAYS:
+                return new Period().withDays(number);
+            case MONTHS:
+                return new Period().withMonths(number);
+            case YEARS:
+                return new Period().withYears(number);
+            case UNLIMITED:
+                return new Period().withYears(100);
+            default:
+                return new Period();
+        }
+    }
 }
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionData.java b/entitlement/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionData.java
index 8943038..703e0bc 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionData.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionData.java
@@ -24,6 +24,7 @@ import java.util.UUID;
 
 import javax.annotation.Nullable;
 
+import com.ning.billing.util.tag.ControlTagType;
 import org.joda.time.DateTime;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -55,8 +56,7 @@ import com.ning.billing.util.clock.Clock;
 import com.ning.billing.util.customfield.CustomField;
 import com.ning.billing.util.entity.ExtendedEntityBase;
 
-public class SubscriptionData extends ExtendedEntityBase implements
-        Subscription {
+public class SubscriptionData extends ExtendedEntityBase implements Subscription {
 
     private final static Logger log = LoggerFactory.getLogger(SubscriptionData.class);
 
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/TestApiBase.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/TestApiBase.java
index e4c011a..e6d7705 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/api/TestApiBase.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/TestApiBase.java
@@ -31,6 +31,7 @@ import javax.annotation.Nullable;
 import org.apache.commons.io.IOUtils;
 import org.joda.time.DateTime;
 import org.joda.time.DateTimeZone;
+import org.joda.time.Period;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.testng.annotations.AfterClass;
@@ -159,7 +160,7 @@ public abstract class TestApiBase {
         return (! (theDao instanceof MockEntitlementDaoMemory));
     }
 
-   private void setupMySQL() throws IOException {
+    private void setupMySQL() throws IOException {
         if (helper != null) {
             final String entitlementDdl = IOUtils.toString(TestApiBase.class.getResourceAsStream("/com/ning/billing/entitlement/ddl.sql"));
             final String utilDdl = IOUtils.toString(TestApiBase.class.getResourceAsStream("/com/ning/billing/util/ddl.sql"));
@@ -254,7 +255,6 @@ public abstract class TestApiBase {
         }
     }
 
-
     protected void assertDateWithin(DateTime in, DateTime lower, DateTime upper) {
         assertTrue(in.isEqual(lower) || in.isAfter(lower));
         assertTrue(in.isEqual(upper) || in.isBefore(upper));
@@ -275,6 +275,10 @@ public abstract class TestApiBase {
             public DateTime addToDateTime(DateTime dateTime) {
                 return null;
             }
+            @Override
+            public Period toJodaPeriod() {
+                throw new UnsupportedOperationException();
+            }
         };
         return result;
     }
@@ -294,6 +298,10 @@ public abstract class TestApiBase {
             public DateTime addToDateTime(DateTime dateTime) {
                 return null;  //To change body of implemented methods use File | Settings | File Templates.
             }
+            @Override
+            public Period toJodaPeriod() {
+                throw new UnsupportedOperationException();
+            }
         };
         return result;
     }
@@ -314,6 +322,10 @@ public abstract class TestApiBase {
             public DateTime addToDateTime(DateTime dateTime) {
                 return null;  //To change body of implemented methods use File | Settings | File Templates.
             }
+            @Override
+            public Period toJodaPeriod() {
+                throw new UnsupportedOperationException();
+            }
         };
         return result;
     }
@@ -341,6 +353,16 @@ public abstract class TestApiBase {
             }
 
             @Override
+            public boolean isMigrated() {
+                return false;
+            }
+
+            @Override
+            public boolean isNotifiedForInvoices() {
+                return false;
+            }
+
+            @Override
             public String getExternalKey() {
                 return "k123456";
             }
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiAddOn.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiAddOn.java
index df3d3f0..2780fe5 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiAddOn.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiAddOn.java
@@ -23,6 +23,7 @@ import static org.testng.Assert.assertTrue;
 
 import org.apache.commons.lang.NotImplementedException;
 import org.joda.time.DateTime;
+import org.joda.time.Period;
 import org.testng.Assert;
 import org.testng.annotations.Test;
 
@@ -84,9 +85,8 @@ public class TestUserApiAddOn extends TestApiBase {
         }
     }
 
-    //TODO Martin re-enable this after merge
-    @Test(enabled=false, groups={"slow"})
-    public void testCancelBPWthAddon() {
+    @Test(enabled=true, groups={"slow"})
+    public void testCancelBPWithAddon() {
         try {
 
             String baseProduct = "Shotgun";
@@ -114,6 +114,7 @@ public class TestUserApiAddOn extends TestApiBase {
             // SET CTD TO CANCEL IN FUTURE
             DateTime now = clock.getUTCNow();
             Duration ctd = getDurationMonth(1);
+            // Why not just use clock.getUTCNow().plusMonths(1) ?
             DateTime newChargedThroughDate = DefaultClock.addDuration(now, ctd);
             billingApi.setChargedThroughDate(baseSubscription.getId(), newChargedThroughDate, context);
             baseSubscription = (SubscriptionData) entitlementApi.getSubscriptionFromId(baseSubscription.getId());
@@ -126,7 +127,6 @@ public class TestUserApiAddOn extends TestApiBase {
             assertEquals(aoSubscription.getState(), SubscriptionState.ACTIVE);
             assertTrue(aoSubscription.isSubscriptionFutureCancelled());
 
-
             // MOVE AFTER CANCELLATION
             testListener.reset();
             testListener.pushNextApiExpectedEvent(NextEvent.CANCEL);
@@ -145,7 +145,7 @@ public class TestUserApiAddOn extends TestApiBase {
 
 
     @Test(enabled=true, groups={"slow"})
-    public void testChangeBPWthAddonNonIncluded() {
+    public void testChangeBPWithAddonNonIncluded() {
         try {
 
             String baseProduct = "Shotgun";
@@ -197,9 +197,8 @@ public class TestUserApiAddOn extends TestApiBase {
         }
     }
 
-    //TODO Martin re-enable this after merge
-    @Test(enabled=false, groups={"slow"})
-    public void testChangeBPWthAddonNonAvailable() {
+    @Test(enabled=true, groups={"slow"})
+    public void testChangeBPWithAddonNonAvailable() {
         try {
 
             String baseProduct = "Shotgun";
@@ -364,20 +363,24 @@ public class TestUserApiAddOn extends TestApiBase {
            
            //someTimeLater = aoCurrentPhase.getDuration();
            someTimeLater = new Duration() {
-               @Override
-               public TimeUnit getUnit() {
+                @Override
+                public TimeUnit getUnit() {
                    return TimeUnit.DAYS;
-               }
+                }
 
-               @Override
-               public int getNumber() {
+                @Override
+                public int getNumber() {
                    return 32;
-               }
+                }
 
-               @Override
-               public DateTime addToDateTime(DateTime dateTime) {
+                @Override
+                public DateTime addToDateTime(DateTime dateTime) {
                    throw new NotImplementedException();
-               }
+                }
+                @Override
+                public Period toJodaPeriod() {
+                    throw new UnsupportedOperationException();
+                }
            };
            
            clock.addDeltaFromReality(someTimeLater);

invoice/pom.xml 7(+0 -7)

diff --git a/invoice/pom.xml b/invoice/pom.xml
index 552b41d..5f5522a 100644
--- a/invoice/pom.xml
+++ b/invoice/pom.xml
@@ -57,13 +57,6 @@
         </dependency>
         <dependency>
             <groupId>com.ning.billing</groupId>
-            <artifactId>killbill-junction</artifactId>
-            <type>test-jar</type>
-            <scope>test</scope>
-        </dependency>
-        
-        <dependency>
-            <groupId>com.ning.billing</groupId>
             <artifactId>killbill-catalog</artifactId>
             <scope>test</scope>
         </dependency>
diff --git a/invoice/src/main/java/com/ning/billing/invoice/dao/DefaultInvoiceDao.java b/invoice/src/main/java/com/ning/billing/invoice/dao/DefaultInvoiceDao.java
index caecf73..b4e4fd3 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/dao/DefaultInvoiceDao.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/dao/DefaultInvoiceDao.java
@@ -165,13 +165,13 @@ public class DefaultInvoiceDao implements InvoiceDao {
                     RecurringInvoiceItemSqlDao recurringInvoiceItemDao = invoiceDao.become(RecurringInvoiceItemSqlDao.class);
                     recurringInvoiceItemDao.batchCreateFromTransaction(recurringInvoiceItems, context);
 
-                    notifyOfFutureBillingEvents(invoiceSqlDao, recurringInvoiceItems);
+                    notifyOfFutureBillingEvents(invoiceDao, recurringInvoiceItems);
 
                     List<InvoiceItem> fixedPriceInvoiceItems = invoice.getInvoiceItems(FixedPriceInvoiceItem.class);
                     FixedPriceInvoiceItemSqlDao fixedPriceInvoiceItemDao = invoiceDao.become(FixedPriceInvoiceItemSqlDao.class);
                     fixedPriceInvoiceItemDao.batchCreateFromTransaction(fixedPriceInvoiceItems, context);
 
-                    setChargedThroughDates(invoiceSqlDao, fixedPriceInvoiceItems, recurringInvoiceItems, context);
+                    setChargedThroughDates(invoiceDao, fixedPriceInvoiceItems, recurringInvoiceItems, context);
 
                     // STEPH Why do we need that? Are the payments not always null at this point?
                     List<InvoicePayment> invoicePayments = invoice.getPayments();
diff --git a/invoice/src/main/java/com/ning/billing/invoice/glue/DefaultInvoiceModule.java b/invoice/src/main/java/com/ning/billing/invoice/glue/DefaultInvoiceModule.java
index b825d81..128e8fa 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/glue/DefaultInvoiceModule.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/glue/DefaultInvoiceModule.java
@@ -23,9 +23,11 @@ import com.ning.billing.config.InvoiceConfig;
 import com.ning.billing.invoice.InvoiceListener;
 import com.ning.billing.invoice.api.DefaultInvoiceService;
 import com.ning.billing.invoice.api.InvoiceMigrationApi;
+import com.ning.billing.invoice.api.InvoiceNotifier;
 import com.ning.billing.invoice.api.InvoicePaymentApi;
 import com.ning.billing.invoice.api.InvoiceService;
 import com.ning.billing.invoice.api.InvoiceUserApi;
+import com.ning.billing.invoice.api.formatters.InvoiceFormatterFactory;
 import com.ning.billing.invoice.api.invoice.DefaultInvoicePaymentApi;
 import com.ning.billing.invoice.api.migration.DefaultInvoiceMigrationApi;
 import com.ning.billing.invoice.api.user.DefaultInvoiceUserApi;
@@ -37,8 +39,11 @@ import com.ning.billing.invoice.notification.DefaultNextBillingDateNotifier;
 import com.ning.billing.invoice.notification.DefaultNextBillingDatePoster;
 import com.ning.billing.invoice.notification.NextBillingDateNotifier;
 import com.ning.billing.invoice.notification.NextBillingDatePoster;
-import com.ning.billing.util.glue.GlobalLockerModule;
-
+import com.ning.billing.invoice.notification.NullInvoiceNotifier;
+import com.ning.billing.invoice.template.formatters.DefaultInvoiceFormatterFactory;
+import com.ning.billing.util.email.templates.MustacheTemplateEngine;
+import com.ning.billing.util.email.templates.TemplateEngine;
+import com.ning.billing.util.template.translation.TranslatorConfig;
 
 public class DefaultInvoiceModule extends AbstractModule {
     protected void installInvoiceDao() {
@@ -66,28 +71,34 @@ public class DefaultInvoiceModule extends AbstractModule {
     	bind(InvoiceMigrationApi.class).to(DefaultInvoiceMigrationApi.class).asEagerSingleton();
 	}
 
-    protected void installNotifier() {
+    protected void installNotifiers() {
         bind(NextBillingDateNotifier.class).to(DefaultNextBillingDateNotifier.class).asEagerSingleton();
         bind(NextBillingDatePoster.class).to(DefaultNextBillingDatePoster.class).asEagerSingleton();
+        TranslatorConfig config = new ConfigurationObjectFactory(System.getProperties()).build(TranslatorConfig.class);
+        bind(TranslatorConfig.class).toInstance(config);
+        bind(InvoiceFormatterFactory.class).to(DefaultInvoiceFormatterFactory.class).asEagerSingleton();
+        bind(TemplateEngine.class).to(MustacheTemplateEngine.class).asEagerSingleton();
+        bind(InvoiceNotifier.class).to(NullInvoiceNotifier.class).asEagerSingleton();
     }
 
     protected void installInvoiceListener() {
         bind(InvoiceListener.class).asEagerSingleton();
     }
 
+    protected void installInvoiceGenerator() {
+        bind(InvoiceGenerator.class).to(DefaultInvoiceGenerator.class).asEagerSingleton();
+    }
+
     @Override
     protected void configure() {
         installInvoiceService();
-        installNotifier();
-
-        installInvoiceListener();
-        bind(InvoiceGenerator.class).to(DefaultInvoiceGenerator.class).asEagerSingleton();
         installConfig();
+        installNotifiers();
+        installInvoiceListener();
+        installInvoiceGenerator();
         installInvoiceDao();
         installInvoiceUserApi();
         installInvoicePaymentApi();
         installInvoiceMigrationApi();
     }
-
-
 }
diff --git a/invoice/src/main/java/com/ning/billing/invoice/InvoiceDispatcher.java b/invoice/src/main/java/com/ning/billing/invoice/InvoiceDispatcher.java
index 7de8c71..53093a2 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/InvoiceDispatcher.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/InvoiceDispatcher.java
@@ -34,6 +34,7 @@ import com.ning.billing.catalog.api.Currency;
 import com.ning.billing.entitlement.api.billing.BillingEvent;
 import com.ning.billing.entitlement.api.user.SubscriptionEvent;
 import com.ning.billing.invoice.api.Invoice;
+import com.ning.billing.invoice.api.InvoiceNotifier;
 import com.ning.billing.invoice.api.InvoiceApiException;
 import com.ning.billing.invoice.api.InvoiceItem;
 import com.ning.billing.invoice.api.user.DefaultEmptyInvoiceEvent;
@@ -59,6 +60,7 @@ public class InvoiceDispatcher {
     private final BillingApi billingApi;
     private final AccountUserApi accountUserApi;
     private final InvoiceDao invoiceDao;
+    private final InvoiceNotifier invoiceNotifier;
     private final GlobalLocker locker;
     private final Bus eventBus;
     private final Clock clock;
@@ -69,6 +71,7 @@ public class InvoiceDispatcher {
     public InvoiceDispatcher(final InvoiceGenerator generator, final AccountUserApi accountUserApi,
                              final BillingApi billingApi,
                              final InvoiceDao invoiceDao,
+                             final InvoiceNotifier invoiceNotifier,
                              final GlobalLocker locker,
                              final Bus eventBus,
                              final Clock clock) {
@@ -76,6 +79,7 @@ public class InvoiceDispatcher {
         this.billingApi = billingApi;
         this.accountUserApi = accountUserApi;
         this.invoiceDao = invoiceDao;
+        this.invoiceNotifier = invoiceNotifier;
         this.locker = locker;
         this.eventBus = eventBus;
         this.clock = clock;
@@ -138,6 +142,7 @@ public class InvoiceDispatcher {
             log.error("Failed to post DefaultEmptyInvoiceNotification event for account {} ", accountId, e);
         }
     }
+
     private Invoice processAccountWithLock(final UUID accountId, final DateTime targetDate,
             final boolean dryRun, final CallContext context) throws InvoiceApiException {
         try {
@@ -169,13 +174,16 @@ public class InvoiceDispatcher {
                     invoiceDao.create(invoice, context);
                 }
             }
+
+            if (account.isNotifiedForInvoices()) {
+                invoiceNotifier.notify(account, invoice);
+            }
+
             return invoice;
         } catch(AccountApiException e) {
             log.error("Failed handling entitlement change.",e);
             return null;    
-
         }
-
     }
 
     private void outputDebugData(Collection<BillingEvent> events, Collection<Invoice> invoices) {
diff --git a/invoice/src/main/java/com/ning/billing/invoice/model/CreditInvoiceItem.java b/invoice/src/main/java/com/ning/billing/invoice/model/CreditInvoiceItem.java
new file mode 100644
index 0000000..58c4a49
--- /dev/null
+++ b/invoice/src/main/java/com/ning/billing/invoice/model/CreditInvoiceItem.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you 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.ning.billing.invoice.model;
+
+import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.invoice.api.InvoiceItem;
+import org.joda.time.DateTime;
+
+import javax.annotation.Nullable;
+import java.math.BigDecimal;
+import java.util.UUID;
+
+public class CreditInvoiceItem extends InvoiceItemBase {
+    public CreditInvoiceItem(UUID invoiceId, UUID accountId, DateTime date, BigDecimal amount, Currency currency) {
+        this(UUID.randomUUID(), invoiceId, accountId, date, amount, currency, null, null);
+    }
+
+    public CreditInvoiceItem(UUID id, UUID invoiceId, UUID accountId, DateTime date, BigDecimal amount, Currency currency,
+                             @Nullable String createdBy, @Nullable DateTime createdDate) {
+        super(id, invoiceId, accountId, null, null, null, null, date, date, amount, currency, createdBy, createdDate);
+    }
+
+    @Override
+    public InvoiceItem asReversingItem() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public String getDescription() {
+        return "Credit";
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+
+        CreditInvoiceItem that = (CreditInvoiceItem) o;
+
+        if (accountId.compareTo(that.accountId) != 0) return false;
+        if (amount.compareTo(that.amount) != 0) return false;
+        if (currency != that.currency) return false;
+        if (startDate.compareTo(that.startDate) != 0) return false;
+        if (endDate.compareTo(that.endDate) != 0) return false;
+
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = accountId.hashCode();
+        result = 31 * result + startDate.hashCode();
+        result = 31 * result + endDate.hashCode();
+        result = 31 * result + amount.hashCode();
+        result = 31 * result + currency.hashCode();
+        return result;
+    }
+
+    @Override
+    public int compareTo(InvoiceItem item) {
+        if (!(item instanceof CreditInvoiceItem)) {
+            return 1;
+        }
+
+        CreditInvoiceItem that = (CreditInvoiceItem) item;
+        return id.compareTo(that.getId());
+    }
+}
diff --git a/invoice/src/main/java/com/ning/billing/invoice/model/DefaultInvoiceGenerator.java b/invoice/src/main/java/com/ning/billing/invoice/model/DefaultInvoiceGenerator.java
index 627a7be..c24215c 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/model/DefaultInvoiceGenerator.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/model/DefaultInvoiceGenerator.java
@@ -19,8 +19,10 @@ package com.ning.billing.invoice.model;
 import java.math.BigDecimal;
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.HashMap;
 import java.util.Iterator;
 import java.util.List;
+import java.util.Map;
 import java.util.UUID;
 
 import javax.annotation.Nullable;
@@ -59,9 +61,9 @@ public class DefaultInvoiceGenerator implements InvoiceGenerator {
     */
     @Override
     public Invoice generateInvoice(final UUID accountId, @Nullable final BillingEventSet events,
-                                   @Nullable final List<Invoice> existingInvoices,
-                                   DateTime targetDate,
-                                   final Currency targetCurrency) throws InvoiceApiException {
+                                         @Nullable final List<Invoice> existingInvoices,
+                                         DateTime targetDate,
+                                         final Currency targetCurrency) throws InvoiceApiException {
         if ((events == null) || (events.size() == 0)) {
             return null;
         }
@@ -91,18 +93,92 @@ public class DefaultInvoiceGenerator implements InvoiceGenerator {
         for (InvoiceItem existingItem : existingItems) {
             if (existingItem instanceof RecurringInvoiceItem) {
                 RecurringInvoiceItem recurringItem = (RecurringInvoiceItem) existingItem;
-                proposedItems.add(recurringItem.asCredit());
+                proposedItems.add(recurringItem.asReversingItem());
             }
         }
 
+        //addCreditItems(accountId, proposedItems, existingInvoices, targetCurrency);
+
         if (proposedItems == null || proposedItems.size() == 0) {
             return null;
         } else {
             invoice.addInvoiceItems(proposedItems);
+
             return invoice;
         }
     }
 
+   /*
+    * ensures that the balance of all invoices are zero or positive, adding an adjusting credit item if needed
+    */
+    private void addCreditItems(UUID accountId, List<InvoiceItem> invoiceItems, List<Invoice> invoices, Currency currency) {
+        Map<UUID, BigDecimal> invoiceBalances = new HashMap<UUID, BigDecimal>();
+
+        updateInvoiceBalance(invoiceItems, invoiceBalances);
+
+        // add all existing items and payments
+        if (invoices != null) {
+            for (Invoice invoice : invoices) {
+                updateInvoiceBalance(invoice.getInvoiceItems(), invoiceBalances);
+            }
+
+            for (Invoice invoice : invoices) {
+                UUID invoiceId = invoice.getId();
+                invoiceBalances.put(invoiceId, invoiceBalances.get(invoiceId).subtract(invoice.getAmountPaid()));
+            }
+        }
+
+        BigDecimal creditTotal = BigDecimal.ZERO;
+
+        for (UUID invoiceId : invoiceBalances.keySet()) {
+            BigDecimal balance = invoiceBalances.get(invoiceId);
+            if (balance.compareTo(BigDecimal.ZERO) < 0) {
+                creditTotal = creditTotal.add(balance.negate());
+                invoiceItems.add(new CreditInvoiceItem(invoiceId, accountId, clock.getUTCNow(), balance, currency));
+            }
+        }
+
+        if (creditTotal.compareTo(BigDecimal.ZERO) != 0) {
+            // create a single credit item to cover all credits
+            //invoiceItems.add(new CreditInvoiceItem());
+        }
+    }
+
+    private void updateInvoiceBalance(List<InvoiceItem> items, Map<UUID, BigDecimal> invoiceBalances) {
+        for (InvoiceItem item : items) {
+            UUID invoiceId = item.getInvoiceId();
+
+            if (!invoiceBalances.containsKey(invoiceId)) {
+                invoiceBalances.put(invoiceId, BigDecimal.ZERO);
+            }
+
+            invoiceBalances.put(invoiceId, invoiceBalances.get(invoiceId).add(item.getAmount()));
+        }
+    }
+
+    @Override
+    public void distributeItems(List<Invoice> invoices) {
+        Map<UUID, Invoice> invoiceMap = new HashMap<UUID, Invoice>();
+
+        for (Invoice invoice : invoices) {
+            invoiceMap.put(invoice.getId(), invoice);
+        }
+
+        for (final Invoice invoice: invoices) {
+            Iterator<InvoiceItem> itemIterator = invoice.getInvoiceItems().iterator();
+            final UUID invoiceId = invoice.getId();
+
+            while (itemIterator.hasNext()) {
+                InvoiceItem item = itemIterator.next();
+
+                if (!item.getInvoiceId().equals(invoiceId)) {
+                    invoiceMap.get(item.getInvoiceId()).addInvoiceItem(item);
+                    itemIterator.remove();
+                }
+            }
+        }
+    }
+
     private void validateTargetDate(DateTime targetDate) throws InvoiceApiException {
         int maximumNumberOfMonths = config.getNumberOfMonthsInFuture();
 
diff --git a/invoice/src/main/java/com/ning/billing/invoice/model/FixedPriceInvoiceItem.java b/invoice/src/main/java/com/ning/billing/invoice/model/FixedPriceInvoiceItem.java
index 8c2398b..72566df 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/model/FixedPriceInvoiceItem.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/model/FixedPriceInvoiceItem.java
@@ -38,7 +38,7 @@ public class FixedPriceInvoiceItem extends InvoiceItemBase {
     }
 
     @Override
-    public InvoiceItem asCredit() {
+    public InvoiceItem asReversingItem() {
         throw new UnsupportedOperationException();
     }
 
diff --git a/invoice/src/main/java/com/ning/billing/invoice/model/InvoiceGenerator.java b/invoice/src/main/java/com/ning/billing/invoice/model/InvoiceGenerator.java
index 01ebf34..f903087 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/model/InvoiceGenerator.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/model/InvoiceGenerator.java
@@ -19,6 +19,7 @@ package com.ning.billing.invoice.model;
 import com.ning.billing.catalog.api.Currency;
 import com.ning.billing.invoice.api.Invoice;
 import com.ning.billing.invoice.api.InvoiceApiException;
+import com.ning.billing.invoice.api.InvoiceItem;
 import org.joda.time.DateTime;
 
 import javax.annotation.Nullable;
@@ -26,5 +27,8 @@ import java.util.List;
 import java.util.UUID;
 
 public interface InvoiceGenerator {
-    public Invoice generateInvoice(UUID accountId, @Nullable BillingEventSet events, @Nullable List<Invoice> existingInvoices, DateTime targetDate, Currency targetCurrency) throws InvoiceApiException;
+    public Invoice generateInvoice(UUID accountId, @Nullable BillingEventSet events, @Nullable List<Invoice> existingInvoices,
+                                   DateTime targetDate, Currency targetCurrency) throws InvoiceApiException;
+
+    public void distributeItems(List<Invoice> invoices);
 }
diff --git a/invoice/src/main/java/com/ning/billing/invoice/model/InvoiceItemBase.java b/invoice/src/main/java/com/ning/billing/invoice/model/InvoiceItemBase.java
index 175a08e..f659807 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/model/InvoiceItemBase.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/model/InvoiceItemBase.java
@@ -119,7 +119,7 @@ public abstract class InvoiceItemBase extends EntityBase implements InvoiceItem 
     }
 
     @Override
-    public abstract InvoiceItem asCredit();
+    public abstract InvoiceItem asReversingItem();
 
     @Override
     public abstract String getDescription();
diff --git a/invoice/src/main/java/com/ning/billing/invoice/model/RecurringInvoiceItem.java b/invoice/src/main/java/com/ning/billing/invoice/model/RecurringInvoiceItem.java
index 683a610..6fd9608 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/model/RecurringInvoiceItem.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/model/RecurringInvoiceItem.java
@@ -67,7 +67,7 @@ public class RecurringInvoiceItem extends InvoiceItemBase {
     }
 
     @Override
-    public InvoiceItem asCredit() {
+    public InvoiceItem asReversingItem() {
         BigDecimal amountNegated = amount == null ? null : amount.negate();
 
         return new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate, endDate,
diff --git a/invoice/src/main/java/com/ning/billing/invoice/notification/EmailInvoiceNotifier.java b/invoice/src/main/java/com/ning/billing/invoice/notification/EmailInvoiceNotifier.java
new file mode 100644
index 0000000..58026b2
--- /dev/null
+++ b/invoice/src/main/java/com/ning/billing/invoice/notification/EmailInvoiceNotifier.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you 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.ning.billing.invoice.notification;
+
+import com.google.inject.Inject;
+import com.ning.billing.ErrorCode;
+import com.ning.billing.account.api.Account;
+import com.ning.billing.account.api.AccountEmail;
+import com.ning.billing.account.api.AccountUserApi;
+import com.ning.billing.invoice.api.Invoice;
+import com.ning.billing.invoice.api.InvoiceApiException;
+import com.ning.billing.invoice.api.InvoiceNotifier;
+import com.ning.billing.invoice.template.HtmlInvoiceGenerator;
+import com.ning.billing.util.email.DefaultEmailSender;
+import com.ning.billing.util.email.EmailApiException;
+import com.ning.billing.util.email.EmailConfig;
+import com.ning.billing.util.email.EmailSender;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+public class EmailInvoiceNotifier implements InvoiceNotifier {
+    private final AccountUserApi accountUserApi;
+    private final HtmlInvoiceGenerator generator;
+    private final EmailConfig config;
+
+    @Inject
+    public EmailInvoiceNotifier(AccountUserApi accountUserApi, HtmlInvoiceGenerator generator, EmailConfig config) {
+        this.accountUserApi = accountUserApi;
+        this.generator = generator;
+        this.config = config;
+    }
+
+    @Override
+    public void notify(Account account, Invoice invoice) throws InvoiceApiException {
+        List<String> to = new ArrayList<String>();
+        to.add(account.getEmail());
+
+        List<AccountEmail> accountEmailList = accountUserApi.getEmails(account.getId());
+        List<String> cc = new ArrayList<String>();
+        for (AccountEmail email : accountEmailList) {
+            cc.add(email.getEmail());
+        }
+
+        String htmlBody = null;
+        try {
+            htmlBody = generator.generateInvoice(account, invoice, "HtmlInvoiceTemplate");
+        } catch (IOException e) {
+            throw new InvoiceApiException(e, ErrorCode.EMAIL_SENDING_FAILED);
+        }
+
+        // TODO: get subject
+        String subject = "";
+
+        EmailSender sender = new DefaultEmailSender(config);
+        try {
+            sender.sendSecureEmail(to, cc, subject, htmlBody);
+        } catch (EmailApiException e) {
+            throw new InvoiceApiException(e, ErrorCode.EMAIL_SENDING_FAILED);
+        } catch (IOException e) {
+            throw new InvoiceApiException(e, ErrorCode.EMAIL_SENDING_FAILED);
+        }
+    }
+}
diff --git a/invoice/src/main/java/com/ning/billing/invoice/notification/NullInvoiceNotifier.java b/invoice/src/main/java/com/ning/billing/invoice/notification/NullInvoiceNotifier.java
new file mode 100644
index 0000000..460e779
--- /dev/null
+++ b/invoice/src/main/java/com/ning/billing/invoice/notification/NullInvoiceNotifier.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you 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.ning.billing.invoice.notification;
+
+import com.ning.billing.account.api.Account;
+import com.ning.billing.invoice.api.Invoice;
+import com.ning.billing.invoice.api.InvoiceNotifier;
+
+public class NullInvoiceNotifier implements InvoiceNotifier {
+    @Override
+    public void notify(Account account, Invoice invoice) {
+        // deliberate no-op
+    }
+}
\ No newline at end of file
diff --git a/invoice/src/main/java/com/ning/billing/invoice/template/formatters/DefaultInvoiceFormatter.java b/invoice/src/main/java/com/ning/billing/invoice/template/formatters/DefaultInvoiceFormatter.java
new file mode 100644
index 0000000..2fad805
--- /dev/null
+++ b/invoice/src/main/java/com/ning/billing/invoice/template/formatters/DefaultInvoiceFormatter.java
@@ -0,0 +1,286 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you 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.
+ */
+
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you 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.ning.billing.invoice.template.formatters;
+
+import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.invoice.api.Invoice;
+import com.ning.billing.invoice.api.InvoiceItem;
+import com.ning.billing.invoice.api.InvoicePayment;
+import com.ning.billing.invoice.api.formatters.InvoiceFormatter;
+import com.ning.billing.util.callcontext.CallContext;
+import com.ning.billing.util.customfield.CustomField;
+import com.ning.billing.util.tag.ControlTagType;
+import com.ning.billing.util.template.translation.TranslatorConfig;
+import com.ning.billing.util.tag.Tag;
+import com.ning.billing.util.tag.TagDefinition;
+import org.joda.time.DateTime;
+import org.joda.time.format.DateTimeFormat;
+import org.joda.time.format.DateTimeFormatter;
+
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+import java.util.UUID;
+
+public class DefaultInvoiceFormatter implements InvoiceFormatter {
+    private final TranslatorConfig config;
+    private final Invoice invoice;
+    private final DateTimeFormatter dateFormatter;
+    private final Locale locale;
+
+    public DefaultInvoiceFormatter(TranslatorConfig config, Invoice invoice, Locale locale) {
+        this.config = config;
+        this.invoice = invoice;
+        dateFormatter = DateTimeFormat.mediumDate().withLocale(locale);
+        this.locale = locale;
+    }
+
+    @Override
+    public Integer getInvoiceNumber() {
+        return invoice.getInvoiceNumber();
+    }
+
+    @Override
+    public List<InvoiceItem> getInvoiceItems() {
+        List<InvoiceItem> formatters = new ArrayList<InvoiceItem>();
+        for (InvoiceItem item : invoice.getInvoiceItems()) {
+            formatters.add(new DefaultInvoiceItemFormatter(config, item, dateFormatter, locale));
+        }
+        return formatters;
+    }
+
+    @Override
+    public boolean addInvoiceItem(InvoiceItem item) {
+        return invoice.addInvoiceItem(item);
+    }
+
+    @Override
+    public boolean addInvoiceItems(List<InvoiceItem> items) {
+        return invoice.addInvoiceItems(items);
+    }
+
+    @Override
+    public <T extends InvoiceItem> List<InvoiceItem> getInvoiceItems(Class<T> clazz) {
+        return invoice.getInvoiceItems(clazz);
+    }
+
+    @Override
+    public int getNumberOfItems() {
+        return invoice.getNumberOfItems();
+    }
+
+    @Override
+    public boolean addPayment(InvoicePayment payment) {
+        return invoice.addPayment(payment);
+    }
+
+    @Override
+    public boolean addPayments(List<InvoicePayment> payments) {
+        return invoice.addPayments(payments);
+    }
+
+    @Override
+    public List<InvoicePayment> getPayments() {
+        return invoice.getPayments();
+    }
+
+    @Override
+    public int getNumberOfPayments() {
+        return invoice.getNumberOfPayments();
+    }
+
+    @Override
+    public UUID getAccountId() {
+        return invoice.getAccountId();
+    }
+
+    @Override
+    public BigDecimal getTotalAmount() {
+        return invoice.getTotalAmount();
+    }
+
+    @Override
+    public BigDecimal getBalance() {
+        return invoice.getBalance();
+    }
+
+    @Override
+    public boolean isDueForPayment(DateTime targetDate, int numberOfDays) {
+        return invoice.isDueForPayment(targetDate, numberOfDays);
+    }
+
+    @Override
+    public boolean isMigrationInvoice() {
+        return invoice.isMigrationInvoice();
+    }
+
+    @Override
+    public DateTime getInvoiceDate() {
+        return invoice.getInvoiceDate();
+    }
+
+    @Override
+    public DateTime getTargetDate() {
+        return invoice.getTargetDate();
+    }
+
+    @Override
+    public Currency getCurrency() {
+        return invoice.getCurrency();
+    }
+
+    @Override
+    public DateTime getLastPaymentAttempt() {
+        return invoice.getLastPaymentAttempt();
+    }
+
+    @Override
+    public BigDecimal getAmountPaid() {
+        return invoice.getAmountPaid();
+    }
+
+    @Override
+    public String getFormattedInvoiceDate() {
+        return invoice.getInvoiceDate().toString(dateFormatter);
+    }
+
+    @Override
+    public String getFieldValue(String fieldName) {
+        return invoice.getFieldValue(fieldName);
+    }
+
+    @Override
+    public void setFieldValue(String fieldName, String fieldValue) {
+        invoice.setFieldValue(fieldName, fieldValue);
+    }
+
+    @Override
+    public void saveFieldValue(String fieldName, String fieldValue, CallContext context) {
+        invoice.saveFieldValue(fieldName, fieldValue, context);
+    }
+
+    @Override
+    public List<CustomField> getFieldList() {
+        return invoice.getFieldList();
+    }
+
+    @Override
+    public void setFields(List<CustomField> fields) {
+        invoice.setFields(fields);
+    }
+
+    @Override
+    public void saveFields(List<CustomField> fields, CallContext context) {
+        invoice.saveFields(fields, context);
+    }
+
+    @Override
+    public void clearFields() {
+        invoice.clearFields();
+    }
+
+    @Override
+    public void clearPersistedFields(CallContext context) {
+        invoice.clearPersistedFields(context);
+    }
+
+    @Override
+    public String getObjectName() {
+        return invoice.getObjectName();
+    }
+
+    @Override
+    public UUID getId() {
+        return invoice.getId();
+    }
+
+    @Override
+    public String getCreatedBy() {
+        return invoice.getCreatedBy();
+    }
+
+    @Override
+    public DateTime getCreatedDate() {
+        return invoice.getCreatedDate();
+    }
+
+    @Override
+    public List<Tag> getTagList() {
+        return invoice.getTagList();
+    }
+
+    @Override
+    public boolean hasTag(TagDefinition tagDefinition) {
+        return invoice.hasTag(tagDefinition);
+    }
+
+    @Override
+    public boolean hasTag(ControlTagType controlTagType) {
+        return invoice.hasTag(controlTagType);
+    }
+
+    @Override
+    public void addTag(TagDefinition definition) {
+        invoice.addTag(definition);
+    }
+
+    @Override
+    public void addTags(List<Tag> tags) {
+        invoice.addTags(tags);
+    }
+
+    @Override
+    public void addTagsFromDefinitions(List<TagDefinition> tagDefinitions) {
+        invoice.addTagsFromDefinitions(tagDefinitions);
+    }
+
+    @Override
+    public void clearTags() {
+        invoice.clearTags();
+    }
+
+    @Override
+    public void removeTag(TagDefinition definition) {
+        invoice.removeTag(definition);
+    }
+
+    @Override
+    public boolean generateInvoice() {
+        return invoice.generateInvoice();
+    }
+
+    @Override
+    public boolean processPayment() {
+        return invoice.processPayment();
+    }
+}
diff --git a/invoice/src/main/java/com/ning/billing/invoice/template/formatters/DefaultInvoiceFormatterFactory.java b/invoice/src/main/java/com/ning/billing/invoice/template/formatters/DefaultInvoiceFormatterFactory.java
new file mode 100644
index 0000000..83c0619
--- /dev/null
+++ b/invoice/src/main/java/com/ning/billing/invoice/template/formatters/DefaultInvoiceFormatterFactory.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you 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.ning.billing.invoice.template.formatters;
+
+import com.ning.billing.invoice.api.Invoice;
+import com.ning.billing.invoice.api.formatters.InvoiceFormatter;
+import com.ning.billing.invoice.api.formatters.InvoiceFormatterFactory;
+import com.ning.billing.util.template.translation.TranslatorConfig;
+
+import java.util.Locale;
+
+public class DefaultInvoiceFormatterFactory implements InvoiceFormatterFactory {
+    @Override
+    public InvoiceFormatter createInvoiceFormatter(TranslatorConfig config, Invoice invoice, Locale locale) {
+        return new DefaultInvoiceFormatter(config, invoice, locale);
+    }
+}
diff --git a/invoice/src/main/java/com/ning/billing/invoice/template/formatters/DefaultInvoiceItemFormatter.java b/invoice/src/main/java/com/ning/billing/invoice/template/formatters/DefaultInvoiceItemFormatter.java
new file mode 100644
index 0000000..cc072d9
--- /dev/null
+++ b/invoice/src/main/java/com/ning/billing/invoice/template/formatters/DefaultInvoiceItemFormatter.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you 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.ning.billing.invoice.template.formatters;
+
+import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.invoice.api.InvoiceItem;
+import com.ning.billing.invoice.api.formatters.InvoiceItemFormatter;
+import com.ning.billing.util.template.translation.DefaultCatalogTranslator;
+import com.ning.billing.util.template.translation.Translator;
+import com.ning.billing.util.template.translation.TranslatorConfig;
+import org.joda.time.DateTime;
+import org.joda.time.format.DateTimeFormatter;
+
+import java.math.BigDecimal;
+import java.util.Locale;
+import java.util.UUID;
+
+public class DefaultInvoiceItemFormatter implements InvoiceItemFormatter {
+    private final Translator translator;
+
+    private final InvoiceItem item;
+    private final DateTimeFormatter dateFormatter;
+    private final Locale locale;
+
+    public DefaultInvoiceItemFormatter(TranslatorConfig config, InvoiceItem item, DateTimeFormatter dateFormatter, Locale locale) {
+        this.item = item;
+        this.dateFormatter = dateFormatter;
+        this.locale = locale;
+
+        this.translator = new DefaultCatalogTranslator(config);
+    }
+
+    @Override
+    public BigDecimal getAmount() {
+        return item.getAmount();
+    }
+
+    @Override
+    public Currency getCurrency() {
+        return item.getCurrency();
+    }
+
+    @Override
+    public InvoiceItem asReversingItem() {
+        return item.asReversingItem();
+    }
+
+    @Override
+    public String getDescription() {
+        return item.getDescription();
+    }
+
+    @Override
+    public DateTime getStartDate() {
+        return item.getStartDate();
+    }
+
+    @Override
+    public DateTime getEndDate() {
+        return item.getEndDate();
+    }
+
+    @Override
+    public String getFormattedStartDate() {
+        return item.getStartDate().toString(dateFormatter);
+    }
+
+    @Override
+    public String getFormattedEndDate() {
+        return item.getEndDate().toString(dateFormatter);
+    }
+
+    @Override
+    public UUID getInvoiceId() {
+        return item.getInvoiceId();
+    }
+
+    @Override
+    public UUID getAccountId() {
+        return item.getAccountId();
+    }
+
+    @Override
+    public UUID getBundleId() {
+        return item.getBundleId();
+    }
+
+    @Override
+    public UUID getSubscriptionId() {
+        return item.getSubscriptionId();
+    }
+
+    @Override
+    public String getPlanName() {
+        return translator.getTranslation(locale, item.getPlanName());
+    }
+
+    @Override
+    public String getPhaseName() {
+        return translator.getTranslation(locale, item.getPhaseName());
+    }
+
+    @Override
+    public int compareTo(InvoiceItem invoiceItem) {
+        return item.compareTo(invoiceItem);
+    }
+
+    @Override
+    public UUID getId() {
+        return item.getId();
+    }
+
+    @Override
+    public String getCreatedBy() {
+        return item.getCreatedBy();
+    }
+
+    @Override
+    public DateTime getCreatedDate() {
+        return item.getCreatedDate();
+    }
+}
\ No newline at end of file
diff --git a/invoice/src/main/java/com/ning/billing/invoice/template/HtmlInvoiceGenerator.java b/invoice/src/main/java/com/ning/billing/invoice/template/HtmlInvoiceGenerator.java
new file mode 100644
index 0000000..2a006d8
--- /dev/null
+++ b/invoice/src/main/java/com/ning/billing/invoice/template/HtmlInvoiceGenerator.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you 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.ning.billing.invoice.template;
+
+import com.google.inject.Inject;
+import com.ning.billing.account.api.Account;
+import com.ning.billing.invoice.api.Invoice;
+import com.ning.billing.invoice.api.formatters.InvoiceFormatter;
+import com.ning.billing.invoice.api.formatters.InvoiceFormatterFactory;
+import com.ning.billing.invoice.template.translator.DefaultInvoiceTranslator;
+import com.ning.billing.util.email.templates.TemplateEngine;
+import com.ning.billing.util.template.translation.TranslatorConfig;
+
+import java.io.IOException;
+import java.lang.String;
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+
+public class HtmlInvoiceGenerator {
+    private final InvoiceFormatterFactory factory;
+    private final TemplateEngine templateEngine;
+    private final TranslatorConfig config;
+
+    @Inject
+    public HtmlInvoiceGenerator(InvoiceFormatterFactory factory, TemplateEngine templateEngine, TranslatorConfig config) {
+        this.factory = factory;
+        this.templateEngine = templateEngine;
+        this.config = config;
+    }
+
+    public String generateInvoice(Account account, Invoice invoice, String templateName) throws IOException {
+        Map<String, Object> data = new HashMap<String, Object>();
+        DefaultInvoiceTranslator invoiceTranslator = new DefaultInvoiceTranslator(config);
+        Locale locale = new Locale(account.getLocale());
+        invoiceTranslator.setLocale(locale);
+        data.put("text", invoiceTranslator);
+        data.put("account", account);
+
+        InvoiceFormatter formattedInvoice = factory.createInvoiceFormatter(config, invoice, locale);
+        data.put("invoice", formattedInvoice);
+
+        return templateEngine.executeTemplate(templateName, data);
+    }
+}
\ No newline at end of file
diff --git a/invoice/src/main/java/com/ning/billing/invoice/template/translator/DefaultInvoiceTranslator.java b/invoice/src/main/java/com/ning/billing/invoice/template/translator/DefaultInvoiceTranslator.java
new file mode 100644
index 0000000..f238ea7
--- /dev/null
+++ b/invoice/src/main/java/com/ning/billing/invoice/template/translator/DefaultInvoiceTranslator.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you 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.ning.billing.invoice.template.translator;
+
+import com.google.inject.Inject;
+import com.ning.billing.util.template.translation.DefaultTranslatorBase;
+import com.ning.billing.util.template.translation.TranslatorConfig;
+
+import java.util.Locale;
+
+public class DefaultInvoiceTranslator extends DefaultTranslatorBase implements InvoiceStrings {
+    private Locale locale;
+
+    @Inject
+    public DefaultInvoiceTranslator(TranslatorConfig config) {
+        super(config);
+    }
+
+    public void setLocale(Locale locale) {
+        this.locale = locale;
+    }
+
+    @Override
+    protected String getBundlePath() {
+        return "com/ning/billing/util/email/translation/InvoiceTranslation";
+    }
+
+    @Override
+    protected String getTranslationType() {
+        return "invoice";
+    }
+
+    @Override
+    public String getInvoiceTitle() {
+        return getTranslation(locale, "invoiceTitle");
+    }
+
+    @Override
+    public String getInvoiceDate() {
+        return getTranslation(locale, "invoiceDate");
+    }
+
+    @Override
+    public String getInvoiceNumber() {
+        return getTranslation(locale, "invoiceNumber");
+    }
+
+    @Override
+    public String getAccountOwnerName() {
+        return getTranslation(locale, "accountOwnerName");
+    }
+
+    @Override
+    public String getAccountOwnerEmail() {
+        return getTranslation(locale, "accountOwnerEmail");
+    }
+
+    @Override
+    public String getAccountOwnerPhone() {
+        return getTranslation(locale, "accountOwnerPhone");
+    }
+
+    @Override
+    public String getCompanyName() {
+        return getTranslation(locale, "companyName");
+    }
+
+    @Override
+    public String getCompanyAddress() {
+        return getTranslation(locale, "companyAddress");
+    }
+
+    @Override
+    public String getCompanyCityProvincePostalCode() {
+        return getTranslation(locale, "");
+    }
+
+    @Override
+    public String getCompanyCountry() {
+        return getTranslation(locale, "companyCountry");
+    }
+
+    @Override
+    public String getCompanyUrl() {
+        return getTranslation(locale, "companyUrl");
+    }
+
+    @Override
+    public String getInvoiceItemBundleName() {
+        return getTranslation(locale, "invoiceItemBundleName");
+    }
+
+    @Override
+    public String getInvoiceItemDescription() {
+        return getTranslation(locale, "invoiceItemDescription");
+    }
+
+    @Override
+    public String getInvoiceItemServicePeriod() {
+        return getTranslation(locale, "invoiceItemServicePeriod");
+    }
+
+    @Override
+    public String getInvoiceItemAmount() {
+        return getTranslation(locale, "invoiceItemAmount");
+    }
+
+    @Override
+    public String getInvoiceAmount() {
+        return getTranslation(locale, "invoiceAmount");
+    }
+
+    @Override
+    public String getInvoiceAmountPaid() {
+        return getTranslation(locale, "invoiceAmountPaid");
+    }
+
+    @Override
+    public String getInvoiceBalance() {
+        return getTranslation(locale, "invoiceBalance");
+    }
+}
diff --git a/invoice/src/main/java/com/ning/billing/invoice/template/translator/InvoiceStrings.java b/invoice/src/main/java/com/ning/billing/invoice/template/translator/InvoiceStrings.java
new file mode 100644
index 0000000..cc19d5f
--- /dev/null
+++ b/invoice/src/main/java/com/ning/billing/invoice/template/translator/InvoiceStrings.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you 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.ning.billing.invoice.template.translator;
+
+public interface InvoiceStrings {
+    String getInvoiceTitle();
+    String getInvoiceDate();
+    String getInvoiceNumber();
+    String getAccountOwnerName();
+    String getAccountOwnerEmail();
+    String getAccountOwnerPhone();
+
+    // company name and address
+    String getCompanyName();
+    String getCompanyAddress();
+    String getCompanyCityProvincePostalCode();
+    String getCompanyCountry();
+    String getCompanyUrl();
+
+    String getInvoiceItemBundleName();
+    String getInvoiceItemDescription();
+    String getInvoiceItemServicePeriod();
+    String getInvoiceItemAmount();
+
+    String getInvoiceAmount();
+    String getInvoiceAmountPaid();
+    String getInvoiceBalance();
+}
diff --git a/invoice/src/test/java/com/ning/billing/invoice/api/migration/MockModuleNoEntitlement.java b/invoice/src/test/java/com/ning/billing/invoice/api/migration/MockModuleNoEntitlement.java
index 7b3423c..2dcd4d9 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/api/migration/MockModuleNoEntitlement.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/api/migration/MockModuleNoEntitlement.java
@@ -16,39 +16,39 @@
 
 package com.ning.billing.invoice.api.migration;
 
-import com.ning.billing.entitlement.api.billing.ChargeThruApi;
 import com.ning.billing.invoice.MockModule;
+import com.ning.billing.invoice.api.InvoiceNotifier;
 import com.ning.billing.invoice.glue.DefaultInvoiceModule;
 import com.ning.billing.invoice.notification.NextBillingDateNotifier;
 import com.ning.billing.invoice.notification.NextBillingDatePoster;
-import com.ning.billing.junction.api.BillingApi;
+import com.ning.billing.invoice.notification.NullInvoiceNotifier;
 import com.ning.billing.mock.BrainDeadProxyFactory;
 import com.ning.billing.mock.BrainDeadProxyFactory.ZombieControl;
 
 public class MockModuleNoEntitlement extends MockModule {
-
-	@Override
-	protected void installEntitlementModule() {
-		BillingApi entitlementApi = BrainDeadProxyFactory.createBrainDeadProxyFor(BillingApi.class);
-        ((ZombieControl)entitlementApi).addResult("setChargedThroughDateFromTransaction", BrainDeadProxyFactory.ZOMBIE_VOID);
-        ((ZombieControl)entitlementApi).addResult("getBillingEventsForAccountAndUpdateAccountBCD", BrainDeadProxyFactory.ZOMBIE_VOID);
-		//bind(BillingApi.class).toInstance(entitlementApi);
-//        bind(EntitlementUserApi.class).toInstance(BrainDeadProxyFactory.createBrainDeadProxyFor(EntitlementUserApi.class));
-        ChargeThruApi cta = BrainDeadProxyFactory.createBrainDeadProxyFor(ChargeThruApi.class);
-        bind(ChargeThruApi.class).toInstance(cta);
-        ((ZombieControl)cta).addResult("setChargedThroughDateFromTransaction", BrainDeadProxyFactory.ZOMBIE_VOID);
-	}
+//	@Override
+//	protected void installEntitlementModule() {
+//		BillingApi entitlementApi = BrainDeadProxyFactory.createBrainDeadProxyFor(BillingApi.class);
+//        ((ZombieControl)entitlementApi).addResult("setChargedThroughDateFromTransaction", BrainDeadProxyFactory.ZOMBIE_VOID);
+//        ((ZombieControl)entitlementApi).addResult("getBillingEventsForAccountAndUpdateAccountBCD", BrainDeadProxyFactory.ZOMBIE_VOID);
+//		//bind(BillingApi.class).toInstance(entitlementApi);
+////        bind(EntitlementUserApi.class).toInstance(BrainDeadProxyFactory.createBrainDeadProxyFor(EntitlementUserApi.class));
+//        ChargeThruApi cta = BrainDeadProxyFactory.createBrainDeadProxyFor(ChargeThruApi.class);
+//        bind(ChargeThruApi.class).toInstance(cta);
+//        ((ZombieControl)cta).addResult("setChargedThroughDateFromTransaction", BrainDeadProxyFactory.ZOMBIE_VOID);
+//	}
 
 	@Override
 	protected void installInvoiceModule() {
 		install(new DefaultInvoiceModule(){
 
 			@Override
-			protected void installNotifier() {
+			protected void installNotifiers() {
 				 bind(NextBillingDateNotifier.class).toInstance(BrainDeadProxyFactory.createBrainDeadProxyFor(NextBillingDateNotifier.class));
 				 NextBillingDatePoster poster = BrainDeadProxyFactory.createBrainDeadProxyFor(NextBillingDatePoster.class);
 				 ((ZombieControl)poster).addResult("insertNextBillingNotification",BrainDeadProxyFactory.ZOMBIE_VOID);
 			     bind(NextBillingDatePoster.class).toInstance(poster);
+                bind(InvoiceNotifier.class).to(NullInvoiceNotifier.class).asEagerSingleton();
 			}
 			
 			
diff --git a/invoice/src/test/java/com/ning/billing/invoice/api/migration/TestDefaultInvoiceMigrationApi.java b/invoice/src/test/java/com/ning/billing/invoice/api/migration/TestDefaultInvoiceMigrationApi.java
index 8efba0d..5d7d14f 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/api/migration/TestDefaultInvoiceMigrationApi.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/api/migration/TestDefaultInvoiceMigrationApi.java
@@ -23,6 +23,7 @@ import java.util.SortedSet;
 import java.util.TreeSet;
 import java.util.UUID;
 
+import com.ning.billing.invoice.tests.InvoicingTestBase;
 import org.apache.commons.io.IOUtils;
 import org.joda.time.DateTime;
 import org.slf4j.Logger;
@@ -51,12 +52,13 @@ import com.ning.billing.invoice.InvoiceDispatcher;
 import com.ning.billing.invoice.TestInvoiceDispatcher;
 import com.ning.billing.invoice.api.Invoice;
 import com.ning.billing.invoice.api.InvoiceMigrationApi;
+import com.ning.billing.invoice.api.InvoiceNotifier;
 import com.ning.billing.invoice.api.InvoicePaymentApi;
 import com.ning.billing.invoice.api.InvoiceUserApi;
 import com.ning.billing.invoice.dao.InvoiceDao;
 import com.ning.billing.invoice.model.InvoiceGenerator;
+import com.ning.billing.invoice.notification.NullInvoiceNotifier;
 import com.ning.billing.junction.api.BillingApi;
-import com.ning.billing.junction.plumbing.billing.DefaultBillingEvent;
 import com.ning.billing.mock.BrainDeadProxyFactory;
 import com.ning.billing.mock.BrainDeadProxyFactory.ZombieControl;
 import com.ning.billing.util.bus.BusService;
@@ -69,8 +71,8 @@ import com.ning.billing.util.clock.Clock;
 import com.ning.billing.util.clock.ClockMock;
 import com.ning.billing.util.globallocker.GlobalLocker;
 
-@Guice(modules = {MockModuleNoEntitlement.class, })
-public class TestDefaultInvoiceMigrationApi {
+@Guice(modules = {MockModuleNoEntitlement.class})
+public class TestDefaultInvoiceMigrationApi extends InvoicingTestBase {
 	Logger log = LoggerFactory.getLogger(TestDefaultInvoiceMigrationApi.class);
 
 	@Inject
@@ -98,8 +100,6 @@ public class TestDefaultInvoiceMigrationApi {
 	@Inject
 	private BillingApi billingApi;
 
-
-
 	private UUID accountId ;
 	private UUID subscriptionId ;
 	private DateTime date_migrated;
@@ -174,6 +174,7 @@ public class TestDefaultInvoiceMigrationApi {
 		((ZombieControl)accountUserApi).addResult("getAccountById", account);
 		((ZombieControl)account).addResult("getCurrency", Currency.USD);
 		((ZombieControl)account).addResult("getId", accountId);
+        ((ZombieControl)account).addResult("isNotifiedForInvoices", true);
 
 		Subscription subscription =  BrainDeadProxyFactory.createBrainDeadProxyFor(Subscription.class);
         ((ZombieControl)subscription).addResult("getId", subscriptionId);
@@ -184,13 +185,16 @@ public class TestDefaultInvoiceMigrationApi {
 		DateTime effectiveDate = new DateTime().minusDays(1);
 		Currency currency = Currency.USD;
 		BigDecimal fixedPrice = null;
-		events.add(new DefaultBillingEvent(account, subscription, effectiveDate,plan, planPhase,
-				fixedPrice, BigDecimal.ONE, currency, BillingPeriod.MONTHLY, 1,
-				BillingModeType.IN_ADVANCE, "", 1L, SubscriptionTransitionType.CREATE));
+		events.add(createMockBillingEvent(account, subscription, effectiveDate, plan, planPhase,
+                fixedPrice, BigDecimal.ONE, currency, BillingPeriod.MONTHLY, 1,
+                BillingModeType.IN_ADVANCE, "", 1L, SubscriptionTransitionType.CREATE));
 
 		BillingApi entitlementBillingApi = BrainDeadProxyFactory.createBrainDeadProxyFor(BillingApi.class);
         ((ZombieControl)entitlementBillingApi).addResult("getBillingEventsForAccountAndUpdateAccountBCD", events);
-		InvoiceDispatcher dispatcher = new InvoiceDispatcher(generator, accountUserApi, entitlementBillingApi, invoiceDao, locker, busService.getBus(), clock);
+
+        InvoiceNotifier invoiceNotifier = new NullInvoiceNotifier();
+		InvoiceDispatcher dispatcher = new InvoiceDispatcher(generator, accountUserApi, entitlementBillingApi,
+                                                             invoiceDao, invoiceNotifier, locker, busService.getBus(), clock);
 
         CallContext context = new DefaultCallContextFactory(clock).createCallContext("Migration test", CallOrigin.TEST, UserType.TEST);
 		Invoice invoice = dispatcher.processAccount(accountId, date_regular, true, context);
@@ -209,7 +213,7 @@ public class TestDefaultInvoiceMigrationApi {
 	}
 
 	// Check migration invoice is NOT returned for all user api invoice calls
-	@Test(groups={"slow"},enabled=true)
+	@Test(groups={"slow"}, enabled=true)
 	public void testUserApiAccess(){
 		List<Invoice> byAccount = invoiceUserApi.getInvoicesByAccount(accountId);
 		Assert.assertEquals(byAccount.size(),1);
diff --git a/invoice/src/test/java/com/ning/billing/invoice/dao/InvoiceDaoTestBase.java b/invoice/src/test/java/com/ning/billing/invoice/dao/InvoiceDaoTestBase.java
index a6980bc..08354db 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/dao/InvoiceDaoTestBase.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/dao/InvoiceDaoTestBase.java
@@ -70,12 +70,10 @@ public abstract class InvoiceDaoTestBase extends InvoicingTestBase {
     protected void setup() throws IOException {
             module = new InvoiceModuleWithEmbeddedDb();
             final String invoiceDdl = IOUtils.toString(DefaultInvoiceDao.class.getResourceAsStream("/com/ning/billing/invoice/ddl.sql"));
-            final String entitlementDdl = IOUtils.toString(DefaultInvoiceDao.class.getResourceAsStream("/com/ning/billing/entitlement/ddl.sql"));
             final String utilDdl = IOUtils.toString(DefaultInvoiceDao.class.getResourceAsStream("/com/ning/billing/util/ddl.sql"));
 
             module.startDb();
             module.initDb(invoiceDdl);
-            module.initDb(entitlementDdl);
             module.initDb(utilDdl);
 
             final Injector injector = Guice.createInjector(Stage.DEVELOPMENT, module);
@@ -103,21 +101,9 @@ public abstract class InvoiceDaoTestBase extends InvoicingTestBase {
             @Override
             public Void inTransaction(Handle h, TransactionStatus status)
                     throws Exception {
-                //h.execute("truncate table accounts");
-                //h.execute("truncate table entitlement_events");
-                //h.execute("truncate table subscriptions");
-                //h.execute("truncate table bundles");
-                //h.execute("truncate table notifications");
-                //h.execute("truncate table claimed_notifications");
                 h.execute("truncate table invoices");
                 h.execute("truncate table fixed_invoice_items");
                 h.execute("truncate table recurring_invoice_items");
-                //h.execute("truncate table tag_definitions");
-                //h.execute("truncate table tags");
-                //h.execute("truncate table custom_fields");
-                //h.execute("truncate table invoice_payments");
-                //h.execute("truncate table payment_attempts");
-                //h.execute("truncate table payments");
 
                 return null;
             }
diff --git a/invoice/src/test/java/com/ning/billing/invoice/dao/InvoiceDaoTests.java b/invoice/src/test/java/com/ning/billing/invoice/dao/InvoiceDaoTests.java
index 85d1c16..bb8313a 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/dao/InvoiceDaoTests.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/dao/InvoiceDaoTests.java
@@ -53,12 +53,11 @@ import com.ning.billing.invoice.model.BillingEventSet;
 import com.ning.billing.invoice.model.DefaultInvoice;
 import com.ning.billing.invoice.model.DefaultInvoicePayment;
 import com.ning.billing.invoice.model.RecurringInvoiceItem;
-import com.ning.billing.junction.plumbing.billing.DefaultBillingEvent;
 import com.ning.billing.mock.BrainDeadProxyFactory;
 import com.ning.billing.mock.BrainDeadProxyFactory.ZombieControl;
 import com.ning.billing.util.tag.ControlTagType;
 
-@Test(groups = {"invoicing", "invoicing-invoiceDao"})
+@Test(groups = {"slow", "invoicing", "invoicing-invoiceDao"})
 public class InvoiceDaoTests extends InvoiceDaoTestBase {
     @Test
     public void testCreationAndRetrievalByAccount() {
@@ -407,11 +406,10 @@ public class InvoiceDaoTests extends InvoiceDaoTestBase {
         MockPlanPhase phase1 = new MockPlanPhase(recurringPrice, null, BillingPeriod.MONTHLY, PhaseType.TRIAL);
         MockPlan plan1 = new MockPlan(phase1);
 
-        Subscription subscription = BrainDeadProxyFactory.createBrainDeadProxyFor(Subscription.class);
-        ((ZombieControl) subscription).addResult("getId", UUID.randomUUID());
+        Subscription subscription = getZombieSubscription();
 
         DateTime effectiveDate1 = new DateTime(2011, 2, 1, 0, 0, 0, 0);
-        BillingEvent event1 = new DefaultBillingEvent(null, subscription, effectiveDate1, plan1, phase1, null,
+        BillingEvent event1 = createMockBillingEvent(null, subscription, effectiveDate1, plan1, phase1, null,
                 recurringPrice.getPrice(currency), currency, BillingPeriod.MONTHLY, 1, BillingModeType.IN_ADVANCE,
                 "testEvent1", 1L, SubscriptionTransitionType.CREATE);
 
@@ -429,7 +427,7 @@ public class InvoiceDaoTests extends InvoiceDaoTestBase {
         MockPlan plan2 = new MockPlan(phase2);
 
         DateTime effectiveDate2 = new DateTime(2011, 2, 15, 0, 0, 0, 0);
-        BillingEvent event2 = new DefaultBillingEvent(null, subscription, effectiveDate2, plan2, phase2, null,
+        BillingEvent event2 = createMockBillingEvent(null, subscription, effectiveDate2, plan2, phase2, null,
                 recurringPrice2.getPrice(currency), currency, BillingPeriod.MONTHLY, 1, BillingModeType.IN_ADVANCE,
                 "testEvent2", 2L, SubscriptionTransitionType.CREATE);
         events.add(event2);
@@ -458,11 +456,10 @@ public class InvoiceDaoTests extends InvoiceDaoTestBase {
         MockPlanPhase phase = new MockPlanPhase(recurringPrice, null);
         MockPlan plan = new MockPlan(phase);
 
-        Subscription subscription = BrainDeadProxyFactory.createBrainDeadProxyFor(Subscription.class);
-        ((ZombieControl) subscription).addResult("getId", UUID.randomUUID());
+        Subscription subscription = getZombieSubscription();
         DateTime effectiveDate = buildDateTime(2011, 1, 1);
 
-        BillingEvent event = new DefaultBillingEvent(null, subscription, effectiveDate, plan, phase, null,
+        BillingEvent event = createMockBillingEvent(null, subscription, effectiveDate, plan, phase, null,
                 recurringPrice.getPrice(currency), currency, BillingPeriod.MONTHLY, 15, BillingModeType.IN_ADVANCE,
                 "testEvent", 1L, SubscriptionTransitionType.CREATE);
         BillingEventSet events = new BillingEventSet();
@@ -476,6 +473,13 @@ public class InvoiceDaoTests extends InvoiceDaoTestBase {
         assertEquals(invoice.getTotalAmount().compareTo(ZERO), 0);
     }
 
+    private Subscription getZombieSubscription() {
+        Subscription subscription = BrainDeadProxyFactory.createBrainDeadProxyFor(Subscription.class);
+        ((ZombieControl) subscription).addResult("getId", UUID.randomUUID());
+        ((ZombieControl) subscription).addResult("getBundleId", UUID.randomUUID());
+        return subscription;
+    }
+
     @Test
     public void testInvoiceForFreeTrialWithRecurringDiscount() throws InvoiceApiException, CatalogApiException {
         Currency currency = Currency.USD;
@@ -491,11 +495,10 @@ public class InvoiceDaoTests extends InvoiceDaoTestBase {
 
         MockPlan plan = new MockPlan();
 
-        Subscription subscription = BrainDeadProxyFactory.createBrainDeadProxyFor(Subscription.class);
-        ((ZombieControl) subscription).addResult("getId", UUID.randomUUID());
+        Subscription subscription = getZombieSubscription();
         DateTime effectiveDate1 = buildDateTime(2011, 1, 1);
 
-        BillingEvent event1 = new DefaultBillingEvent(null, subscription, effectiveDate1, plan, phase1, fixedPrice.getPrice(currency),
+        BillingEvent event1 = createMockBillingEvent(null, subscription, effectiveDate1, plan, phase1, fixedPrice.getPrice(currency),
                 null, currency, BillingPeriod.MONTHLY, 1, BillingModeType.IN_ADVANCE,
                 "testEvent1", 1L, SubscriptionTransitionType.CREATE);
         BillingEventSet events = new BillingEventSet();
@@ -511,7 +514,7 @@ public class InvoiceDaoTests extends InvoiceDaoTestBase {
         invoiceList.add(invoice1);
 
         DateTime effectiveDate2 = effectiveDate1.plusDays(30);
-        BillingEvent event2 = new DefaultBillingEvent(null, subscription, effectiveDate2, plan, phase2, null,
+        BillingEvent event2 = createMockBillingEvent(null, subscription, effectiveDate2, plan, phase2, null,
                 recurringPrice.getPrice(currency), currency, BillingPeriod.MONTHLY, 31, BillingModeType.IN_ADVANCE,
                 "testEvent2", 2L, SubscriptionTransitionType.CHANGE);
         events.add(event2);
@@ -551,11 +554,10 @@ public class InvoiceDaoTests extends InvoiceDaoTestBase {
 
         MockPlan plan = new MockPlan();
 
-        Subscription subscription = BrainDeadProxyFactory.createBrainDeadProxyFor(Subscription.class);
-        ((ZombieControl) subscription).addResult("getId", UUID.randomUUID());
+        Subscription subscription = getZombieSubscription();
         DateTime effectiveDate1 = buildDateTime(2011, 1, 1);
 
-        BillingEvent event1 = new DefaultBillingEvent(null, subscription, effectiveDate1, plan, phase1,
+        BillingEvent event1 = createMockBillingEvent(null, subscription, effectiveDate1, plan, phase1,
                 fixedPrice.getPrice(currency), null, currency,
                 BillingPeriod.MONTHLY, 1, BillingModeType.IN_ADVANCE,
                 "testEvent1", 1L, SubscriptionTransitionType.CREATE);
@@ -563,7 +565,7 @@ public class InvoiceDaoTests extends InvoiceDaoTestBase {
         events.add(event1);
 
         DateTime effectiveDate2 = effectiveDate1.plusDays(30);
-        BillingEvent event2 = new DefaultBillingEvent(null, subscription, effectiveDate2, plan, phase2, null,
+        BillingEvent event2 = createMockBillingEvent(null, subscription, effectiveDate2, plan, phase2, null,
                 recurringPrice.getPrice(currency), currency, BillingPeriod.MONTHLY, 31, BillingModeType.IN_ADVANCE,
                 "testEvent2", 2L, SubscriptionTransitionType.CHANGE);
         events.add(event2);
@@ -587,8 +589,7 @@ public class InvoiceDaoTests extends InvoiceDaoTestBase {
         DateTime targetDate1 = DateTime.now().plusMonths(1);
         DateTime targetDate2 = DateTime.now().plusMonths(2);
 
-        Subscription subscription = BrainDeadProxyFactory.createBrainDeadProxyFor(Subscription.class);
-        ((ZombieControl) subscription).addResult("getId", UUID.randomUUID());
+        Subscription subscription = getZombieSubscription();
 
         Plan plan = BrainDeadProxyFactory.createBrainDeadProxyFor(Plan.class);
         ((ZombieControl) plan).addResult("getName", "plan");
@@ -602,10 +603,10 @@ public class InvoiceDaoTests extends InvoiceDaoTestBase {
         BillingEventSet events = new BillingEventSet();
         List<Invoice> invoices = new ArrayList<Invoice>();
 
-        BillingEvent event1 = new DefaultBillingEvent(null, subscription, targetDate1, plan, phase1, null,
-                                                      TEN, currency,
-                                                      BillingPeriod.MONTHLY, 31, BillingModeType.IN_ADVANCE,
-                                                      "testEvent1", 1L, SubscriptionTransitionType.CHANGE);
+        BillingEvent event1 = createMockBillingEvent(null, subscription, targetDate1, plan, phase1, null,
+                TEN, currency,
+                BillingPeriod.MONTHLY, 31, BillingModeType.IN_ADVANCE,
+                "testEvent1", 1L, SubscriptionTransitionType.CHANGE);
         events.add(event1);
 
         Invoice invoice1 = generator.generateInvoice(UUID.randomUUID(), events, invoices, targetDate1, Currency.USD);
@@ -614,10 +615,10 @@ public class InvoiceDaoTests extends InvoiceDaoTestBase {
         invoice1 = invoiceDao.getById(invoice1.getId());
         assertNotNull(invoice1.getInvoiceNumber());
 
-        BillingEvent event2 = new DefaultBillingEvent(null, subscription, targetDate1, plan, phase2, null,
-                                                      TWENTY, currency,
-                                                      BillingPeriod.MONTHLY, 31, BillingModeType.IN_ADVANCE,
-                                                      "testEvent2", 2L, SubscriptionTransitionType.CHANGE);
+        BillingEvent event2 = createMockBillingEvent(null, subscription, targetDate1, plan, phase2, null,
+                TWENTY, currency,
+                BillingPeriod.MONTHLY, 31, BillingModeType.IN_ADVANCE,
+                "testEvent2", 2L, SubscriptionTransitionType.CHANGE);
         events.add(event2);
         Invoice invoice2 = generator.generateInvoice(UUID.randomUUID(), events, invoices, targetDate2, Currency.USD);
         invoiceDao.create(invoice2, context);
@@ -627,8 +628,7 @@ public class InvoiceDaoTests extends InvoiceDaoTestBase {
 
     @Test
     public void testAddingWrittenOffTag() throws InvoiceApiException {
-        Subscription subscription = BrainDeadProxyFactory.createBrainDeadProxyFor(Subscription.class);
-        ((ZombieControl) subscription).addResult("getId", UUID.randomUUID());
+        Subscription subscription = getZombieSubscription();
 
         Plan plan = BrainDeadProxyFactory.createBrainDeadProxyFor(Plan.class);
         ((ZombieControl) plan).addResult("getName", "plan");
@@ -640,10 +640,10 @@ public class InvoiceDaoTests extends InvoiceDaoTestBase {
         Currency currency = Currency.USD;
 
         // create pseudo-random invoice
-        BillingEvent event1 = new DefaultBillingEvent(null, subscription, targetDate1, plan, phase1, null,
-                                                      TEN, currency,
-                                                      BillingPeriod.MONTHLY, 31, BillingModeType.IN_ADVANCE,
-                                                      "testEvent1", 1L, SubscriptionTransitionType.CHANGE);
+        BillingEvent event1 = createMockBillingEvent(null, subscription, targetDate1, plan, phase1, null,
+                TEN, currency,
+                BillingPeriod.MONTHLY, 31, BillingModeType.IN_ADVANCE,
+                "testEvent1", 1L, SubscriptionTransitionType.CHANGE);
         BillingEventSet events = new BillingEventSet();
         events.add(event1);
 
@@ -653,13 +653,12 @@ public class InvoiceDaoTests extends InvoiceDaoTestBase {
         invoiceDao.addControlTag(ControlTagType.WRITTEN_OFF, invoice.getId(), context);
 
         Invoice savedInvoice = invoiceDao.getById(invoice.getId());
-        assertTrue(savedInvoice.hasTag(ControlTagType.WRITTEN_OFF.toString()));
+        assertTrue(savedInvoice.hasTag(ControlTagType.WRITTEN_OFF));
     }
 
     @Test
     public void testRemoveWrittenOffTag() throws InvoiceApiException {
-        Subscription subscription = BrainDeadProxyFactory.createBrainDeadProxyFor(Subscription.class);
-        ((ZombieControl) subscription).addResult("getId", UUID.randomUUID());
+        Subscription subscription = getZombieSubscription();
 
         Plan plan = BrainDeadProxyFactory.createBrainDeadProxyFor(Plan.class);
         ((ZombieControl) plan).addResult("getName", "plan");
@@ -671,10 +670,10 @@ public class InvoiceDaoTests extends InvoiceDaoTestBase {
         Currency currency = Currency.USD;
 
         // create pseudo-random invoice
-        BillingEvent event1 = new DefaultBillingEvent(null, subscription, targetDate1, plan, phase1, null,
-                                                      TEN, currency,
-                                                      BillingPeriod.MONTHLY, 31, BillingModeType.IN_ADVANCE,
-                                                      "testEvent1", 1L, SubscriptionTransitionType.CHANGE);
+        BillingEvent event1 = createMockBillingEvent(null, subscription, targetDate1, plan, phase1, null,
+                TEN, currency,
+                BillingPeriod.MONTHLY, 31, BillingModeType.IN_ADVANCE,
+                "testEvent1", 1L, SubscriptionTransitionType.CHANGE);
         BillingEventSet events = new BillingEventSet();
         events.add(event1);
 
@@ -684,10 +683,10 @@ public class InvoiceDaoTests extends InvoiceDaoTestBase {
         invoiceDao.addControlTag(ControlTagType.WRITTEN_OFF, invoice.getId(), context);
 
         Invoice savedInvoice = invoiceDao.getById(invoice.getId());
-        assertTrue(savedInvoice.hasTag(ControlTagType.WRITTEN_OFF.toString()));
+        assertTrue(savedInvoice.hasTag(ControlTagType.WRITTEN_OFF));
 
         invoiceDao.removeControlTag(ControlTagType.WRITTEN_OFF, invoice.getId(), context);
         savedInvoice = invoiceDao.getById(invoice.getId());
-        assertFalse(savedInvoice.hasTag(ControlTagType.WRITTEN_OFF.toString()));
+        assertFalse(savedInvoice.hasTag(ControlTagType.WRITTEN_OFF));
     }
 }
diff --git a/invoice/src/test/java/com/ning/billing/invoice/glue/InvoiceModuleWithEmbeddedDb.java b/invoice/src/test/java/com/ning/billing/invoice/glue/InvoiceModuleWithEmbeddedDb.java
index 2d46604..5d6f36f 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/glue/InvoiceModuleWithEmbeddedDb.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/glue/InvoiceModuleWithEmbeddedDb.java
@@ -27,6 +27,7 @@ import com.ning.billing.account.api.AccountUserApi;
 import com.ning.billing.catalog.glue.CatalogModule;
 import com.ning.billing.dbi.MysqlTestingHelper;
 import com.ning.billing.entitlement.glue.DefaultEntitlementModule;
+import com.ning.billing.invoice.api.InvoiceNotifier;
 import com.ning.billing.invoice.api.test.DefaultInvoiceTestApi;
 import com.ning.billing.invoice.api.test.InvoiceTestApi;
 import com.ning.billing.invoice.dao.InvoicePaymentSqlDao;
@@ -35,8 +36,10 @@ import com.ning.billing.invoice.notification.MockNextBillingDateNotifier;
 import com.ning.billing.invoice.notification.MockNextBillingDatePoster;
 import com.ning.billing.invoice.notification.NextBillingDateNotifier;
 import com.ning.billing.invoice.notification.NextBillingDatePoster;
+import com.ning.billing.invoice.notification.NullInvoiceNotifier;
 import com.ning.billing.junction.api.BillingApi;
 import com.ning.billing.mock.BrainDeadProxyFactory;
+import com.ning.billing.mock.BrainDeadProxyFactory.ZombieControl;
 import com.ning.billing.util.callcontext.CallContextFactory;
 import com.ning.billing.util.callcontext.DefaultCallContextFactory;
 import com.ning.billing.util.clock.Clock;
@@ -81,9 +84,10 @@ public class InvoiceModuleWithEmbeddedDb extends DefaultInvoiceModule {
     }
 
     @Override
-    protected void installNotifier() {
+    protected void installNotifiers() {
         bind(NextBillingDateNotifier.class).to(MockNextBillingDateNotifier.class).asEagerSingleton();
         bind(NextBillingDatePoster.class).to(MockNextBillingDatePoster.class).asEagerSingleton();
+        bind(InvoiceNotifier.class).to(NullInvoiceNotifier.class).asEagerSingleton();
     }
 
     @Override
@@ -95,13 +99,17 @@ public class InvoiceModuleWithEmbeddedDb extends DefaultInvoiceModule {
 
         bind(Clock.class).to(DefaultClock.class).asEagerSingleton();
         bind(CallContextFactory.class).to(DefaultCallContextFactory.class).asEagerSingleton();
-                install(new FieldStoreModule());
+        install(new FieldStoreModule());
         install(new TagStoreModule());
 
         installNotificationQueue();
 //      install(new AccountModule());
         bind(AccountUserApi.class).toInstance(BrainDeadProxyFactory.createBrainDeadProxyFor(AccountUserApi.class));
-        bind(BillingApi.class).toInstance(BrainDeadProxyFactory.createBrainDeadProxyFor(BillingApi.class));
+
+        BillingApi billingApi = BrainDeadProxyFactory.createBrainDeadProxyFor(BillingApi.class);
+        ((ZombieControl) billingApi).addResult("setChargedThroughDateFromTransaction", BrainDeadProxyFactory.ZOMBIE_VOID);
+        bind(BillingApi.class).toInstance(billingApi);
+
         install(new CatalogModule());
         install(new DefaultEntitlementModule());
         install(new GlobalLockerModule());
diff --git a/invoice/src/test/java/com/ning/billing/invoice/glue/InvoiceModuleWithMocks.java b/invoice/src/test/java/com/ning/billing/invoice/glue/InvoiceModuleWithMocks.java
index 741fa5a..bd43d0a 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/glue/InvoiceModuleWithMocks.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/glue/InvoiceModuleWithMocks.java
@@ -16,33 +16,31 @@
 
 package com.ning.billing.invoice.glue;
 
+import com.ning.billing.invoice.api.InvoiceNotifier;
 import com.ning.billing.invoice.dao.InvoiceDao;
 import com.ning.billing.invoice.dao.MockInvoiceDao;
+import com.ning.billing.invoice.notification.NullInvoiceNotifier;
 import com.ning.billing.util.globallocker.GlobalLocker;
 import com.ning.billing.util.globallocker.MockGlobalLocker;
 import com.ning.billing.util.glue.FieldStoreModule;
 
 
 public class InvoiceModuleWithMocks extends DefaultInvoiceModule {
-    @Override
+    @Override 
     protected void installInvoiceDao() {
         bind(MockInvoiceDao.class).asEagerSingleton();
         bind(InvoiceDao.class).to(MockInvoiceDao.class);
         bind(GlobalLocker.class).to(MockGlobalLocker.class).asEagerSingleton();
     }
 
-    protected void installGlobalLocker() {
-        bind(GlobalLocker.class).to(MockGlobalLocker.class).asEagerSingleton();
-    }
-
     @Override
     protected void installInvoiceListener() {
 
     }
 
     @Override
-    protected void installNotifier() {
-
+    protected void installNotifiers() {
+        bind(InvoiceNotifier.class).to(NullInvoiceNotifier.class).asEagerSingleton();
     }
 
     @Override
@@ -60,6 +58,5 @@ public class InvoiceModuleWithMocks extends DefaultInvoiceModule {
         super.configure();
 
         install(new FieldStoreModule());
-        //install(new TagStoreModule());
     }
 }
diff --git a/invoice/src/test/java/com/ning/billing/invoice/HtmlInvoiceGeneratorTest.java b/invoice/src/test/java/com/ning/billing/invoice/HtmlInvoiceGeneratorTest.java
new file mode 100644
index 0000000..95f39c9
--- /dev/null
+++ b/invoice/src/test/java/com/ning/billing/invoice/HtmlInvoiceGeneratorTest.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you 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.ning.billing.invoice;
+
+import com.ning.billing.account.api.Account;
+import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.invoice.api.Invoice;
+import com.ning.billing.invoice.api.InvoiceItem;
+import com.ning.billing.invoice.api.formatters.InvoiceFormatterFactory;
+import com.ning.billing.invoice.template.HtmlInvoiceGenerator;
+import com.ning.billing.invoice.template.formatters.DefaultInvoiceFormatterFactory;
+import com.ning.billing.mock.BrainDeadProxyFactory;
+import com.ning.billing.mock.BrainDeadProxyFactory.ZombieControl;
+import com.ning.billing.util.email.templates.MustacheTemplateEngine;
+import com.ning.billing.util.email.templates.TemplateEngine;
+import com.ning.billing.util.template.translation.TranslatorConfig;
+import org.joda.time.DateTime;
+import org.skife.config.ConfigurationObjectFactory;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+
+import static org.testng.Assert.assertNotNull;
+
+@Test(groups = {"fast", "email"})
+public class HtmlInvoiceGeneratorTest {
+    private HtmlInvoiceGenerator g;
+    private final static String TEST_TEMPLATE_NAME = "HtmlInvoiceTemplate";
+
+    @BeforeClass
+    public void setup() {
+        TranslatorConfig config = new ConfigurationObjectFactory(System.getProperties()).build(TranslatorConfig.class);
+        TemplateEngine templateEngine = new MustacheTemplateEngine();
+        InvoiceFormatterFactory factory = new DefaultInvoiceFormatterFactory();
+        g = new HtmlInvoiceGenerator(factory, templateEngine, config);
+    }
+
+    @Test
+    public void testGenerateInvoice() throws Exception {
+        String output = g.generateInvoice(createAccount(), createInvoice(), TEST_TEMPLATE_NAME);
+        assertNotNull(output);
+        System.out.print(output);
+    }
+
+    private Account createAccount() {
+        Account account = BrainDeadProxyFactory.createBrainDeadProxyFor(Account.class);
+        ZombieControl zombieControl = (ZombieControl) account;
+        zombieControl.addResult("getExternalKey", "1234abcd");
+        zombieControl.addResult("getName", "Jim Smith");
+        zombieControl.addResult("getFirstNameLength", 3);
+        zombieControl.addResult("getEmail", "jim.smith@mail.com");
+        zombieControl.addResult("getLocale", Locale.US.toString());
+        zombieControl.addResult("getAddress1", "123 Some Street");
+        zombieControl.addResult("getAddress2", "Apt 456");
+        zombieControl.addResult("getCity", "Some City");
+        zombieControl.addResult("getStateOrProvince", "Some State");
+        zombieControl.addResult("getPostalCode", "12345-6789");
+        zombieControl.addResult("getCountry", "USA");
+        zombieControl.addResult("getPhone", "123-456-7890");
+
+        return account;
+    }
+
+    private Invoice createInvoice() {
+        DateTime startDate = new DateTime().minusMonths(1);
+        DateTime endDate = new DateTime();
+
+        BigDecimal price1 = new BigDecimal("29.95");
+        BigDecimal price2 = new BigDecimal("59.95");
+        Invoice dummyInvoice = BrainDeadProxyFactory.createBrainDeadProxyFor(Invoice.class);
+        ZombieControl zombie = (ZombieControl) dummyInvoice;
+        zombie.addResult("getInvoiceDate", startDate);
+        zombie.addResult("getInvoiceNumber", 42);
+        zombie.addResult("getCurrency", Currency.USD);
+        zombie.addResult("getTotalAmount", price1.add(price2));
+        zombie.addResult("getAmountPaid", BigDecimal.ZERO);
+        zombie.addResult("getBalance", price1.add(price2));
+
+        List<InvoiceItem> items = new ArrayList<InvoiceItem>();
+        items.add(createInvoiceItem(price1, "Domain 1", startDate, endDate, "ning-plus"));
+        items.add(createInvoiceItem(price2, "Domain 2", startDate, endDate, "ning-pro"));
+        zombie.addResult("getInvoiceItems", items);
+
+        return dummyInvoice;
+    }
+
+    private InvoiceItem createInvoiceItem(BigDecimal amount, String networkName, DateTime startDate, DateTime endDate, String planName) {
+        InvoiceItem item = BrainDeadProxyFactory.createBrainDeadProxyFor(InvoiceItem.class);
+        ZombieControl zombie = (ZombieControl) item;
+        zombie.addResult("getAmount", amount);
+        zombie.addResult("getStartDate", startDate);
+        zombie.addResult("getEndDate", endDate);
+        zombie.addResult("getPlanName", planName);
+        zombie.addResult("getDescription", networkName);
+
+
+        return item;
+    }
+}
diff --git a/invoice/src/test/java/com/ning/billing/invoice/MockModule.java b/invoice/src/test/java/com/ning/billing/invoice/MockModule.java
index e57ea87..aa4abf6 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/MockModule.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/MockModule.java
@@ -20,29 +20,26 @@ import org.skife.config.ConfigurationObjectFactory;
 import org.skife.jdbi.v2.IDBI;
 
 import com.google.inject.AbstractModule;
-import com.ning.billing.account.api.AccountUserApi;
 import com.ning.billing.catalog.glue.CatalogModule;
 import com.ning.billing.dbi.DBIProvider;
 import com.ning.billing.dbi.DbiConfig;
 import com.ning.billing.dbi.MysqlTestingHelper;
-import com.ning.billing.entitlement.glue.DefaultEntitlementModule;
+import com.ning.billing.invoice.api.formatters.InvoiceFormatterFactory;
 import com.ning.billing.invoice.glue.DefaultInvoiceModule;
-import com.ning.billing.mock.BrainDeadProxyFactory;
+import com.ning.billing.invoice.template.formatters.DefaultInvoiceFormatterFactory;
 import com.ning.billing.mock.glue.MockJunctionModule;
 import com.ning.billing.util.callcontext.CallContextFactory;
 import com.ning.billing.util.callcontext.DefaultCallContextFactory;
 import com.ning.billing.util.clock.Clock;
 import com.ning.billing.util.clock.ClockMock;
+import com.ning.billing.util.email.EmailModule;
 import com.ning.billing.util.glue.BusModule;
 import com.ning.billing.util.glue.FieldStoreModule;
 import com.ning.billing.util.glue.GlobalLockerModule;
 import com.ning.billing.util.glue.NotificationQueueModule;
 import com.ning.billing.util.glue.TagStoreModule;
 
-
 public class MockModule extends AbstractModule {
-    public static final String PLUGIN_NAME = "yoyo";
-
     @Override
     protected void configure() {
         bind(Clock.class).to(ClockMock.class).asEagerSingleton();
@@ -62,21 +59,18 @@ public class MockModule extends AbstractModule {
             bind(IDBI.class).toInstance(dbi);
         }
 
+        bind(InvoiceFormatterFactory.class).to(DefaultInvoiceFormatterFactory.class).asEagerSingleton();
+
+        install(new EmailModule());
         install(new GlobalLockerModule());
         install(new NotificationQueueModule());
-        installEntitlementModule();
         install(new CatalogModule());
         install(new BusModule());
         installInvoiceModule();
         install(new MockJunctionModule());
     }
-    
-    protected void installEntitlementModule() {
-        install(new DefaultEntitlementModule());
-    }
 
     protected void installInvoiceModule() {
     	install(new DefaultInvoiceModule());
     }
-
 }
diff --git a/invoice/src/test/java/com/ning/billing/invoice/notification/TestNextBillingDateNotifier.java b/invoice/src/test/java/com/ning/billing/invoice/notification/TestNextBillingDateNotifier.java
index 6419bcf..4d6b6fe 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/notification/TestNextBillingDateNotifier.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/notification/TestNextBillingDateNotifier.java
@@ -63,11 +63,12 @@ import com.ning.billing.entitlement.engine.dao.RepairEntitlementDao;
 import com.ning.billing.entitlement.glue.DefaultEntitlementModule;
 import com.ning.billing.invoice.InvoiceDispatcher;
 import com.ning.billing.invoice.InvoiceListener;
+import com.ning.billing.invoice.api.InvoiceNotifier;
 import com.ning.billing.invoice.dao.DefaultInvoiceDao;
 import com.ning.billing.invoice.dao.InvoiceDao;
 import com.ning.billing.invoice.model.DefaultInvoiceGenerator;
 import com.ning.billing.invoice.model.InvoiceGenerator;
-import com.ning.billing.lifecycle.KillbillService.ServiceException;
+import com.ning.billing.lifecycle.KillbillService;
 import com.ning.billing.mock.BrainDeadProxyFactory;
 import com.ning.billing.mock.BrainDeadProxyFactory.ZombieControl;
 import com.ning.billing.mock.glue.MockJunctionModule;
@@ -88,7 +89,6 @@ import com.ning.billing.util.notificationq.dao.NotificationSqlDao;
 import com.ning.billing.util.tag.dao.AuditedTagDao;
 import com.ning.billing.util.tag.dao.TagDao;
 
-
 public class TestNextBillingDateNotifier {
 	private Clock clock;
 	private DefaultNextBillingDateNotifier notifier;
@@ -130,7 +130,7 @@ public class TestNextBillingDateNotifier {
 	}
 
 	@BeforeClass(groups={"slow"})
-	public void setup() throws ServiceException, IOException, ClassNotFoundException, SQLException {
+	public void setup() throws KillbillService.ServiceException, IOException, ClassNotFoundException, SQLException {
 		//TestApiBase.loadSystemPropertiesFromClasspath("/entitlement.properties");
         final Injector g = Guice.createInjector(Stage.PRODUCTION,  new AbstractModule() {
 			
@@ -139,6 +139,7 @@ public class TestNextBillingDateNotifier {
                 bind(CallContextFactory.class).to(DefaultCallContextFactory.class).asEagerSingleton();
                 bind(Bus.class).to(InMemoryBus.class).asEagerSingleton();
                 bind(NotificationQueueService.class).to(DefaultNotificationQueueService.class).asEagerSingleton();
+                bind(InvoiceNotifier.class).to(NullInvoiceNotifier.class).asEagerSingleton();
                 final InvoiceConfig invoiceConfig = new ConfigurationObjectFactory(System.getProperties()).build(InvoiceConfig.class);
                 bind(InvoiceConfig.class).toInstance(invoiceConfig);
                 final CatalogConfig catalogConfig = new ConfigurationObjectFactory(System.getProperties()).build(CatalogConfig.class);
@@ -196,11 +197,9 @@ public class TestNextBillingDateNotifier {
 	private void startMysql() throws IOException, ClassNotFoundException, SQLException {
 		final String ddl = IOUtils.toString(NotificationSqlDao.class.getResourceAsStream("/com/ning/billing/util/ddl.sql"));
 		final String testDdl = IOUtils.toString(NotificationSqlDao.class.getResourceAsStream("/com/ning/billing/util/ddl_test.sql"));
-		final String entitlementDdl = IOUtils.toString(NotificationSqlDao.class.getResourceAsStream("/com/ning/billing/entitlement/ddl.sql"));
 		helper.startMysql();
 		helper.initDb(ddl);
 		helper.initDb(testDdl);
-        helper.initDb(entitlementDdl);
 	}
 
 
diff --git a/invoice/src/test/java/com/ning/billing/invoice/TestInvoiceDispatcher.java b/invoice/src/test/java/com/ning/billing/invoice/TestInvoiceDispatcher.java
index 68a024e..e591dfa 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/TestInvoiceDispatcher.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/TestInvoiceDispatcher.java
@@ -23,6 +23,9 @@ import java.util.SortedSet;
 import java.util.TreeSet;
 import java.util.UUID;
 
+import com.ning.billing.invoice.api.InvoiceNotifier;
+import com.ning.billing.invoice.notification.NullInvoiceNotifier;
+import com.ning.billing.invoice.tests.InvoicingTestBase;
 import org.apache.commons.io.IOUtils;
 import org.joda.time.DateTime;
 import org.slf4j.Logger;
@@ -53,7 +56,6 @@ import com.ning.billing.invoice.dao.InvoiceDao;
 import com.ning.billing.invoice.model.InvoiceGenerator;
 import com.ning.billing.invoice.notification.NextBillingDateNotifier;
 import com.ning.billing.junction.api.BillingApi;
-import com.ning.billing.junction.plumbing.billing.DefaultBillingEvent;
 import com.ning.billing.mock.BrainDeadProxyFactory;
 import com.ning.billing.mock.BrainDeadProxyFactory.ZombieControl;
 import com.ning.billing.mock.glue.MockJunctionModule;
@@ -68,7 +70,7 @@ import com.ning.billing.util.globallocker.GlobalLocker;
 
 @Test(groups = "slow")
 @Guice(modules = {MockModule.class})
-public class TestInvoiceDispatcher {
+public class TestInvoiceDispatcher extends InvoicingTestBase {
 	private Logger log = LoggerFactory.getLogger(TestInvoiceDispatcher.class);
 
 	@Inject
@@ -100,13 +102,11 @@ public class TestInvoiceDispatcher {
     @BeforeSuite(groups = "slow")
     public void setup() throws IOException
     {
-		final String entitlementDdl = IOUtils.toString(TestInvoiceDispatcher.class.getResourceAsStream("/com/ning/billing/entitlement/ddl.sql"));
 		final String invoiceDdl = IOUtils.toString(TestInvoiceDispatcher.class.getResourceAsStream("/com/ning/billing/invoice/ddl.sql"));
 		final String utilDdl = IOUtils.toString(TestInvoiceDispatcher.class.getResourceAsStream("/com/ning/billing/util/ddl.sql"));
 
 		helper.startMysql();
 
-		helper.initDb(entitlementDdl);
 		helper.initDb(invoiceDdl);
 		helper.initDb(utilDdl);
 		notifier.initialize();
@@ -140,6 +140,7 @@ public class TestInvoiceDispatcher {
 		((ZombieControl)accountUserApi).addResult("getAccountById", account);
 		((ZombieControl)account).addResult("getCurrency", Currency.USD);
 		((ZombieControl)account).addResult("getId", accountId);
+        ((ZombieControl)account).addResult(("isNotifiedForInvoices"), true);
 
 		Subscription subscription =  BrainDeadProxyFactory.createBrainDeadProxyFor(Subscription.class);
         ((ZombieControl)subscription).addResult("getId", subscriptionId);
@@ -150,16 +151,18 @@ public class TestInvoiceDispatcher {
 		DateTime effectiveDate = new DateTime().minusDays(1);
 		Currency currency = Currency.USD;
 		BigDecimal fixedPrice = null;
-		events.add(new DefaultBillingEvent(account, subscription, effectiveDate,plan, planPhase,
-				fixedPrice, BigDecimal.ONE, currency, BillingPeriod.MONTHLY, 1,
-				BillingModeType.IN_ADVANCE, "", 1L, SubscriptionTransitionType.CREATE));
+		events.add(createMockBillingEvent(account, subscription, effectiveDate, plan, planPhase,
+                fixedPrice, BigDecimal.ONE, currency, BillingPeriod.MONTHLY, 1,
+                BillingModeType.IN_ADVANCE, "", 1L, SubscriptionTransitionType.CREATE));
 
 		BillingApi entitlementBillingApi = BrainDeadProxyFactory.createBrainDeadProxyFor(BillingApi.class);
-		((ZombieControl)entitlementBillingApi).addResult("getBillingEventsForAccountAndUpdateAccountBCD", events);
+		((ZombieControl) entitlementBillingApi).addResult("getBillingEventsForAccountAndUpdateAccountBCD", events);
 
 		DateTime target = new DateTime();
 
-		InvoiceDispatcher dispatcher = new InvoiceDispatcher(generator, accountUserApi, entitlementBillingApi, invoiceDao, locker, busService.getBus(), clock);
+        InvoiceNotifier invoiceNotifier = new NullInvoiceNotifier();
+		InvoiceDispatcher dispatcher = new InvoiceDispatcher(generator, accountUserApi, entitlementBillingApi, invoiceDao,
+                                                             invoiceNotifier, locker, busService.getBus(), clock);
 
 		Invoice invoice = dispatcher.processAccount(accountId, target, true, context);
 		Assert.assertNotNull(invoice);
diff --git a/invoice/src/test/java/com/ning/billing/invoice/tests/DefaultInvoiceGeneratorTests.java b/invoice/src/test/java/com/ning/billing/invoice/tests/DefaultInvoiceGeneratorTests.java
index 1eaa8af..49fca25 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/tests/DefaultInvoiceGeneratorTests.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/tests/DefaultInvoiceGeneratorTests.java
@@ -22,11 +22,14 @@ import static org.testng.Assert.assertNull;
 
 import java.math.BigDecimal;
 import java.util.ArrayList;
+import java.util.Iterator;
 import java.util.List;
 import java.util.UUID;
 
 import javax.annotation.Nullable;
 
+import com.ning.billing.invoice.api.InvoiceItem;
+import com.ning.billing.invoice.model.DefaultInvoicePayment;
 import org.joda.time.DateTime;
 import org.testng.annotations.Test;
 
@@ -44,9 +47,7 @@ import com.ning.billing.config.InvoiceConfig;
 import com.ning.billing.entitlement.api.SubscriptionTransitionType;
 import com.ning.billing.entitlement.api.billing.BillingEvent;
 import com.ning.billing.entitlement.api.billing.BillingModeType;
-import com.ning.billing.entitlement.api.user.DefaultSubscriptionFactory.SubscriptionBuilder;
 import com.ning.billing.entitlement.api.user.Subscription;
-import com.ning.billing.entitlement.api.user.SubscriptionData;
 import com.ning.billing.invoice.api.Invoice;
 import com.ning.billing.invoice.api.InvoiceApiException;
 import com.ning.billing.invoice.model.BillingEventSet;
@@ -54,7 +55,6 @@ import com.ning.billing.invoice.model.DefaultInvoiceGenerator;
 import com.ning.billing.invoice.model.FixedPriceInvoiceItem;
 import com.ning.billing.invoice.model.InvoiceGenerator;
 import com.ning.billing.invoice.model.RecurringInvoiceItem;
-import com.ning.billing.junction.plumbing.billing.DefaultBillingEvent;
 import com.ning.billing.mock.BrainDeadProxyFactory;
 import com.ning.billing.mock.BrainDeadProxyFactory.ZombieControl;
 import com.ning.billing.util.clock.Clock;
@@ -62,38 +62,38 @@ import com.ning.billing.util.clock.DefaultClock;
 
 @Test(groups = {"fast", "invoicing", "invoiceGenerator"})
 public class DefaultInvoiceGeneratorTests extends InvoicingTestBase {
-    private final Clock clock = new DefaultClock();
-    private final InvoiceConfig invoiceConfig = new InvoiceConfig() {
-        @Override
-        public long getDaoClaimTimeMs() {
-            throw new UnsupportedOperationException();
-        }
-
-        @Override
-        public int getDaoMaxReadyEvents() {
-            throw new UnsupportedOperationException();
-        }
-
-        @Override
-        public long getNotificationSleepTimeMs() {
-            throw new UnsupportedOperationException();
-        }
-
-        @Override
-        public int getNumberOfMonthsInFuture() {
-            return 36;
-        }
-
-		@Override
-		public boolean isNotificationProcessingOff() {
-            throw new UnsupportedOperationException();			
-		}
-    };
-
     private final InvoiceGenerator generator;
 
     public DefaultInvoiceGeneratorTests() {
         super();
+
+        Clock clock = new DefaultClock();
+        InvoiceConfig invoiceConfig = new InvoiceConfig() {
+            @Override
+            public long getDaoClaimTimeMs() {
+                throw new UnsupportedOperationException();
+            }
+
+            @Override
+            public int getDaoMaxReadyEvents() {
+                throw new UnsupportedOperationException();
+            }
+
+            @Override
+            public long getNotificationSleepTimeMs() {
+                throw new UnsupportedOperationException();
+            }
+
+            @Override
+            public int getNumberOfMonthsInFuture() {
+                return 36;
+            }
+
+            @Override
+            public boolean isNotificationProcessingOff() {
+                throw new UnsupportedOperationException();
+            }
+        };
         this.generator = new DefaultInvoiceGenerator(clock, invoiceConfig);
     }
 
@@ -119,7 +119,7 @@ public class DefaultInvoiceGeneratorTests extends InvoicingTestBase {
     public void testWithSingleMonthlyEvent() throws InvoiceApiException, CatalogApiException {
         BillingEventSet events = new BillingEventSet();
 
-        Subscription sub = new SubscriptionData(new SubscriptionBuilder().setId(UUID.randomUUID()));
+        Subscription sub = createZombieSubscription();
         DateTime startDate = buildDateTime(2011, 9, 1);
 
         Plan plan = new MockPlan();
@@ -139,11 +139,23 @@ public class DefaultInvoiceGeneratorTests extends InvoicingTestBase {
         assertEquals(invoice.getInvoiceItems().get(0).getSubscriptionId(), sub.getId());
     }
 
+    private Subscription createZombieSubscription() {
+        return createZombieSubscription(UUID.randomUUID());
+    }
+
+    private Subscription createZombieSubscription(UUID subscriptionId) {
+        Subscription sub = BrainDeadProxyFactory.createBrainDeadProxyFor(Subscription.class);
+        ((ZombieControl) sub).addResult("getId", subscriptionId);
+        ((ZombieControl) sub).addResult("getBundleId", UUID.randomUUID());
+
+        return sub;
+    }
+
     @Test
     public void testWithSingleMonthlyEventWithLeadingProRation() throws InvoiceApiException, CatalogApiException {
         BillingEventSet events = new BillingEventSet();
 
-        Subscription sub = new SubscriptionData(new SubscriptionBuilder().setId(UUID.randomUUID()));
+        Subscription sub = createZombieSubscription();
         DateTime startDate = buildDateTime(2011, 9, 1);
 
         Plan plan = new MockPlan();
@@ -177,7 +189,7 @@ public class DefaultInvoiceGeneratorTests extends InvoicingTestBase {
         BigDecimal rate2 = TEN;
         PlanPhase phase2 = createMockMonthlyPlanPhase(rate2);
 
-        Subscription sub = new SubscriptionData(new SubscriptionBuilder().setId(UUID.randomUUID()));
+        Subscription sub = createZombieSubscription();
 
         BillingEvent event1 = createBillingEvent(sub.getId(), buildDateTime(2011, 9, 1), plan1, phase1, 1);
         events.add(event1);
@@ -202,7 +214,7 @@ public class DefaultInvoiceGeneratorTests extends InvoicingTestBase {
         BigDecimal rate1 = FIVE;
         PlanPhase phase1 = createMockMonthlyPlanPhase(rate1);
 
-        Subscription sub = new SubscriptionData(new SubscriptionBuilder().setId(UUID.randomUUID()));
+        Subscription sub = createZombieSubscription();
         BillingEvent event1 = createBillingEvent(sub.getId(), buildDateTime(2011, 9, 1), plan1,phase1, 1);
         events.add(event1);
 
@@ -239,7 +251,7 @@ public class DefaultInvoiceGeneratorTests extends InvoicingTestBase {
         BigDecimal rate1 = FIVE;
         PlanPhase phase1 = createMockMonthlyPlanPhase(rate1);
 
-        Subscription sub = new SubscriptionData(new SubscriptionBuilder().setId(UUID.randomUUID()));
+        Subscription sub = createZombieSubscription();
         BillingEvent event1 = createBillingEvent(sub.getId(), buildDateTime(2011, 9, 1), plan1, phase1, 1);
         events.add(event1);
 
@@ -266,7 +278,7 @@ public class DefaultInvoiceGeneratorTests extends InvoicingTestBase {
     public void testSingleEventWithExistingInvoice() throws InvoiceApiException, CatalogApiException {
         BillingEventSet events = new BillingEventSet();
 
-        Subscription sub = new SubscriptionData(new SubscriptionBuilder().setId(UUID.randomUUID()));
+        Subscription sub = createZombieSubscription();
         DateTime startDate = buildDateTime(2011, 9, 1);
 
         Plan plan1 = new MockPlan();
@@ -480,9 +492,7 @@ public class DefaultInvoiceGeneratorTests extends InvoicingTestBase {
     @Test
     public void testFixedPriceLifeCycle() throws InvoiceApiException {
         UUID accountId = UUID.randomUUID();
-        Subscription subscription = BrainDeadProxyFactory.createBrainDeadProxyFor(Subscription.class);
-        ((ZombieControl) subscription).addResult("getId", UUID.randomUUID());
-        ((ZombieControl) subscription).addResult("getBundleId", UUID.randomUUID());
+        Subscription subscription = createZombieSubscription();
 
         Plan plan = new MockPlan("plan 1");
         MockInternationalPrice zeroPrice = new MockInternationalPrice(new DefaultPrice(ZERO, Currency.USD));
@@ -495,17 +505,17 @@ public class DefaultInvoiceGeneratorTests extends InvoicingTestBase {
 
         BillingEventSet events = new BillingEventSet();
 
-        BillingEvent event1 = new DefaultBillingEvent(null, subscription, new DateTime("2012-01-1T00:00:00.000-08:00"),
-                                                      plan, phase1,
-                                                      ZERO, null, Currency.USD, BillingPeriod.NO_BILLING_PERIOD, 1,
-                                                      BillingModeType.IN_ADVANCE, "Test Event 1", 1L,
-                                                      SubscriptionTransitionType.CREATE);
+        BillingEvent event1 = createMockBillingEvent(null, subscription, new DateTime("2012-01-1T00:00:00.000-08:00"),
+                plan, phase1,
+                ZERO, null, Currency.USD, BillingPeriod.NO_BILLING_PERIOD, 1,
+                BillingModeType.IN_ADVANCE, "Test Event 1", 1L,
+                SubscriptionTransitionType.CREATE);
 
-        BillingEvent event2 = new DefaultBillingEvent(null, subscription, changeDate,
-                                                      plan, phase2,
-                                                      ZERO, null, Currency.USD, BillingPeriod.NO_BILLING_PERIOD, 1,
-                                                      BillingModeType.IN_ADVANCE, "Test Event 2", 2L,
-                                                      SubscriptionTransitionType.PHASE);
+        BillingEvent event2 = createMockBillingEvent(null, subscription, changeDate,
+                plan, phase2,
+                ZERO, null, Currency.USD, BillingPeriod.NO_BILLING_PERIOD, 1,
+                BillingModeType.IN_ADVANCE, "Test Event 2", 2L,
+                SubscriptionTransitionType.PHASE);
 
         events.add(event2);
         events.add(event1);
@@ -688,16 +698,16 @@ public class DefaultInvoiceGeneratorTests extends InvoicingTestBase {
                                  null, BillingPeriod.ANNUAL, phaseType);
     }
 
-    private DefaultBillingEvent createBillingEvent(final UUID subscriptionId, final DateTime startDate,
+    private BillingEvent createBillingEvent(final UUID subscriptionId, final DateTime startDate,
                                                    final Plan plan, final PlanPhase planPhase, final int billCycleDay) throws CatalogApiException {
-        Subscription sub = new SubscriptionData(new SubscriptionBuilder().setId(subscriptionId));
+        Subscription sub = createZombieSubscription(subscriptionId);
         Currency currency = Currency.USD;
 
-        return new DefaultBillingEvent(null, sub, startDate, plan, planPhase,
-                                       planPhase.getFixedPrice() == null ? null : planPhase.getFixedPrice().getPrice(currency),
-                                       planPhase.getRecurringPrice() == null ? null : planPhase.getRecurringPrice().getPrice(currency),
-                                       currency, planPhase.getBillingPeriod(),
-                                       billCycleDay, BillingModeType.IN_ADVANCE, "Test", 1L, SubscriptionTransitionType.CREATE);
+        return createMockBillingEvent(null, sub, startDate, plan, planPhase,
+                planPhase.getFixedPrice() == null ? null : planPhase.getFixedPrice().getPrice(currency),
+                planPhase.getRecurringPrice() == null ? null : planPhase.getRecurringPrice().getPrice(currency),
+                currency, planPhase.getBillingPeriod(),
+                billCycleDay, BillingModeType.IN_ADVANCE, "Test", 1L, SubscriptionTransitionType.CREATE);
     }
 
     private void testInvoiceGeneration(final UUID accountId, final BillingEventSet events, final List<Invoice> existingInvoices,
@@ -712,5 +722,116 @@ public class DefaultInvoiceGeneratorTests extends InvoicingTestBase {
         assertEquals(invoice.getTotalAmount(), expectedAmount);
     }
 
+    @Test(groups = {"fast", "invoicing"})
+    public void testAddOnInvoiceGeneration() throws CatalogApiException, InvoiceApiException {
+        DateTime april25 = new DateTime(2012, 4, 25, 0, 0, 0, 0);
+
+        // create a base plan on April 25th
+        UUID accountId = UUID.randomUUID();
+        Subscription baseSubscription = createZombieSubscription();
+
+        Plan basePlan = new MockPlan("base Plan");
+        MockInternationalPrice price5 = new MockInternationalPrice(new DefaultPrice(FIVE, Currency.USD));
+        MockInternationalPrice price10 = new MockInternationalPrice(new DefaultPrice(TEN, Currency.USD));
+        MockInternationalPrice price20 = new MockInternationalPrice(new DefaultPrice(TWENTY, Currency.USD));
+        PlanPhase basePlanEvergreen = new MockPlanPhase(price10, null, BillingPeriod.MONTHLY, PhaseType.EVERGREEN);
+
+        BillingEventSet events = new BillingEventSet();
+        events.add(createBillingEvent(baseSubscription.getId(), april25, basePlan, basePlanEvergreen, 25));
+
+        // generate invoice
+        Invoice invoice1 = generator.generateInvoice(accountId, events, null, april25, Currency.USD);
+        assertNotNull(invoice1);
+        assertEquals(invoice1.getNumberOfItems(), 1);
+        assertEquals(invoice1.getTotalAmount().compareTo(TEN), 0);
+
+        List<Invoice> invoices = new ArrayList<Invoice>();
+        invoices.add(invoice1);
+
+        // create 2 add ons on April 28th
+        DateTime april28 = new DateTime(2012, 4, 28, 0, 0, 0, 0);
+        Subscription addOnSubscription1 = createZombieSubscription();
+        Plan addOn1Plan = new MockPlan("add on 1");
+        PlanPhase addOn1PlanPhaseEvergreen = new MockPlanPhase(price5, null, BillingPeriod.MONTHLY, PhaseType.EVERGREEN);
+        events.add(createBillingEvent(addOnSubscription1.getId(), april28, addOn1Plan, addOn1PlanPhaseEvergreen, 25));
+
+        Subscription addOnSubscription2 = createZombieSubscription();
+        Plan addOn2Plan = new MockPlan("add on 2");
+        PlanPhase addOn2PlanPhaseEvergreen = new MockPlanPhase(price20, null, BillingPeriod.MONTHLY, PhaseType.EVERGREEN);
+        events.add(createBillingEvent(addOnSubscription2.getId(), april28, addOn2Plan, addOn2PlanPhaseEvergreen, 25));
+
+        // generate invoice
+        Invoice invoice2 = generator.generateInvoice(accountId, events, invoices, april28, Currency.USD);
+        invoices.add(invoice2);
+        assertNotNull(invoice2);
+        assertEquals(invoice2.getNumberOfItems(), 2);
+        assertEquals(invoice2.getTotalAmount().compareTo(TWENTY_FIVE.multiply(new BigDecimal("0.9")).setScale(NUMBER_OF_DECIMALS, ROUNDING_METHOD)), 0);
+
+        // perform a repair (change base plan; remove one add-on)
+        // event stream should include just two plans
+        BillingEventSet newEvents = new BillingEventSet();
+        Plan basePlan2 = new MockPlan("base plan 2");
+        MockInternationalPrice price13 = new MockInternationalPrice(new DefaultPrice(THIRTEEN, Currency.USD));
+        PlanPhase basePlan2Phase = new MockPlanPhase(price13, null, BillingPeriod.MONTHLY, PhaseType.EVERGREEN);
+        newEvents.add(createBillingEvent(baseSubscription.getId(), april25, basePlan2, basePlan2Phase, 25));
+        newEvents.add(createBillingEvent(addOnSubscription1.getId(), april28, addOn1Plan, addOn1PlanPhaseEvergreen, 25));
+
+        // generate invoice
+        DateTime may1 = new DateTime(2012, 5, 1, 0, 0, 0, 0);
+        Invoice invoice3 = generator.generateInvoice(accountId, newEvents, invoices, may1, Currency.USD);
+        assertNotNull(invoice3);
+        assertEquals(invoice3.getNumberOfItems(), 5);
+        // -4.50 -18 - 10 (to correct the previous 2 invoices) + 4.50 + 13
+        assertEquals(invoice3.getTotalAmount().compareTo(FIFTEEN.negate()), 0);
+    }
+
+    @Test(enabled = false)
+    public void testRepairForPaidInvoice() throws CatalogApiException, InvoiceApiException {
+        // create an invoice
+        DateTime april25 = new DateTime(2012, 4, 25, 0, 0, 0, 0);
+
+        // create a base plan on April 25th
+        UUID accountId = UUID.randomUUID();
+        Subscription originalSubscription = createZombieSubscription();
+
+        Plan originalPlan = new MockPlan("original plan");
+        MockInternationalPrice price10 = new MockInternationalPrice(new DefaultPrice(TEN, Currency.USD));
+        PlanPhase originalPlanEvergreen = new MockPlanPhase(price10, null, BillingPeriod.MONTHLY, PhaseType.EVERGREEN);
+
+        BillingEventSet events = new BillingEventSet();
+        events.add(createBillingEvent(originalSubscription.getId(), april25, originalPlan, originalPlanEvergreen, 25));
+
+        Invoice invoice1 = generator.generateInvoice(accountId, events, null, april25, Currency.USD);
+        List<Invoice> invoices = new ArrayList<Invoice>();
+        invoices.add(invoice1);
+
+        // pay the invoice
+        invoice1.addPayment(new DefaultInvoicePayment(UUID.randomUUID(), invoice1.getId(), april25, TEN, Currency.USD));
+        assertEquals(invoice1.getBalance().compareTo(ZERO), 0);
+
+        // change the plan (i.e. repair) on start date
+        events.clear();
+        Subscription newSubscription = createZombieSubscription();
+        Plan newPlan = new MockPlan("new plan");
+        MockInternationalPrice price5 = new MockInternationalPrice(new DefaultPrice(FIVE, Currency.USD));
+        PlanPhase newPlanEvergreen = new MockPlanPhase(price5, null, BillingPeriod.MONTHLY, PhaseType.EVERGREEN);
+        events.add(createBillingEvent(newSubscription.getId(), april25, newPlan, newPlanEvergreen, 25));
+
+        // generate a new invoice
+        Invoice invoice2 = generator.generateInvoice(accountId, events, invoices, april25, Currency.USD);
+        invoices.add(invoice2);
+
+        // move items to the correct invoice (normally, the dao calls will sort that out)
+        generator.distributeItems(invoices);
+
+        // ensure that the original invoice balance is zero
+
+        assertEquals(invoice1.getBalance().compareTo(ZERO), 0);
+
+        // ensure that the account balance is correct
+        assertEquals(invoice2.getBalance().compareTo(FIVE.negate()), 0);
+
+    }
+
     // TODO: Jeff C -- how do we ensure that an annual add-on is properly aligned *at the end* with the base plan?
 }
\ No newline at end of file
diff --git a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/monthly/ProRationTests.java b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/monthly/ProRationTests.java
index 8d564c8..78f06b4 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/monthly/ProRationTests.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/monthly/ProRationTests.java
@@ -215,9 +215,10 @@ public class ProRationTests extends ProRationInAdvanceTestBase {
         DateTime targetDate = buildDateTime(2011, 4, 21);
 
         BigDecimal expectedValue;
-        expectedValue = SEVEN.divide(THIRTY_ONE, NUMBER_OF_DECIMALS, ROUNDING_METHOD);
+        expectedValue = SEVEN.divide(THIRTY_ONE, 2 * NUMBER_OF_DECIMALS, ROUNDING_METHOD);
         expectedValue = expectedValue.add(ONE);
-        expectedValue = expectedValue.add(THREE.divide(THIRTY_ONE, NUMBER_OF_DECIMALS, ROUNDING_METHOD));
+        expectedValue = expectedValue.add(THREE.divide(THIRTY_ONE, 2 * NUMBER_OF_DECIMALS, ROUNDING_METHOD));
+        expectedValue = expectedValue.setScale(NUMBER_OF_DECIMALS, ROUNDING_METHOD);
         testCalculateNumberOfBillingCycles(startDate, planChangeDate, targetDate, 7, expectedValue);
 
         expectedValue = FIVE.divide(TWENTY_EIGHT, NUMBER_OF_DECIMALS, ROUNDING_METHOD).add(TWO);  
diff --git a/invoice/src/test/java/com/ning/billing/invoice/tests/InvoicingTestBase.java b/invoice/src/test/java/com/ning/billing/invoice/tests/InvoicingTestBase.java
index 50f1b23..25be5e2 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/tests/InvoicingTestBase.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/tests/InvoicingTestBase.java
@@ -18,10 +18,21 @@ package com.ning.billing.invoice.tests;
 
 import java.math.BigDecimal;
 
+import com.ning.billing.account.api.Account;
+import com.ning.billing.catalog.api.BillingPeriod;
+import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.catalog.api.Plan;
+import com.ning.billing.catalog.api.PlanPhase;
+import com.ning.billing.entitlement.api.SubscriptionTransitionType;
+import com.ning.billing.entitlement.api.billing.BillingEvent;
+import com.ning.billing.entitlement.api.billing.BillingModeType;
+import com.ning.billing.entitlement.api.user.Subscription;
 import org.joda.time.DateTime;
 
 import com.ning.billing.invoice.model.InvoicingConfiguration;
 
+import javax.annotation.Nullable;
+
 public abstract class InvoicingTestBase {
     protected static final int NUMBER_OF_DECIMALS = InvoicingConfiguration.getNumberOfDecimals();
     protected static final int ROUNDING_METHOD = InvoicingConfiguration.getRoundingMode();
@@ -32,7 +43,7 @@ public abstract class InvoicingTestBase {
     protected static final BigDecimal ONE_AND_A_HALF = new BigDecimal("1.5").setScale(NUMBER_OF_DECIMALS);
     protected static final BigDecimal TWO = new BigDecimal("2.0").setScale(NUMBER_OF_DECIMALS);
     protected static final BigDecimal THREE = new BigDecimal("3.0").setScale(NUMBER_OF_DECIMALS);
-    //protected static final BigDecimal FOUR = new BigDecimal("4.0").setScale(NUMBER_OF_DECIMALS);
+    protected static final BigDecimal FOUR = new BigDecimal("4.0").setScale(NUMBER_OF_DECIMALS);
     protected static final BigDecimal FIVE = new BigDecimal("5.0").setScale(NUMBER_OF_DECIMALS);
     protected static final BigDecimal SIX = new BigDecimal("6.0").setScale(NUMBER_OF_DECIMALS);
     protected static final BigDecimal SEVEN = new BigDecimal("7.0").setScale(NUMBER_OF_DECIMALS);
@@ -49,6 +60,7 @@ public abstract class InvoicingTestBase {
     protected static final BigDecimal TWENTY = new BigDecimal("20.0").setScale(NUMBER_OF_DECIMALS);
 
     protected static final BigDecimal TWENTY_FOUR = new BigDecimal("24.0").setScale(NUMBER_OF_DECIMALS);
+    protected static final BigDecimal TWENTY_FIVE = new BigDecimal("25.0").setScale(NUMBER_OF_DECIMALS);
 
     protected static final BigDecimal TWENTY_EIGHT = new BigDecimal("28.0").setScale(NUMBER_OF_DECIMALS);
     protected static final BigDecimal TWENTY_NINE = new BigDecimal("29.0").setScale(NUMBER_OF_DECIMALS);
@@ -70,4 +82,84 @@ public abstract class InvoicingTestBase {
     protected DateTime buildDateTime(int year, int month, int day) {
         return new DateTime(year, month, day, 0, 0, 0, 0);
     }
+
+    protected BillingEvent createMockBillingEvent(@Nullable final Account account, final Subscription subscription,
+                                                  final DateTime effectiveDate,
+                                                  final Plan plan, final PlanPhase planPhase,
+                                                  @Nullable final BigDecimal fixedPrice, @Nullable final BigDecimal recurringPrice,
+                                                  final Currency currency, final BillingPeriod billingPeriod, final int billCycleDay,
+                                                  final BillingModeType billingModeType, final String description,
+                                                  final long totalOrdering,
+                                                  final SubscriptionTransitionType type) {
+        return new BillingEvent() {
+            @Override
+            public Account getAccount() {
+                return account;
+            }
+            @Override
+            public int getBillCycleDay() {
+                return billCycleDay;
+            }
+            @Override
+            public Subscription getSubscription() {
+                return subscription;
+            }
+            @Override
+            public DateTime getEffectiveDate() {
+                return effectiveDate;
+            }
+            @Override
+            public PlanPhase getPlanPhase() {
+                return planPhase;
+            }
+            @Override
+            public Plan getPlan() {
+                return plan;
+            }
+            @Override
+            public BillingPeriod getBillingPeriod() {
+                return billingPeriod;
+            }
+            @Override
+            public BillingModeType getBillingMode() {
+                return billingModeType;
+            }
+            @Override
+            public String getDescription() {
+                return description;
+            }
+            @Override
+            public BigDecimal getFixedPrice() {
+                return fixedPrice;
+            }
+            @Override
+            public BigDecimal getRecurringPrice() {
+                return recurringPrice;
+            }
+            @Override
+            public Currency getCurrency() {
+                return currency;
+            }
+            @Override
+            public SubscriptionTransitionType getTransitionType() {
+                return type;
+            }
+            @Override
+            public Long getTotalOrdering() {
+                return totalOrdering;
+            }
+            @Override
+            public int compareTo(BillingEvent e1) {
+                if (!getSubscription().getId().equals(e1.getSubscription().getId())) { // First order by subscription
+                    return getSubscription().getId().compareTo(e1.getSubscription().getId());
+                } else { // subscriptions are the same
+                    if (! getEffectiveDate().equals(e1.getEffectiveDate())) { // Secondly order by date
+                        return getEffectiveDate().compareTo(e1.getEffectiveDate());
+                    } else { // dates and subscriptions are the same
+                        return getTotalOrdering().compareTo(e1.getTotalOrdering());
+                    }
+                }
+            }
+        };
+    }
 }
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/json/AccountJson.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/json/AccountJson.java
index 4401b06..2b7e409 100644
--- a/jaxrs/src/main/java/com/ning/billing/jaxrs/json/AccountJson.java
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/json/AccountJson.java
@@ -104,6 +104,17 @@ public class AccountJson extends AccountJsonSimple {
             public String getPhone() {
                 return phone;
             }
+
+            @Override
+            public boolean isMigrated() {
+                return false;
+            }
+
+            @Override
+            public boolean isNotifiedForInvoices() {
+                return false;
+            }
+
             @Override
             public String getPaymentProviderName() {
                 return paymentProvider;
diff --git a/junction/src/main/java/com/ning/billing/junction/plumbing/api/BlockingAccount.java b/junction/src/main/java/com/ning/billing/junction/plumbing/api/BlockingAccount.java
index 7f23f84..30347a4 100644
--- a/junction/src/main/java/com/ning/billing/junction/plumbing/api/BlockingAccount.java
+++ b/junction/src/main/java/com/ning/billing/junction/plumbing/api/BlockingAccount.java
@@ -19,6 +19,7 @@ package com.ning.billing.junction.plumbing.api;
 import java.util.List;
 import java.util.UUID;
 
+import com.ning.billing.util.tag.ControlTagType;
 import org.joda.time.DateTime;
 import org.joda.time.DateTimeZone;
 
@@ -46,6 +47,11 @@ public class BlockingAccount implements Account {
         return account.getTagList();
     }
 
+    @Override
+    public boolean hasTag(TagDefinition tagDefinition) {
+        return account.hasTag(tagDefinition);
+    }
+
     public String getUpdatedBy() {
         return account.getUpdatedBy();
     }
@@ -58,8 +64,9 @@ public class BlockingAccount implements Account {
         return account.getCreatedBy();
     }
 
-    public boolean hasTag(String tagName) {
-        return account.hasTag(tagName);
+    @Override
+    public boolean hasTag(ControlTagType controlTagType) {
+        return account.hasTag(controlTagType);
     }
 
     public DateTime getUpdatedDate() {
@@ -90,6 +97,11 @@ public class BlockingAccount implements Account {
         account.addTags(tags);
     }
 
+    @Override
+    public void addTagsFromDefinitions(List<TagDefinition> tagDefinitions) {
+        account.addTagsFromDefinitions(tagDefinitions);
+    }
+
     public void setFieldValue(String fieldName, String fieldValue) {
         account.setFieldValue(fieldName, fieldValue);
     }
@@ -209,4 +221,14 @@ public class BlockingAccount implements Account {
         return account.getPhone();
     }
 
+    @Override
+    public boolean isMigrated() {
+        return account.isMigrated();
+    }
+
+    @Override
+    public boolean isNotifiedForInvoices() {
+        return account.isNotifiedForInvoices();
+    }
+
 }
diff --git a/junction/src/main/java/com/ning/billing/junction/plumbing/api/BlockingAccountUserApi.java b/junction/src/main/java/com/ning/billing/junction/plumbing/api/BlockingAccountUserApi.java
index 38af49f..45e3d5c 100644
--- a/junction/src/main/java/com/ning/billing/junction/plumbing/api/BlockingAccountUserApi.java
+++ b/junction/src/main/java/com/ning/billing/junction/plumbing/api/BlockingAccountUserApi.java
@@ -23,13 +23,14 @@ import com.google.inject.Inject;
 import com.ning.billing.account.api.Account;
 import com.ning.billing.account.api.AccountApiException;
 import com.ning.billing.account.api.AccountData;
+import com.ning.billing.account.api.AccountEmail;
 import com.ning.billing.account.api.AccountUserApi;
 import com.ning.billing.account.api.MigrationAccountData;
 import com.ning.billing.junction.api.BlockingApi;
 import com.ning.billing.util.callcontext.CallContext;
 import com.ning.billing.util.customfield.CustomField;
 import com.ning.billing.util.glue.RealImplementation;
-import com.ning.billing.util.tag.Tag;
+import com.ning.billing.util.tag.TagDefinition;
 
 public class BlockingAccountUserApi implements AccountUserApi { 
     private AccountUserApi userApi;
@@ -42,15 +43,15 @@ public class BlockingAccountUserApi implements AccountUserApi {
     }
 
     @Override
-    public Account createAccount(AccountData data, List<CustomField> fields, List<Tag> tags, CallContext context)
+    public Account createAccount(AccountData data, List<CustomField> fields, List<TagDefinition> tagDefinitions, CallContext context)
             throws AccountApiException {
-        return userApi.createAccount(data, fields, tags, context);
+        return userApi.createAccount(data, fields, tagDefinitions, context);
     }
 
     @Override
-    public Account migrateAccount(MigrationAccountData data, List<CustomField> fields, List<Tag> tags,
+    public Account migrateAccount(MigrationAccountData data, List<CustomField> fields, List<TagDefinition> tagDefinitions,
             CallContext context) throws AccountApiException {
-        return userApi.migrateAccount(data, fields, tags, context);
+        return userApi.migrateAccount(data, fields, tagDefinitions, context);
     }
 
     @Override
@@ -88,4 +89,14 @@ public class BlockingAccountUserApi implements AccountUserApi {
         return userApi.getIdFromKey(externalKey);
     }
 
+    @Override
+    public List<AccountEmail> getEmails(UUID accountId) {
+        return userApi.getEmails(accountId);
+    }
+
+    @Override
+    public void saveEmails(UUID accountId, List<AccountEmail> emails, CallContext context) {
+        userApi.saveEmails(accountId, emails, context);
+    }
+
 }
diff --git a/junction/src/main/java/com/ning/billing/junction/plumbing/api/BlockingSubscription.java b/junction/src/main/java/com/ning/billing/junction/plumbing/api/BlockingSubscription.java
index a3ded85..bc38db9 100644
--- a/junction/src/main/java/com/ning/billing/junction/plumbing/api/BlockingSubscription.java
+++ b/junction/src/main/java/com/ning/billing/junction/plumbing/api/BlockingSubscription.java
@@ -19,6 +19,7 @@ package com.ning.billing.junction.plumbing.api;
 import java.util.List;
 import java.util.UUID;
 
+import com.ning.billing.util.tag.ControlTagType;
 import org.joda.time.DateTime;
 
 import com.ning.billing.catalog.api.BillingPeriod;
@@ -57,6 +58,11 @@ public class BlockingSubscription implements Subscription {
         return subscription.getTagList();
     }
 
+    @Override
+    public boolean hasTag(TagDefinition tagDefinition) {
+        return subscription.hasTag(tagDefinition);
+    }
+
     public UUID getId() {
         return subscription.getId();
     }
@@ -65,8 +71,8 @@ public class BlockingSubscription implements Subscription {
         return subscription.getCreatedBy();
     }
 
-    public boolean hasTag(String tagName) {
-        return subscription.hasTag(tagName);
+    public boolean hasTag(ControlTagType controlTagType) {
+        return subscription.hasTag(controlTagType);
     }
 
     public DateTime getCreatedDate() {
@@ -85,6 +91,11 @@ public class BlockingSubscription implements Subscription {
         subscription.addTags(tags);
     }
 
+    @Override
+    public void addTagsFromDefinitions(List<TagDefinition> tagDefinitions) {
+        subscription.addTagsFromDefinitions(tagDefinitions);
+    }
+
     public void setFieldValue(String fieldName, String fieldValue) {
         subscription.setFieldValue(fieldName, fieldValue);
     }
diff --git a/junction/src/test/java/com/ning/billing/junction/dao/TestBlockingDao.java b/junction/src/test/java/com/ning/billing/junction/dao/TestBlockingDao.java
index 3fc73ec..0c3b8c8 100644
--- a/junction/src/test/java/com/ning/billing/junction/dao/TestBlockingDao.java
+++ b/junction/src/test/java/com/ning/billing/junction/dao/TestBlockingDao.java
@@ -65,10 +65,11 @@ public class TestBlockingDao {
     @AfterClass(groups = "slow")
     public void stopMysql()
     {
-        helper.stopMysql();
+        if (helper != null) {
+            helper.stopMysql();
+        }
     }
-    
-    
+
     @Test(groups={"slow"}, enabled=true)
     public void testDao() { 
         ClockMock clock = new ClockMock();
diff --git a/junction/src/test/java/com/ning/billing/junction/plumbing/billing/TestDefaultEntitlementBillingApi.java b/junction/src/test/java/com/ning/billing/junction/plumbing/billing/TestDefaultEntitlementBillingApi.java
index bb5e1bb..895eb32 100644
--- a/junction/src/test/java/com/ning/billing/junction/plumbing/billing/TestDefaultEntitlementBillingApi.java
+++ b/junction/src/test/java/com/ning/billing/junction/plumbing/billing/TestDefaultEntitlementBillingApi.java
@@ -148,13 +148,13 @@ public class TestDefaultEntitlementBillingApi {
 	private Subscription subscription;
 	private DateTime subscriptionStartDate;
 
-	@BeforeSuite(alwaysRun=true)
+	@BeforeSuite(groups={"fast", "slow"})
 	public void setup() throws ServiceException {
         catalogService = new MockCatalogService(new MockCatalog());
         clock = new ClockMock();
 	}
 
-	@BeforeMethod(alwaysRun=true)
+	@BeforeMethod(groups={"fast", "slow"})
 	public void setupEveryTime() {
 		bundles = new ArrayList<SubscriptionBundle>();
 		final SubscriptionBundle bundle = new SubscriptionBundleData( eventId,"TestKey", subId,  clock.getUTCNow().minusDays(4), null);
diff --git a/overdue/src/main/java/com/ning/billing/overdue/config/DefaultDuration.java b/overdue/src/main/java/com/ning/billing/overdue/config/DefaultDuration.java
index 90d3670..7301e46 100644
--- a/overdue/src/main/java/com/ning/billing/overdue/config/DefaultDuration.java
+++ b/overdue/src/main/java/com/ning/billing/overdue/config/DefaultDuration.java
@@ -21,6 +21,7 @@ import javax.xml.bind.annotation.XmlAccessorType;
 import javax.xml.bind.annotation.XmlElement;
 
 import org.joda.time.DateTime;
+import org.joda.time.Period;
 
 import com.ning.billing.catalog.api.Duration;
 import com.ning.billing.catalog.api.TimeUnit;
@@ -53,7 +54,7 @@ public class DefaultDuration extends ValidatingConfig<OverdueConfig> implements 
 
     @Override
     public DateTime addToDateTime(DateTime dateTime) {
-        if (number == null) {return dateTime;}
+        if ((number == null) && (unit != TimeUnit.UNLIMITED)) {return dateTime;}
 
         switch (unit) {
             case DAYS:
@@ -70,6 +71,24 @@ public class DefaultDuration extends ValidatingConfig<OverdueConfig> implements 
     }
 
     @Override
+    public Period toJodaPeriod() {
+        if ((number == null) && (unit != TimeUnit.UNLIMITED)) {return new Period();}
+
+        switch (unit) {
+            case DAYS:
+                return new Period().withDays(number);
+            case MONTHS:
+                return new Period().withMonths(number);
+            case YEARS:
+                return new Period().withYears(number);
+            case UNLIMITED:
+                return new Period().withYears(100);
+            default:
+                return new Period();
+        }
+    }
+
+    @Override
 	public ValidationErrors validate(OverdueConfig catalog, ValidationErrors errors) {
 		//TODO MDW - Validation TimeUnit UNLIMITED iff number == -1
 		return errors;
diff --git a/overdue/src/main/java/com/ning/billing/overdue/config/DefaultOverdueState.java b/overdue/src/main/java/com/ning/billing/overdue/config/DefaultOverdueState.java
index 1d76d72..7d84e7c 100644
--- a/overdue/src/main/java/com/ning/billing/overdue/config/DefaultOverdueState.java
+++ b/overdue/src/main/java/com/ning/billing/overdue/config/DefaultOverdueState.java
@@ -58,7 +58,7 @@ public class DefaultOverdueState<T extends Blockable> extends ValidatingConfig<O
 	//Other actions could include
 	// - send email
 	// - trigger payment retry?
-	// - add tags to bundle/account
+	// - add tagStore to bundle/account
 	// - set payment failure email template
 	// - set payment retry interval
 	// - backup payment mechanism?
diff --git a/payment/src/main/java/com/ning/billing/payment/dao/PaymentSqlDao.java b/payment/src/main/java/com/ning/billing/payment/dao/PaymentSqlDao.java
index cc3db6d..b7b861a 100644
--- a/payment/src/main/java/com/ning/billing/payment/dao/PaymentSqlDao.java
+++ b/payment/src/main/java/com/ning/billing/payment/dao/PaymentSqlDao.java
@@ -108,7 +108,7 @@ public interface PaymentSqlDao extends Transactional<PaymentSqlDao>, CloseMe, Tr
     public static final class PaymentAttemptBinder extends BinderBase implements Binder<Bind, PaymentAttempt> {
         @Override
         public void bind(@SuppressWarnings("rawtypes") SQLStatement stmt, Bind bind, PaymentAttempt paymentAttempt) {
-            stmt.bind("payment_attempt_id", paymentAttempt.getPaymentAttemptId().toString());
+            stmt.bind("payment_attempt_id", paymentAttempt.getPaymentAttemptId() == null ? null : paymentAttempt.getPaymentAttemptId().toString());
             stmt.bind("invoice_id", paymentAttempt.getInvoiceId().toString());
             stmt.bind("account_id", paymentAttempt.getAccountId().toString());
             stmt.bind("amount", paymentAttempt.getAmount());
diff --git a/payment/src/main/java/com/ning/billing/payment/RetryService.java b/payment/src/main/java/com/ning/billing/payment/RetryService.java
index 7558fbe..447bb74 100644
--- a/payment/src/main/java/com/ning/billing/payment/RetryService.java
+++ b/payment/src/main/java/com/ning/billing/payment/RetryService.java
@@ -100,7 +100,10 @@ public class RetryService implements KillbillService {
                 return id;
             }
         };
-        retryQueue.recordFutureNotification(timeOfRetry, key);
+
+        if (retryQueue != null) {
+            retryQueue.recordFutureNotification(timeOfRetry, key);
+        }
     }
 
     private void retry(String paymentAttemptId, CallContext context) {
diff --git a/payment/src/test/java/com/ning/billing/payment/setup/PaymentTestModuleWithMocks.java b/payment/src/test/java/com/ning/billing/payment/setup/PaymentTestModuleWithMocks.java
index 17383ee..8ec8862 100644
--- a/payment/src/test/java/com/ning/billing/payment/setup/PaymentTestModuleWithMocks.java
+++ b/payment/src/test/java/com/ning/billing/payment/setup/PaymentTestModuleWithMocks.java
@@ -16,19 +16,16 @@
 
 package com.ning.billing.payment.setup;
 
+import com.ning.billing.invoice.api.test.DefaultInvoiceTestApi;
+import com.ning.billing.invoice.api.test.InvoiceTestApi;
 import org.apache.commons.collections.MapUtils;
 
 import com.google.common.collect.ImmutableMap;
 import com.google.inject.Provider;
-import com.ning.billing.account.api.AccountUserApi;
-import com.ning.billing.account.api.user.DefaultAccountUserApi;
-import com.ning.billing.account.dao.AccountDao;
-import com.ning.billing.account.dao.MockAccountDao;
 import com.ning.billing.config.PaymentConfig;
-import com.ning.billing.invoice.dao.InvoiceDao;
-import com.ning.billing.invoice.dao.MockInvoiceDao;
 import com.ning.billing.junction.api.BillingApi;
 import com.ning.billing.mock.BrainDeadProxyFactory;
+import com.ning.billing.mock.BrainDeadProxyFactory.ZombieControl;
 import com.ning.billing.payment.dao.MockPaymentDao;
 import com.ning.billing.payment.dao.PaymentDao;
 import com.ning.billing.payment.provider.MockPaymentProviderPluginModule;
@@ -49,7 +46,7 @@ public class PaymentTestModuleWithMocks extends PaymentModule {
 
     public PaymentTestModuleWithMocks() {
         super(MapUtils.toProperties(ImmutableMap.of("killbill.payment.provider.default", "my-mock",
-                                                    "killbill.payment.engine.events.off", "false")));
+                "killbill.payment.engine.events.off", "false")));
     }
 
     @Override
@@ -66,9 +63,8 @@ public class PaymentTestModuleWithMocks extends PaymentModule {
     protected void configure() {
         super.configure();
         bind(Bus.class).to(InMemoryBus.class).asEagerSingleton();
+        bind(InvoiceTestApi.class).to(DefaultInvoiceTestApi.class).asEagerSingleton();
 
-        bind(MockInvoiceDao.class).asEagerSingleton();
-        bind(InvoiceDao.class).to(MockInvoiceDao.class);
         bind(NotificationQueueService.class).to(MockNotificationQueueService.class).asEagerSingleton();
     }
 }
diff --git a/payment/src/test/java/com/ning/billing/payment/TestHelper.java b/payment/src/test/java/com/ning/billing/payment/TestHelper.java
index f856de7..2939c05 100644
--- a/payment/src/test/java/com/ning/billing/payment/TestHelper.java
+++ b/payment/src/test/java/com/ning/billing/payment/TestHelper.java
@@ -19,6 +19,7 @@ package com.ning.billing.payment;
 import java.math.BigDecimal;
 import java.util.UUID;
 
+import com.ning.billing.invoice.api.test.InvoiceTestApi;
 import org.apache.commons.lang.RandomStringUtils;
 import org.joda.time.DateTime;
 import org.joda.time.DateTimeZone;
@@ -30,7 +31,6 @@ import com.ning.billing.account.api.user.AccountBuilder;
 import com.ning.billing.catalog.api.Currency;
 import com.ning.billing.invoice.api.Invoice;
 import com.ning.billing.invoice.api.InvoiceItem;
-import com.ning.billing.invoice.dao.InvoiceDao;
 import com.ning.billing.invoice.model.DefaultInvoice;
 import com.ning.billing.invoice.model.RecurringInvoiceItem;
 import com.ning.billing.mock.BrainDeadProxyFactory.ZombieControl;
@@ -42,13 +42,13 @@ import com.ning.billing.util.entity.EntityPersistenceException;
 
 public class TestHelper {
     protected final AccountUserApi accountUserApi;
-    protected final InvoiceDao invoiceDao;
+    protected final InvoiceTestApi invoiceTestApi;
     private final CallContext context;
 
     @Inject
-    public TestHelper(CallContextFactory factory, AccountUserApi accountUserApi, InvoiceDao invoiceDao) {
+    public TestHelper(CallContextFactory factory, AccountUserApi accountUserApi, InvoiceTestApi invoiceTestApi) {
         this.accountUserApi = accountUserApi;
-        this.invoiceDao = invoiceDao;
+        this.invoiceTestApi = invoiceTestApi;
         context = factory.createCallContext("Princess Buttercup", CallOrigin.TEST, UserType.TEST);
     }
 
@@ -107,7 +107,8 @@ public class TestHelper {
                                                                recurringInvoiceItem.getCurrency()));
             }
         }
-        invoiceDao.create(invoice, context);
+
+        invoiceTestApi.create(invoice, context);
         return invoice;
     }
 

pom.xml 20(+18 -2)

diff --git a/pom.xml b/pom.xml
index a36879b..02b1aa6 100644
--- a/pom.xml
+++ b/pom.xml
@@ -243,9 +243,19 @@
                 <scope>test</scope>
             </dependency>
             <dependency>
+                <groupId>org.apache.commons</groupId>
+                <artifactId>commons-email</artifactId>
+                <version>1.2</version>
+            </dependency>
+            <dependency>
+                <groupId>org.apache.directory.studio</groupId>
+                <artifactId>org.apache.commons.io</artifactId>
+                <version>2.1</version>
+            </dependency>
+            <dependency>
                 <groupId>commons-io</groupId>
                 <artifactId>commons-io</artifactId>
-                <version>2.0.1</version>
+                <version>2.0</version>
             </dependency>
             <dependency>
                 <groupId>commons-lang</groupId>
@@ -317,6 +327,11 @@
                 <scope>test</scope>
             </dependency>
             <dependency>
+                <groupId>com.samskivert</groupId>
+                <artifactId>jmustache</artifactId>
+                <version>1.5</version>
+            </dependency>
+            <dependency>
                 <groupId>com.jayway.awaitility</groupId>
                 <artifactId>awaitility</artifactId>
                 <version>1.3.3</version>
@@ -447,7 +462,8 @@
                                 <exclude>**/.settings/**</exclude>
                                 <exclude>.travis.yml</exclude>
                                 <exclude>bin/**</exclude>
-
+                                <!-- exclude mustache template files -->
+                                <exclude>**/*.mustache</exclude>
                             </excludes>
                         </configuration>
                     </execution>
diff --git a/server/src/main/java/com/ning/billing/server/modules/KillbillServerModule.java b/server/src/main/java/com/ning/billing/server/modules/KillbillServerModule.java
index 8de0e2e..e2c37e3 100644
--- a/server/src/main/java/com/ning/billing/server/modules/KillbillServerModule.java
+++ b/server/src/main/java/com/ning/billing/server/modules/KillbillServerModule.java
@@ -16,6 +16,8 @@
 
 package com.ning.billing.server.modules;
 
+import com.ning.billing.util.email.EmailModule;
+import com.ning.billing.util.glue.GlobalLockerModule;
 import org.skife.jdbi.v2.DBI;
 import org.skife.jdbi.v2.IDBI;
 
@@ -72,6 +74,8 @@ public class KillbillServerModule extends AbstractModule
     }
     
     protected void installKillbillModules() {
+        install(new EmailModule());
+        install(new GlobalLockerModule());
         install(new FieldStoreModule());
         install(new TagStoreModule());
         install(new CatalogModule());
diff --git a/server/src/test/java/com/ning/billing/jaxrs/TestJaxrsBase.java b/server/src/test/java/com/ning/billing/jaxrs/TestJaxrsBase.java
index 012f88c..73ea829 100644
--- a/server/src/test/java/com/ning/billing/jaxrs/TestJaxrsBase.java
+++ b/server/src/test/java/com/ning/billing/jaxrs/TestJaxrsBase.java
@@ -30,6 +30,8 @@ import java.util.concurrent.TimeUnit;
 
 import javax.ws.rs.core.Response.Status;
 
+import com.ning.billing.util.email.EmailModule;
+import com.ning.billing.util.glue.GlobalLockerModule;
 import org.apache.commons.io.IOUtils;
 import org.codehaus.jackson.map.ObjectMapper;
 import org.eclipse.jetty.servlet.FilterHolder;
@@ -177,6 +179,8 @@ public class TestJaxrsBase {
             super.installKillbillModules();
             Modules.override(new com.ning.billing.payment.setup.PaymentModule()).with(new PaymentMockModule());
             */
+            install(new EmailModule());
+            install(new GlobalLockerModule());
             install(new FieldStoreModule());
             install(new TagStoreModule());
             install(new CatalogModule());
diff --git a/server/src/test/java/com/ning/billing/jaxrs/TestSubscription.java b/server/src/test/java/com/ning/billing/jaxrs/TestSubscription.java
index d520d49..4a74dfc 100644
--- a/server/src/test/java/com/ning/billing/jaxrs/TestSubscription.java
+++ b/server/src/test/java/com/ning/billing/jaxrs/TestSubscription.java
@@ -31,10 +31,8 @@ import org.testng.Assert;
 import org.testng.annotations.Test;
 
 import com.ning.billing.catalog.api.BillingPeriod;
-import com.ning.billing.catalog.api.Duration;
 import com.ning.billing.catalog.api.PriceListSet;
 import com.ning.billing.catalog.api.ProductCategory;
-import com.ning.billing.catalog.api.TimeUnit;
 import com.ning.billing.jaxrs.json.AccountJson;
 import com.ning.billing.jaxrs.json.BundleJsonNoSubsciptions;
 import com.ning.billing.jaxrs.json.SubscriptionJsonNoEvents;

util/pom.xml 16(+16 -0)

diff --git a/util/pom.xml b/util/pom.xml
index f28dcff..580d683 100644
--- a/util/pom.xml
+++ b/util/pom.xml
@@ -73,6 +73,14 @@
             <scope>test</scope>
         </dependency>
         <dependency>
+            <groupId>org.apache.commons</groupId>
+            <artifactId>commons-email</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.directory.studio</groupId>
+            <artifactId>org.apache.commons.io</artifactId>
+        </dependency>
+        <dependency>
             <groupId>commons-io</groupId>
             <artifactId>commons-io</artifactId>
             <scope>test</scope>
@@ -91,6 +99,14 @@
             <artifactId>awaitility</artifactId>
             <scope>test</scope>
         </dependency>
+        <dependency>
+            <groupId>com.samskivert</groupId>
+            <artifactId>jmustache</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>commons-io</groupId>
+            <artifactId>commons-io</artifactId>
+        </dependency>
     </dependencies>
     <build>
         <plugins>
diff --git a/util/src/main/java/com/ning/billing/util/customfield/CustomFieldHistoryBinder.java b/util/src/main/java/com/ning/billing/util/customfield/CustomFieldHistoryBinder.java
index 51e3621..5d6f6f2 100644
--- a/util/src/main/java/com/ning/billing/util/customfield/CustomFieldHistoryBinder.java
+++ b/util/src/main/java/com/ning/billing/util/customfield/CustomFieldHistoryBinder.java
@@ -37,7 +37,7 @@ public @interface CustomFieldHistoryBinder {
             return new Binder<CustomFieldHistoryBinder, CustomFieldHistory>() {
                 @Override
                 public void bind(SQLStatement q, CustomFieldHistoryBinder bind, CustomFieldHistory customFieldHistory) {
-                    q.bind("historyId", customFieldHistory.getHistoryId().toString());
+                    q.bind("historyRecordId", customFieldHistory.getHistoryId().toString());
                     q.bind("changeType", customFieldHistory.getChangeType().toString());
                     q.bind("id", customFieldHistory.getId().toString());
                     q.bind("fieldName", customFieldHistory.getName());
diff --git a/util/src/main/java/com/ning/billing/util/customfield/dao/AuditedCustomFieldDao.java b/util/src/main/java/com/ning/billing/util/customfield/dao/AuditedCustomFieldDao.java
index b7d2de0..e319117 100644
--- a/util/src/main/java/com/ning/billing/util/customfield/dao/AuditedCustomFieldDao.java
+++ b/util/src/main/java/com/ning/billing/util/customfield/dao/AuditedCustomFieldDao.java
@@ -45,7 +45,7 @@ public class AuditedCustomFieldDao implements CustomFieldDao {
             while (existingFieldIterator.hasNext()) {
                 CustomField existingField = existingFieldIterator.next();
                 if (field.getName().equals(existingField.getName())) {
-                    // if the tags match, remove from both lists
+                    // if the tagStore match, remove from both lists
                     fieldsToUpdate.add(field);
                     fieldIterator.remove();
                     existingFieldIterator.remove();
diff --git a/util/src/main/java/com/ning/billing/util/dao/AuditedDaoBase.java b/util/src/main/java/com/ning/billing/util/dao/AuditedDaoBase.java
new file mode 100644
index 0000000..03417b4
--- /dev/null
+++ b/util/src/main/java/com/ning/billing/util/dao/AuditedDaoBase.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you 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.ning.billing.util.dao;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+
+public abstract class AuditedDaoBase {
+    protected List<String> getIdList(int size) {
+        List<String> results = new ArrayList<String>();
+        for (int i = 0; i < size; i++) {
+            results.add(UUID.randomUUID().toString());
+        }
+        return results;
+    }
+}
diff --git a/util/src/main/java/com/ning/billing/util/email/DefaultEmailSender.java b/util/src/main/java/com/ning/billing/util/email/DefaultEmailSender.java
new file mode 100644
index 0000000..b4e3587
--- /dev/null
+++ b/util/src/main/java/com/ning/billing/util/email/DefaultEmailSender.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you 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.ning.billing.util.email;
+
+import java.util.List;
+
+import org.apache.commons.mail.EmailException;
+import org.apache.commons.mail.HtmlEmail;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.inject.Inject;
+
+public class DefaultEmailSender implements EmailSender { 
+    private final Logger log = LoggerFactory.getLogger(EmailSender.class);
+    private final EmailConfig config;
+
+    @Inject
+    public DefaultEmailSender(EmailConfig emailConfig) {
+        this.config = emailConfig;
+    }
+
+    @Override
+    public void sendSecureEmail(List<String> to, List<String> cc, String subject, String htmlBody) throws EmailApiException {
+        HtmlEmail email;
+        try {
+            email = new HtmlEmail();
+
+            email.setSmtpPort(config.getSmtpPort());
+            email.setAuthentication(config.getSmtpUserName(), config.getSmtpPassword());
+            email.setHostName(config.getSmtpServerName());
+            email.setFrom(config.getSmtpUserName());
+            email.setSubject(subject);
+            email.setHtmlMsg(htmlBody);
+
+            if (to != null) {
+                for (String recipient : to) {
+                    email.addTo(recipient);
+                }
+            }
+
+            if (cc != null) {
+                for (String recipient : cc) {
+                    email.addCc(recipient);
+                }
+            }
+
+            email.setSSL(true);
+            email.send();
+        } catch (EmailException ee)  {
+            log.warn("Failed to send e-mail", ee);
+        }
+    }
+}
diff --git a/util/src/main/java/com/ning/billing/util/email/EmailConfig.java b/util/src/main/java/com/ning/billing/util/email/EmailConfig.java
new file mode 100644
index 0000000..bcbf5e5
--- /dev/null
+++ b/util/src/main/java/com/ning/billing/util/email/EmailConfig.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you 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.ning.billing.util.email;
+
+import com.ning.billing.config.KillbillConfig;
+import org.skife.config.Config;
+import org.skife.config.Default;
+
+import java.util.Locale;
+
+public interface EmailConfig extends KillbillConfig {
+    @Config("mail.smtp.host")
+    @Default("smtp.gmail.com")
+    public String getSmtpServerName();
+
+    @Config("mail.smtp.port")
+    @Default("465")
+    public int getSmtpPort();
+
+    @Config("mail.smtp.user")
+    @Default("killbill.ning@gmail.com")
+    public String getSmtpUserName();
+
+    @Config("mail.smtp.password")
+    @Default("killbill@ning!")
+    public String getSmtpPassword();
+}
diff --git a/util/src/main/java/com/ning/billing/util/email/EmailModule.java b/util/src/main/java/com/ning/billing/util/email/EmailModule.java
new file mode 100644
index 0000000..5bb252a
--- /dev/null
+++ b/util/src/main/java/com/ning/billing/util/email/EmailModule.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you 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.ning.billing.util.email;
+
+import com.google.inject.AbstractModule;
+import org.skife.config.ConfigurationObjectFactory;
+
+public class EmailModule extends AbstractModule {
+    protected void installEmailConfig() {
+        EmailConfig config = new ConfigurationObjectFactory(System.getProperties()).build(EmailConfig.class);
+        bind(EmailConfig.class).toInstance(config);
+    }
+
+    @Override
+    protected void configure() {
+        installEmailConfig();
+    }
+}
diff --git a/util/src/main/java/com/ning/billing/util/email/templates/MustacheTemplateEngine.java b/util/src/main/java/com/ning/billing/util/email/templates/MustacheTemplateEngine.java
new file mode 100644
index 0000000..dba8757
--- /dev/null
+++ b/util/src/main/java/com/ning/billing/util/email/templates/MustacheTemplateEngine.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you 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.ning.billing.util.email.templates;
+
+import com.samskivert.mustache.Mustache;
+import com.samskivert.mustache.Template;
+import org.apache.commons.io.IOUtils;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.StringWriter;
+import java.util.Map;
+
+public class MustacheTemplateEngine implements TemplateEngine {
+    @Override
+    public String executeTemplate(String templateName, Map<String, Object> data) throws IOException {
+        String templateText = getTemplateText(templateName);
+        Template template = Mustache.compiler().compile(templateText);
+        return template.execute(data);
+    }
+
+    private String getTemplateText(String templateName) throws IOException {
+        InputStream templateStream = this.getClass().getResourceAsStream(templateName + ".mustache");
+        StringWriter writer = new StringWriter();
+        IOUtils.copy(templateStream, writer, "UTF-8");
+        return writer.toString();
+    }
+}
diff --git a/util/src/main/java/com/ning/billing/util/email/templates/TemplateEngine.java b/util/src/main/java/com/ning/billing/util/email/templates/TemplateEngine.java
new file mode 100644
index 0000000..9a47008
--- /dev/null
+++ b/util/src/main/java/com/ning/billing/util/email/templates/TemplateEngine.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you 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.ning.billing.util.email.templates;
+
+import java.io.IOException;
+import java.util.Map;
+
+public interface TemplateEngine {
+    public String executeTemplate(String templateName, Map<String, Object> data) throws IOException;
+}
diff --git a/util/src/main/java/com/ning/billing/util/entity/ExtendedEntityBase.java b/util/src/main/java/com/ning/billing/util/entity/ExtendedEntityBase.java
index 0017397..a52e276 100644
--- a/util/src/main/java/com/ning/billing/util/entity/ExtendedEntityBase.java
+++ b/util/src/main/java/com/ning/billing/util/entity/ExtendedEntityBase.java
@@ -21,6 +21,8 @@ import com.ning.billing.util.customfield.CustomField;
 import com.ning.billing.util.customfield.Customizable;
 import com.ning.billing.util.customfield.DefaultFieldStore;
 import com.ning.billing.util.customfield.FieldStore;
+import com.ning.billing.util.tag.ControlTagType;
+import com.ning.billing.util.tag.DefaultControlTag;
 import com.ning.billing.util.tag.DefaultTagStore;
 import com.ning.billing.util.tag.DescriptiveTag;
 import com.ning.billing.util.tag.Tag;
@@ -30,23 +32,24 @@ import com.ning.billing.util.tag.Taggable;
 import org.joda.time.DateTime;
 
 import javax.annotation.Nullable;
+import java.util.ArrayList;
 import java.util.List;
 import java.util.UUID;
 
 public abstract class ExtendedEntityBase extends EntityBase implements Customizable, Taggable {
     protected final FieldStore fields;
-    protected final TagStore tags;
+    protected final TagStore tagStore;
 
     public ExtendedEntityBase() {
         super();
         this.fields = DefaultFieldStore.create(getId(), getObjectName());
-        this.tags = new DefaultTagStore(id, getObjectName());
+        this.tagStore = new DefaultTagStore(id, getObjectName());
     }
 
     public ExtendedEntityBase(final UUID id, @Nullable final String createdBy, @Nullable final DateTime createdDate) {
         super(id, createdBy, createdDate);
         this.fields = DefaultFieldStore.create(getId(), getObjectName());
-        this.tags = new DefaultTagStore(id, getObjectName());
+        this.tagStore = new DefaultTagStore(id, getObjectName());
     }
 
     @Override
@@ -78,45 +81,67 @@ public abstract class ExtendedEntityBase extends EntityBase implements Customiza
 
     @Override
 	public List<Tag> getTagList() {
-		return tags.getEntityList();
+		return tagStore.getEntityList();
 	}
 
 	@Override
-	public boolean hasTag(final String tagName) {
-		return tags.containsTag(tagName);
+	public boolean hasTag(final TagDefinition tagDefinition) {
+		return tagStore.containsTagForDefinition(tagDefinition);
 	}
 
+    @Override
+    public boolean hasTag(ControlTagType controlTagType) {
+        return tagStore.containsTagForControlTagType(controlTagType);
+    }
+
 	@Override
 	public void addTag(final TagDefinition definition) {
 		Tag tag = new DescriptiveTag(definition);
-		tags.add(tag) ;
+		tagStore.add(tag) ;
 	}
 
+    @Override
+    public void addTags(final List<Tag> tags) {
+        this.tagStore.add(tags);
+    }
+
 	@Override
-	public void addTags(final List<Tag> tags) {
-		if (tags != null) {
-			this.tags.add(tags);
+	public void addTagsFromDefinitions(final List<TagDefinition> tagDefinitions) {
+		if (tagStore != null) {
+            List<Tag> tags = new ArrayList<Tag>();
+            if (tagDefinitions != null) {
+                for (TagDefinition tagDefinition : tagDefinitions) {
+                    try {
+                        ControlTagType controlTagType = ControlTagType.valueOf(tagDefinition.getName());
+                        tags.add(new DefaultControlTag(controlTagType));
+                    } catch (IllegalArgumentException ex) {
+                        tags.add(new DescriptiveTag(tagDefinition));
+                    }
+                }
+            }
+
+			this.tagStore.add(tags);
 		}
 	}
 
 	@Override
 	public void clearTags() {
-		this.tags.clear();
+		this.tagStore.clear();
 	}
 
 	@Override
-	public void removeTag(final TagDefinition definition) {
-		tags.remove(definition.getName());
+	public void removeTag(final TagDefinition tagDefinition) {
+		tagStore.remove(tagDefinition);
 	}
 
 	@Override
 	public boolean generateInvoice() {
-		return tags.generateInvoice();
+		return tagStore.generateInvoice();
 	}
 
 	@Override
 	public boolean processPayment() {
-		return tags.processPayment();
+		return tagStore.processPayment();
 	}
 
     @Override
diff --git a/util/src/main/java/com/ning/billing/util/glue/RealImplementation.java b/util/src/main/java/com/ning/billing/util/glue/RealImplementation.java
index 4ccd871..d72a698 100644
--- a/util/src/main/java/com/ning/billing/util/glue/RealImplementation.java
+++ b/util/src/main/java/com/ning/billing/util/glue/RealImplementation.java
@@ -30,7 +30,7 @@ import com.google.inject.BindingAnnotation;
  * This annotation is used to bing classes that are being intercepted in junction.
  * 
  * The real implementation of the class is bound in Guice with this parameter, the Blocking implementation
- * is let unannotated.
+ * is left unannotated.
  *
  */
 @BindingAnnotation @Target({ FIELD, PARAMETER, METHOD,LOCAL_VARIABLE }) @Retention(RUNTIME)
diff --git a/util/src/main/java/com/ning/billing/util/tag/dao/AuditedTagDao.java b/util/src/main/java/com/ning/billing/util/tag/dao/AuditedTagDao.java
index 374e0a3..3b238c0 100644
--- a/util/src/main/java/com/ning/billing/util/tag/dao/AuditedTagDao.java
+++ b/util/src/main/java/com/ning/billing/util/tag/dao/AuditedTagDao.java
@@ -22,18 +22,18 @@ import com.ning.billing.invoice.api.InvoiceApiException;
 import com.ning.billing.util.ChangeType;
 import com.ning.billing.util.audit.dao.AuditSqlDao;
 import com.ning.billing.util.callcontext.CallContext;
+import com.ning.billing.util.dao.AuditedDaoBase;
 import com.ning.billing.util.tag.Tag;
 import org.skife.jdbi.v2.IDBI;
 import org.skife.jdbi.v2.Transaction;
 import org.skife.jdbi.v2.TransactionStatus;
 import org.skife.jdbi.v2.sqlobject.mixins.Transmogrifier;
 
-import java.util.ArrayList;
 import java.util.Iterator;
 import java.util.List;
 import java.util.UUID;
 
-public class AuditedTagDao implements TagDao {
+public class AuditedTagDao extends AuditedDaoBase implements TagDao {
     private final TagSqlDao tagSqlDao;
 
     @Inject
@@ -52,10 +52,10 @@ public class AuditedTagDao implements TagDao {
                                         final List<Tag> tags, final CallContext context) {
         TagSqlDao tagSqlDao = dao.become(TagSqlDao.class);
 
-        // get list of existing tags
+        // get list of existing tagStore
         List<Tag> existingTags = tagSqlDao.load(objectId.toString(), objectType);
 
-        // sort into tags to update (tagsToUpdate), tags to add (tags), and tags to delete (existingTags)
+        // sort into tagStore to update (tagsToUpdate), tagStore to add (tagStore), and tagStore to delete (existingTags)
         Iterator<Tag> tagIterator = tags.iterator();
         while (tagIterator.hasNext()) {
             Tag tag = tagIterator.next();
@@ -64,7 +64,7 @@ public class AuditedTagDao implements TagDao {
             while (existingTagIterator.hasNext()) {
                 Tag existingTag = existingTagIterator.next();
                 if (tag.getTagDefinitionName().equals(existingTag.getTagDefinitionName())) {
-                    // if the tags match, remove from both lists
+                    // if the tagStore match, remove from both lists
                     // in the case of tag, this just means the tag remains associated
                     tagIterator.remove();
                     existingTagIterator.remove();
@@ -85,14 +85,6 @@ public class AuditedTagDao implements TagDao {
         auditSqlDao.insertAuditFromTransaction("tag_history", historyIdsForDelete, ChangeType.DELETE, context);
     }
 
-    private List<String> getIdList(int size) {
-        List<String> results = new ArrayList<String>();
-        for (int i = 0; i < size; i++) {
-            results.add(UUID.randomUUID().toString());
-        }
-        return results;
-    }
-
     @Override
     public List<Tag> loadTags(final UUID objectId, final String objectType) {
         return tagSqlDao.load(objectId.toString(), objectType);
diff --git a/util/src/main/java/com/ning/billing/util/tag/DefaultTagStore.java b/util/src/main/java/com/ning/billing/util/tag/DefaultTagStore.java
index eace017..aa9c3af 100644
--- a/util/src/main/java/com/ning/billing/util/tag/DefaultTagStore.java
+++ b/util/src/main/java/com/ning/billing/util/tag/DefaultTagStore.java
@@ -32,7 +32,7 @@ public class DefaultTagStore extends EntityCollectionBase<Tag> implements TagSto
     @Override
     /***
      * Collates the contents of the TagStore to determine if payments should be processed
-     * @return true is no tags contraindicate payment processing
+     * @return true if no tags contraindicate payment processing
      */
     public boolean processPayment() {
         for (Tag tag : entities.values()) {
@@ -49,7 +49,7 @@ public class DefaultTagStore extends EntityCollectionBase<Tag> implements TagSto
 
     /***
      * Collates the contents of the TagStore to determine if invoices should be generated
-     * @return true is no tags contraindicate invoice generation
+     * @return true if no tags contraindicate invoice generation
      */
     @Override
     public boolean generateInvoice() {
@@ -66,18 +66,30 @@ public class DefaultTagStore extends EntityCollectionBase<Tag> implements TagSto
     }
 
     @Override
-    public void remove(final String tagName) {
-        entities.remove(entities.get(tagName));
+    public boolean containsTagForDefinition(final TagDefinition tagDefinition) {
+        for (Tag tag : entities.values()) {
+            if (tag.getTagDefinitionName().equals(tagDefinition.getName())) {
+                return true;
+            }
+        }
+
+        return false;
     }
 
     @Override
-    public boolean containsTag(final String tagName) {
+    public boolean containsTagForControlTagType(final ControlTagType controlTagType) {
         for (Tag tag : entities.values()) {
-            if (tag.getTagDefinitionName().equals(tagName)) {
+            if (tag.getTagDefinitionName().equals(controlTagType.toString())) {
                 return true;
             }
         }
 
         return false;
     }
+
+    @Override
+    public Tag remove(TagDefinition tagDefinition) {
+        Tag tag = entities.get(tagDefinition.getName());
+        return (tag == null) ? null : entities.remove(tag);
+    }
 }
\ No newline at end of file
diff --git a/util/src/main/java/com/ning/billing/util/template/translation/DefaultCatalogTranslator.java b/util/src/main/java/com/ning/billing/util/template/translation/DefaultCatalogTranslator.java
new file mode 100644
index 0000000..933b24f
--- /dev/null
+++ b/util/src/main/java/com/ning/billing/util/template/translation/DefaultCatalogTranslator.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you 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.ning.billing.util.template.translation;
+
+import com.google.inject.Inject;
+
+public class DefaultCatalogTranslator extends DefaultTranslatorBase {
+    @Inject
+    public DefaultCatalogTranslator(TranslatorConfig config) {
+        super(config);
+    }
+
+    @Override
+    protected String getBundlePath() {
+        return "com/ning/billing/util/template/translation/CatalogTranslation";
+    }
+
+    @Override
+    protected String getTranslationType() {
+        return "catalog";
+    }
+}
diff --git a/util/src/main/java/com/ning/billing/util/template/translation/DefaultTranslatorBase.java b/util/src/main/java/com/ning/billing/util/template/translation/DefaultTranslatorBase.java
new file mode 100644
index 0000000..4d1a7b6
--- /dev/null
+++ b/util/src/main/java/com/ning/billing/util/template/translation/DefaultTranslatorBase.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you 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.ning.billing.util.template.translation;
+
+import com.google.inject.Inject;
+import com.ning.billing.ErrorCode;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Locale;
+import java.util.MissingResourceException;
+import java.util.ResourceBundle;
+
+public abstract class DefaultTranslatorBase implements Translator {
+    protected final TranslatorConfig config;
+    protected final Logger log = LoggerFactory.getLogger(DefaultTranslatorBase.class);
+
+    @Inject
+    public DefaultTranslatorBase(TranslatorConfig config) {
+        this.config = config;
+    }
+
+    protected abstract String getBundlePath();
+
+    /*
+     * string used for exception handling
+     */
+    protected abstract String getTranslationType();
+
+    @Override
+    public String getTranslation(Locale locale, String originalText) {
+        ResourceBundle bundle = null;
+        try {
+            bundle = ResourceBundle.getBundle(getBundlePath(), locale);
+        } catch (MissingResourceException mrex) {
+            log.warn(String.format(ErrorCode.MISSING_TRANSLATION_RESOURCE.toString(), getTranslationType()));
+        }
+
+        if ((bundle != null) && (bundle.containsKey(originalText))) {
+            return bundle.getString(originalText);
+        } else {
+            try {
+                Locale defaultLocale = new Locale(config.getDefaultLocale());
+                bundle = ResourceBundle.getBundle(getBundlePath(), defaultLocale);
+
+                if ((bundle != null) && (bundle.containsKey(originalText))) {
+                    return bundle.getString(originalText);
+                } else {
+                    return originalText;
+                }
+            } catch (MissingResourceException mrex) {
+                log.warn(String.format(ErrorCode.MISSING_TRANSLATION_RESOURCE.toString(), getTranslationType()));
+                return originalText;
+            }
+        }
+    }
+}
diff --git a/util/src/main/resources/com/ning/billing/util/customfield/dao/CustomFieldHistorySqlDao.sql.stg b/util/src/main/resources/com/ning/billing/util/customfield/dao/CustomFieldHistorySqlDao.sql.stg
index c9e2ab0..6e5a43f 100644
--- a/util/src/main/resources/com/ning/billing/util/customfield/dao/CustomFieldHistorySqlDao.sql.stg
+++ b/util/src/main/resources/com/ning/billing/util/customfield/dao/CustomFieldHistorySqlDao.sql.stg
@@ -1,7 +1,7 @@
 group CustomFieldHistorySqlDao;
 
 fields(prefix) ::= <<
-  <prefix>history_id,
+  <prefix>history_record_id,
   <prefix>id,
   <prefix>object_id,
   <prefix>object_type,
@@ -14,5 +14,5 @@ fields(prefix) ::= <<
 
 batchAddHistoryFromTransaction() ::= <<
     INSERT INTO custom_field_history(<fields()>)
-    VALUES(:historyId, :id, :objectId, :objectType, :fieldName, :fieldValue, :userName, :updatedDate, :changeType);
+    VALUES(:historyRecordId, :id, :objectId, :objectType, :fieldName, :fieldValue, :userName, :updatedDate, :changeType);
 >>
\ No newline at end of file
diff --git a/util/src/main/resources/com/ning/billing/util/ddl.sql b/util/src/main/resources/com/ning/billing/util/ddl.sql
index 852dd0b..2162500 100644
--- a/util/src/main/resources/com/ning/billing/util/ddl.sql
+++ b/util/src/main/resources/com/ning/billing/util/ddl.sql
@@ -16,7 +16,7 @@ CREATE UNIQUE INDEX custom_fields_unique ON custom_fields(object_id, object_type
 
 DROP TABLE IF EXISTS custom_field_history;
 CREATE TABLE custom_field_history (
-  history_id char(36) NOT NULL,
+  history_record_id char(36) NOT NULL,
   id char(36) NOT NULL,
   object_id char(36) NOT NULL,
   object_type varchar(30) NOT NULL,
@@ -119,6 +119,8 @@ CREATE TABLE audit_log (
     user_token char(36),
     PRIMARY KEY(id)
 ) ENGINE=innodb;
+CREATE INDEX audit_log_fetch_record ON audit_log(table_name, record_id);
+CREATE INDEX audit_log_user_name ON audit_log(changed_by);
 
 DROP TABLE IF EXISTS bus_events;
 CREATE TABLE bus_events (
diff --git a/util/src/main/resources/com/ning/billing/util/email/templates/HtmlInvoiceTemplate.mustache b/util/src/main/resources/com/ning/billing/util/email/templates/HtmlInvoiceTemplate.mustache
new file mode 100644
index 0000000..73c70b3
--- /dev/null
+++ b/util/src/main/resources/com/ning/billing/util/email/templates/HtmlInvoiceTemplate.mustache
@@ -0,0 +1,96 @@
+<html>
+    <head>
+        <style type="text/css">
+            th {align=left; width=225px; border-bottom: solid 2px black;}
+        </style>
+    </head>
+    <body>
+        <h1>{{text.invoiceTitle}}</h1>
+        <table>
+            <tr>
+                <td rowspan=3 width=350px>Insert image here</td>
+                <td width=100px/>
+                <td width=225px/>
+                <td width=225px/>
+            </tr>
+            <tr>
+                <td />
+                <td align=right>{{text.invoiceDate}}</td>
+                <td>{{invoice.formattedInvoiceDate}}</td>
+            </tr>
+            <tr>
+                <td />
+                <td align=right>{{text.invoiceNumber}}</td>
+                <td>{{invoice.invoiceNumber}}</td>
+            </tr>
+            <tr>
+                <td>{{text.companyName}}</td>
+                <td></td>
+                <td align=right>{{text.accountOwnerName}}</td>
+                <td>{{account.name}}</td>
+            </tr>
+            <tr>
+                <td>{{text.companyAddress}}</td>
+                <td />
+                <td />
+                <td>{{account.email}}</td>
+            </tr>
+            <tr>
+                <td>{{text.companyCityProvincePostalCode}}</td>
+                <td />
+                <td />
+                <td>{{account.phone}}</td>
+            </tr>
+            <tr>
+                <td>{{text.companyCountry}}</td>
+                <td />
+                <td />
+                <td />
+            </tr>
+            <tr>
+                <td><{{text.companyUrl}}</td>
+                <td />
+                <td />
+                <td />
+            </tr>
+        </table>
+        <br />
+        <br />
+        <br />
+        <table>
+            <tr>
+                <th>{{text.invoiceItemBundleName}}</td>
+                <th>{{text.invoiceItemDescription}}</td>
+                <th>{{text.invoiceItemServicePeriod}}</td>
+                <th>{{text.invoiceItemAmount}}</td>
+            </tr>
+            {{#invoice.invoiceItems}}
+            <tr>
+                <td>{{description}}</td>
+                <td>{{planName}}</td>
+                <td>{{formattedStartDate}} - {{formattedEndDate}}</td>
+                <td>{{invoice.currency}} {{amount}}</td>
+            </tr>
+            {{/invoice.invoiceItems}}
+            <tr>
+                <td colspan=4 />
+            </tr>
+            <tr>
+                <td colspan=2 />
+                <td align=right><strong>{{text.invoiceAmount}}</strong></td>
+                <td align=right><strong>{{invoice.totalAmount}}</strong></td>
+            </tr>
+            <tr>
+                <td colspan=2 />
+                <td align=right><strong>{{text.invoiceAmountPaid}}</strong></td>
+                <td align=right><strong>{{invoice.amountPaid}}</strong></td>
+            </tr>
+            <tr>
+                <td colspan=2 />
+                <td align=right><strong>{{text.invoiceBalance}}</strong></td>
+                <td align=right><strong>{{invoice.balance}}</strong></td>
+            </tr>
+        </table>
+    </body>
+</html>
+
diff --git a/util/src/main/resources/com/ning/billing/util/template/translation/CatalogTranslation_EN_US.properties b/util/src/main/resources/com/ning/billing/util/template/translation/CatalogTranslation_EN_US.properties
new file mode 100644
index 0000000..b05a595
--- /dev/null
+++ b/util/src/main/resources/com/ning/billing/util/template/translation/CatalogTranslation_EN_US.properties
@@ -0,0 +1,2 @@
+ning-pro = Pro
+ning-plus = Plus
\ No newline at end of file
diff --git a/util/src/main/resources/com/ning/billing/util/template/translation/CatalogTranslation_FR_CA.properties b/util/src/main/resources/com/ning/billing/util/template/translation/CatalogTranslation_FR_CA.properties
new file mode 100644
index 0000000..e025a88
--- /dev/null
+++ b/util/src/main/resources/com/ning/billing/util/template/translation/CatalogTranslation_FR_CA.properties
@@ -0,0 +1 @@
+ning-plus = Plus en francais
\ No newline at end of file
diff --git a/util/src/main/resources/com/ning/billing/util/template/translation/InvoiceTranslation_EN_US.properties b/util/src/main/resources/com/ning/billing/util/template/translation/InvoiceTranslation_EN_US.properties
new file mode 100644
index 0000000..0162671
--- /dev/null
+++ b/util/src/main/resources/com/ning/billing/util/template/translation/InvoiceTranslation_EN_US.properties
@@ -0,0 +1,20 @@
+invoiceTitle=INVOICE
+invoiceDate=Date:
+invoiceNumber=Invoice #
+invoiceAmount=New Charges
+invoiceAmountPaid=Payment
+invoiceBalance=Balance
+
+accountOwnerName=Network Creator
+
+companyName=Ning, Inc.
+companyAddress=P.O. Box 1622
+companyCityProvincePostalCode=Palo Alto, CA 94302
+companyCountry=USA
+companyUrl=http://www.ning.com
+
+invoiceItemBundleName=NetworkName
+invoiceItemDescription=Description
+invoiceItemServicePeriod=Service Period
+invoiceItemAmount=Amount
+
diff --git a/util/src/test/java/com/ning/billing/util/customfield/TestFieldStore.java b/util/src/test/java/com/ning/billing/util/customfield/TestFieldStore.java
index 8a2385b..fbd2be3 100644
--- a/util/src/test/java/com/ning/billing/util/customfield/TestFieldStore.java
+++ b/util/src/test/java/com/ning/billing/util/customfield/TestFieldStore.java
@@ -79,7 +79,9 @@ public class TestFieldStore {
     @AfterClass(groups = {"util", "slow"})
     public void stopMysql()
     {
-        helper.stopMysql();
+        if (helper!= null) {
+            helper.stopMysql();
+        }
     }
 
     @Test
diff --git a/util/src/test/java/com/ning/billing/util/email/DefaultCatalogTranslationTest.java b/util/src/test/java/com/ning/billing/util/email/DefaultCatalogTranslationTest.java
new file mode 100644
index 0000000..0be05ac
--- /dev/null
+++ b/util/src/test/java/com/ning/billing/util/email/DefaultCatalogTranslationTest.java
@@ -0,0 +1,84 @@
+package com.ning.billing.util.email;/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you 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.
+ */
+
+import com.ning.billing.util.template.translation.DefaultCatalogTranslator;
+import com.ning.billing.util.template.translation.Translator;
+import com.ning.billing.util.template.translation.TranslatorConfig;
+import org.skife.config.ConfigurationObjectFactory;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+import java.util.Locale;
+
+import static org.testng.Assert.assertEquals;
+
+@Test(groups = {"fast", "email"})
+public class DefaultCatalogTranslationTest {
+    private Translator translation;
+
+    @BeforeClass(groups={"fast", "email"})
+    public void setup() {
+        final TranslatorConfig config = new ConfigurationObjectFactory(System.getProperties()).build(TranslatorConfig.class);
+        translation = new DefaultCatalogTranslator(config);
+    }
+
+    @Test(groups = {"fast", "email"})
+    public void testInitialization() {
+        String ningPlusText = "ning-plus";
+        String ningProText = "ning-pro";
+        String badText = "Bad text";
+
+        assertEquals(translation.getTranslation(Locale.US, ningPlusText), "Plus");
+        assertEquals(translation.getTranslation(Locale.US, ningProText), "Pro");
+        assertEquals(translation.getTranslation(Locale.US, badText), badText);
+
+        assertEquals(translation.getTranslation(Locale.CANADA_FRENCH, ningPlusText), "Plus en francais");
+        assertEquals(translation.getTranslation(Locale.CANADA_FRENCH, ningProText), "Pro");
+        assertEquals(translation.getTranslation(Locale.CANADA_FRENCH, badText), badText);
+
+        assertEquals(translation.getTranslation(Locale.CHINA, ningPlusText), "Plus");
+        assertEquals(translation.getTranslation(Locale.CHINA, ningProText), "Pro");
+        assertEquals(translation.getTranslation(Locale.CHINA, badText), badText);
+    }
+
+    @Test
+    public void testExistingTranslation() {
+        // if the translation exists, return the translation
+        String originalText = "ning-plus";
+        assertEquals(translation.getTranslation(Locale.US,  originalText), "Plus");
+    }
+
+    @Test
+    public void testMissingTranslation() {
+        // if the translation is missing from the file, return the original text
+        String originalText = "missing translation";
+        assertEquals(translation.getTranslation(Locale.US, originalText), originalText);
+    }
+
+    @Test
+    public void testMissingTranslationFileWithEnglishText() {
+        // if the translation file doesn't exist, return the "English" translation
+        String originalText = "ning-plus";
+        assertEquals(translation.getTranslation(Locale.CHINA, originalText), "Plus");
+    }
+
+    @Test
+    public void testMissingFileAndText() {
+        // if the file is missing, and the "English" translation is missing, return the original text
+        String originalText = "missing translation";
+        assertEquals(translation.getTranslation(Locale.CHINA, originalText), originalText);
+    }
+}
diff --git a/util/src/test/java/com/ning/billing/util/email/EmailSenderTest.java b/util/src/test/java/com/ning/billing/util/email/EmailSenderTest.java
new file mode 100644
index 0000000..1685628
--- /dev/null
+++ b/util/src/test/java/com/ning/billing/util/email/EmailSenderTest.java
@@ -0,0 +1,42 @@
+package com.ning.billing.util.email;/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you 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.
+ */
+
+import org.skife.config.ConfigurationObjectFactory;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@Test(groups = {"slow", "email"})
+public class EmailSenderTest {
+    private EmailConfig config;
+
+    @BeforeClass
+    public void setup() {
+        config = new ConfigurationObjectFactory(System.getProperties()).build(EmailConfig.class);
+    }
+
+    @Test
+    public void testSendEmail() throws Exception {
+        String html = "<html><body><h1>Test E-mail</h1></body></html>";
+        List<String> recipients = new ArrayList<String>();
+        recipients.add("killbill.ning@gmail.com");
+
+        EmailSender sender = new DefaultEmailSender(config);
+        sender.sendSecureEmail(recipients, null, "Test message", html);
+    }
+}
diff --git a/util/src/test/java/com/ning/billing/util/globallocker/TestMysqlGlobalLocker.java b/util/src/test/java/com/ning/billing/util/globallocker/TestMysqlGlobalLocker.java
index 6f2facd..be642f8 100644
--- a/util/src/test/java/com/ning/billing/util/globallocker/TestMysqlGlobalLocker.java
+++ b/util/src/test/java/com/ning/billing/util/globallocker/TestMysqlGlobalLocker.java
@@ -58,7 +58,9 @@ public class TestMysqlGlobalLocker {
 
     @AfterClass(groups = "slow")
     public void tearDown() {
-        helper.stopMysql();
+        if (helper != null) {
+            helper.stopMysql();
+        }
     }
 
     // Used as a manual test to validate the simple DAO by stepping through that locking is done and release correctly
diff --git a/util/src/test/java/com/ning/billing/util/notificationq/dao/TestNotificationSqlDao.java b/util/src/test/java/com/ning/billing/util/notificationq/dao/TestNotificationSqlDao.java
index 891cadf..d5de415 100644
--- a/util/src/test/java/com/ning/billing/util/notificationq/dao/TestNotificationSqlDao.java
+++ b/util/src/test/java/com/ning/billing/util/notificationq/dao/TestNotificationSqlDao.java
@@ -78,7 +78,9 @@ public class TestNotificationSqlDao {
     @AfterSuite(groups = "slow")
     public void stopMysql()
     {
-        helper.stopMysql();
+        if (helper != null) {
+            helper.stopMysql();
+        }
     }
 
 
diff --git a/util/src/test/java/com/ning/billing/util/notificationq/TestNotificationQueue.java b/util/src/test/java/com/ning/billing/util/notificationq/TestNotificationQueue.java
index 1a9654c..80f4a5c 100644
--- a/util/src/test/java/com/ning/billing/util/notificationq/TestNotificationQueue.java
+++ b/util/src/test/java/com/ning/billing/util/notificationq/TestNotificationQueue.java
@@ -92,7 +92,9 @@ public class TestNotificationQueue {
 
     @AfterClass(groups="slow")
     public void tearDown() {
-        helper.stopMysql();
+        if (helper != null) {
+            helper.stopMysql();
+        }
     }
 
     @BeforeTest(groups="slow")
@@ -131,7 +133,7 @@ public class TestNotificationQueue {
                 synchronized (expectedNotifications) {
                     log.info("Handler received key: " + notificationKey);
 
-                    expectedNotifications.put(notificationKey.toString(), Boolean.TRUE);
+                    expectedNotifications.put(notificationKey, Boolean.TRUE);
                     expectedNotifications.notify();
                 }
             }
@@ -432,7 +434,7 @@ public class TestNotificationQueue {
                 new NotificationQueueHandler() {
             @Override
             public void handleReadyNotification(String key, DateTime eventDateTime) {
-                    if(key.equals(notificationKey) || key.equals(notificationKey2)) { //ig nore stray events from other tests
+                    if(key.equals(notificationKey) || key.equals(notificationKey2)) { //ignore stray events from other tests
                         log.info("Received notification with key: " + notificationKey);
                         eventsReceived++;
                     }
diff --git a/util/src/test/java/com/ning/billing/util/tag/TestTagStore.java b/util/src/test/java/com/ning/billing/util/tag/TestTagStore.java
index a8d353f..0e5041a 100644
--- a/util/src/test/java/com/ning/billing/util/tag/TestTagStore.java
+++ b/util/src/test/java/com/ning/billing/util/tag/TestTagStore.java
@@ -103,7 +103,9 @@ public class TestTagStore {
     @AfterClass(groups="slow")
     public void stopMysql()
     {
-        helper.stopMysql();
+        if (helper != null) {
+            helper.stopMysql();
+        }
     }
 
     private void cleanupTags() {
@@ -113,7 +115,7 @@ public class TestTagStore {
                 public Void withHandle(Handle handle) throws Exception {
                     handle.createScript("delete from tag_definitions").execute();
                     handle.createScript("delete from tag_definition_history").execute();
-                    handle.createScript("delete from tags").execute();
+                    handle.createScript("delete from tagStore").execute();
                     handle.createScript("delete from tag_history").execute();
                     return null;
                 }
@@ -307,7 +309,7 @@ public class TestTagStore {
         try {
             tagDefinitionDao.deleteAllTagsForDefinition(definitionName, context);
         } catch (TagDefinitionApiException e) {
-            fail("Could not delete tags for tag definition", e);
+            fail("Could not delete tagStore for tag definition", e);
         }
 
         try {
@@ -332,7 +334,7 @@ public class TestTagStore {
         try {
             tagDefinitionDao.deleteAllTagsForDefinition(definitionName, context);
         } catch (TagDefinitionApiException e) {
-            fail("Could not delete tags for tag definition", e);
+            fail("Could not delete tagStore for tag definition", e);
         }
 
         try {
@@ -391,6 +393,8 @@ public class TestTagStore {
         String query = String.format("select * from audit_log a inner join tag_history th on a.record_id = th.history_record_id where a.table_name = 'tag_history' and th.id='%s' and a.change_type='INSERT'",
                                      tag.getId().toString());
         List<Map<String, Object>> result = handle.select(query);
+        handle.close();
+
         assertNotNull(result);
         assertEquals(result.size(), 1);
         assertEquals(result.get(0).get("change_type"), "INSERT");
@@ -420,6 +424,8 @@ public class TestTagStore {
         String query = String.format("select * from audit_log a inner join tag_history th on a.record_id = th.history_record_id where a.table_name = 'tag_history' and th.id='%s' and a.change_type='DELETE'",
                                      tag.getId().toString());
         List<Map<String, Object>> result = handle.select(query);
+        handle.close();
+
         assertNotNull(result);
         assertEquals(result.size(), 1);
         assertNotNull(result.get(0).get("change_date"));
diff --git a/util/src/test/java/com/ning/billing/util/validation/TestValidationManager.java b/util/src/test/java/com/ning/billing/util/validation/TestValidationManager.java
index 1848950..4e845db 100644
--- a/util/src/test/java/com/ning/billing/util/validation/TestValidationManager.java
+++ b/util/src/test/java/com/ning/billing/util/validation/TestValidationManager.java
@@ -67,7 +67,9 @@ public class TestValidationManager {
     }
 
     private void stopDatabase() {
-        helper.stopMysql();
+        if (helper != null) {
+            helper.stopMysql();
+        }
     }
 
     @Test(groups = "slow")