killbill-uncached

overdue: allow email notifications Add option to send emails

9/21/2012 9:18:57 PM

Details

diff --git a/api/src/main/java/com/ning/billing/overdue/EmailNotification.java b/api/src/main/java/com/ning/billing/overdue/EmailNotification.java
new file mode 100644
index 0000000..a8e4bf5
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/overdue/EmailNotification.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2010-2012 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.overdue;
+
+public interface EmailNotification {
+
+    public String getSubject();
+
+    public String getTemplateName();
+
+    public Boolean isHTML();
+}
diff --git a/api/src/main/java/com/ning/billing/overdue/OverdueState.java b/api/src/main/java/com/ning/billing/overdue/OverdueState.java
index 0c216b1..897f6db 100644
--- a/api/src/main/java/com/ning/billing/overdue/OverdueState.java
+++ b/api/src/main/java/com/ning/billing/overdue/OverdueState.java
@@ -20,7 +20,6 @@ import org.joda.time.Period;
 
 import com.ning.billing.junction.api.Blockable;
 
-
 public interface OverdueState<T extends Blockable> {
 
     public String getName();
@@ -40,4 +39,6 @@ public interface OverdueState<T extends Blockable> {
     public Period getReevaluationInterval() throws OverdueApiException;
 
     public Condition<T> getCondition();
+
+    public EmailNotification getEnterStateEmailNotification();
 }
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
index 198a2eb..f877937 100644
--- a/api/src/main/java/com/ning/billing/util/email/EmailSender.java
+++ b/api/src/main/java/com/ning/billing/util/email/EmailSender.java
@@ -20,5 +20,8 @@ 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;
+
+    public void sendHTMLEmail(List<String> to, List<String> cc, String subject, String htmlBody) throws IOException, EmailApiException;
+
+    public void sendPlainTextEmail(List<String> to, List<String> cc, String subject, String body) throws IOException, EmailApiException;
 }
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
index 10d7202..aa63fe3 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/notification/EmailInvoiceNotifier.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/notification/EmailInvoiceNotifier.java
@@ -86,7 +86,7 @@ public class EmailInvoiceNotifier implements InvoiceNotifier {
 
         final EmailSender sender = new DefaultEmailSender(config);
         try {
-            sender.sendSecureEmail(to, cc, subject, htmlBody);
+            sender.sendHTMLEmail(to, cc, subject, htmlBody);
         } catch (EmailApiException e) {
             throw new InvoiceApiException(e, ErrorCode.EMAIL_SENDING_FAILED);
         } catch (IOException e) {
diff --git a/overdue/src/main/java/com/ning/billing/overdue/applicator/OverdueEmailGenerator.java b/overdue/src/main/java/com/ning/billing/overdue/applicator/OverdueEmailGenerator.java
new file mode 100644
index 0000000..ec2f971
--- /dev/null
+++ b/overdue/src/main/java/com/ning/billing/overdue/applicator/OverdueEmailGenerator.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2010-2012 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.overdue.applicator;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+import com.ning.billing.account.api.Account;
+import com.ning.billing.junction.api.Blockable;
+import com.ning.billing.overdue.OverdueState;
+import com.ning.billing.overdue.config.api.BillingState;
+import com.ning.billing.util.email.templates.TemplateEngine;
+
+import com.google.inject.Inject;
+
+public class OverdueEmailGenerator {
+
+    private final TemplateEngine templateEngine;
+
+    @Inject
+    public OverdueEmailGenerator(final TemplateEngine templateEngine) {
+        this.templateEngine = templateEngine;
+    }
+
+    public <T extends Blockable> String generateEmail(final Account account, final BillingState<T> billingState,
+                                                      final T overdueable, final OverdueState<T> nextOverdueState) throws IOException {
+        final Map<String, Object> data = new HashMap<String, Object>();
+
+        // TODO raw objects for now. We eventually should respect the account locale and support translations
+        data.put("account", account);
+        data.put("billingState", billingState);
+        data.put("overdueable", overdueable);
+        data.put("nextOverdueState", nextOverdueState);
+
+        // TODO single template for all languages for now
+        return templateEngine.executeTemplate(nextOverdueState.getEnterStateEmailNotification().getTemplateName(), data);
+    }
+}
diff --git a/overdue/src/main/java/com/ning/billing/overdue/applicator/OverdueStateApplicator.java b/overdue/src/main/java/com/ning/billing/overdue/applicator/OverdueStateApplicator.java
index a5d44da..7492ae9 100644
--- a/overdue/src/main/java/com/ning/billing/overdue/applicator/OverdueStateApplicator.java
+++ b/overdue/src/main/java/com/ning/billing/overdue/applicator/OverdueStateApplicator.java
@@ -16,8 +16,10 @@
 
 package com.ning.billing.overdue.applicator;
 
+import java.io.IOException;
 import java.util.LinkedList;
 import java.util.List;
+import java.util.UUID;
 
 import org.joda.time.DateTime;
 import org.joda.time.LocalDate;
@@ -27,12 +29,15 @@ import org.slf4j.LoggerFactory;
 
 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.AccountUserApi;
 import com.ning.billing.catalog.api.ActionPolicy;
 import com.ning.billing.entitlement.api.user.EntitlementUserApi;
 import com.ning.billing.entitlement.api.user.EntitlementUserApiException;
 import com.ning.billing.entitlement.api.user.Subscription;
 import com.ning.billing.entitlement.api.user.SubscriptionBundle;
 import com.ning.billing.junction.api.Blockable;
+import com.ning.billing.junction.api.Blockable.Type;
 import com.ning.billing.junction.api.BlockingApi;
 import com.ning.billing.junction.api.BlockingApiException;
 import com.ning.billing.junction.api.DefaultBlockingState;
@@ -50,8 +55,14 @@ import com.ning.billing.util.callcontext.CallContextFactory;
 import com.ning.billing.util.callcontext.CallOrigin;
 import com.ning.billing.util.callcontext.UserType;
 import com.ning.billing.util.clock.Clock;
+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 com.google.common.collect.ImmutableList;
 import com.google.inject.Inject;
+import com.samskivert.mustache.MustacheException;
 
 public class OverdueStateApplicator<T extends Blockable> {
 
@@ -63,16 +74,23 @@ public class OverdueStateApplicator<T extends Blockable> {
     private final Clock clock;
     private final OverdueCheckPoster poster;
     private final Bus bus;
+    private final AccountUserApi accountUserApi;
     private final EntitlementUserApi entitlementUserApi;
     private final CallContextFactory factory;
+    private final OverdueEmailGenerator overdueEmailGenerator;
+    private final EmailSender emailSender;
 
     @Inject
-    public OverdueStateApplicator(final BlockingApi accessApi, final EntitlementUserApi entitlementUserApi, final Clock clock,
-                                  final OverdueCheckPoster poster, final Bus bus, final CallContextFactory factory) {
+    public OverdueStateApplicator(final BlockingApi accessApi, final AccountUserApi accountUserApi, final EntitlementUserApi entitlementUserApi,
+                                  final Clock clock, final OverdueCheckPoster poster, final OverdueEmailGenerator overdueEmailGenerator,
+                                  final EmailConfig config, final Bus bus, final CallContextFactory factory) {
         this.blockingApi = accessApi;
+        this.accountUserApi = accountUserApi;
         this.entitlementUserApi = entitlementUserApi;
         this.clock = clock;
         this.poster = poster;
+        this.overdueEmailGenerator = overdueEmailGenerator;
+        this.emailSender = new DefaultEmailSender(config);
         this.bus = bus;
         this.factory = factory;
     }
@@ -91,13 +109,16 @@ public class OverdueStateApplicator<T extends Blockable> {
             }
 
             if (previousOverdueStateName.equals(nextOverdueState.getName())) {
-                return; //That's it we are done...
+                return; // That's it, we are done...
             }
 
             storeNewState(overdueable, nextOverdueState);
 
             cancelSubscriptionsIfRequired(overdueable, nextOverdueState);
 
+            sendEmailIfRequired(billingState, overdueable, nextOverdueState);
+
+            // Add entry in notification queue
             final Period reevaluationInterval = nextOverdueState.getReevaluationInterval();
             if (!nextOverdueState.isClearState()) {
                 createFutureNotification(overdueable, clock.getUTCNow().plus(reevaluationInterval));
@@ -158,7 +179,7 @@ public class OverdueStateApplicator<T extends Blockable> {
             return;
         }
         try {
-            ActionPolicy actionPolicy = null;
+            final ActionPolicy actionPolicy;
             switch (nextOverdueState.getSubscriptionCancellationPolicy()) {
                 case END_OF_TERM:
                     actionPolicy = ActionPolicy.END_OF_TERM;
@@ -194,4 +215,62 @@ public class OverdueStateApplicator<T extends Blockable> {
             }
         }
     }
+
+    private void sendEmailIfRequired(final BillingState<T> billingState, final T overdueable, final OverdueState<T> nextOverdueState) {
+        // Note: we don't want to fail the full refresh call because sending the email failed.
+        // That's the reason why we catch all exceptions here.
+        // The alternative would be to: throw new OverdueApiException(e, ErrorCode.EMAIL_SENDING_FAILED);
+
+        // If sending is not configured, skip
+        if (nextOverdueState.getEnterStateEmailNotification() == null) {
+            return;
+        }
+
+        // Retrieve the account
+        final Account account;
+        final Type overdueableType = Blockable.Type.get(overdueable);
+        try {
+            if (Type.SUBSCRIPTION.equals(overdueableType)) {
+                final UUID bundleId = ((Subscription) overdueable).getBundleId();
+                final SubscriptionBundle bundle = entitlementUserApi.getBundleFromId(bundleId);
+                account = accountUserApi.getAccountById(bundle.getAccountId());
+            } else if (Type.SUBSCRIPTION_BUNDLE.equals(overdueableType)) {
+                final UUID bundleId = ((SubscriptionBundle) overdueable).getId();
+                final SubscriptionBundle bundle = entitlementUserApi.getBundleFromId(bundleId);
+                account = accountUserApi.getAccountById(bundle.getAccountId());
+            } else if (Type.ACCOUNT.equals(overdueableType)) {
+                account = (Account) overdueable;
+            } else {
+                log.warn("Unable to retrieve account for overdueable {} (type {})", overdueable.getId(), overdueableType);
+                return;
+            }
+        } catch (EntitlementUserApiException e) {
+            log.warn(String.format("Unable to retrieve account for overdueable %s (type %s)", overdueable.getId(), overdueableType), e);
+            return;
+        } catch (AccountApiException e) {
+            log.warn(String.format("Unable to retrieve account for overdueable %s (type %s)", overdueable.getId(), overdueableType), e);
+            return;
+        }
+
+        final List<String> to = ImmutableList.<String>of(account.getEmail());
+        // TODO - should we look at the account CC: list?
+        final List<String> cc = ImmutableList.<String>of();
+        final String subject = nextOverdueState.getEnterStateEmailNotification().getSubject();
+
+        try {
+            // Generate and send the email
+            final String emailBody = overdueEmailGenerator.generateEmail(account, billingState, overdueable, nextOverdueState);
+            if (nextOverdueState.getEnterStateEmailNotification().isHTML()) {
+                emailSender.sendHTMLEmail(to, cc, subject, emailBody);
+            } else {
+                emailSender.sendPlainTextEmail(to, cc, subject, emailBody);
+            }
+        } catch (IOException e) {
+            log.warn(String.format("Unable to generate or send overdue notification email for account %s and overdueable %s", account.getId(), overdueable.getId()), e);
+        } catch (EmailApiException e) {
+            log.warn(String.format("Unable to send overdue notification email for account %s and overdueable %s", account.getId(), overdueable.getId()), e);
+        } catch (MustacheException e) {
+            log.warn(String.format("Unable to generate overdue notification email for account %s and overdueable %s", account.getId(), overdueable.getId()), e);
+        }
+    }
 }
diff --git a/overdue/src/main/java/com/ning/billing/overdue/config/DefaultEmailNotification.java b/overdue/src/main/java/com/ning/billing/overdue/config/DefaultEmailNotification.java
new file mode 100644
index 0000000..a5491d3
--- /dev/null
+++ b/overdue/src/main/java/com/ning/billing/overdue/config/DefaultEmailNotification.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2010-2012 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.overdue.config;
+
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlElement;
+
+import com.ning.billing.overdue.EmailNotification;
+
+@XmlAccessorType(XmlAccessType.NONE)
+public class DefaultEmailNotification implements EmailNotification {
+
+    @XmlElement(required = true, name = "subject")
+    private String subject;
+
+    @XmlElement(required = true, name = "templateName")
+    private String templateName;
+
+    @XmlElement(required = false, name = "isHTML")
+    private Boolean isHTML = false;
+
+    @Override
+    public String getSubject() {
+        return subject;
+    }
+
+    @Override
+    public String getTemplateName() {
+        return templateName;
+    }
+
+    @Override
+    public Boolean isHTML() {
+        return isHTML;
+    }
+}
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 7cbc01f..d95b487 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
@@ -27,6 +27,7 @@ import org.joda.time.Period;
 import com.ning.billing.ErrorCode;
 import com.ning.billing.catalog.api.TimeUnit;
 import com.ning.billing.junction.api.Blockable;
+import com.ning.billing.overdue.EmailNotification;
 import com.ning.billing.overdue.OverdueApiException;
 import com.ning.billing.overdue.OverdueCancellationPolicicy;
 import com.ning.billing.overdue.OverdueState;
@@ -64,8 +65,10 @@ public class DefaultOverdueState<T extends Blockable> extends ValidatingConfig<O
     @XmlElement(required = false, name = "autoReevaluationInterval")
     private DefaultDuration autoReevaluationInterval;
 
+    @XmlElement(required = false, name = "enterStateEmailNotification")
+    private DefaultEmailNotification enterStateEmailNotification;
+
     //Other actions could include
-    // - send email
     // - trigger payment retry?
     // - add tagStore to bundle/account
     // - set payment failure email template
@@ -163,4 +166,9 @@ public class DefaultOverdueState<T extends Blockable> extends ValidatingConfig<O
     public int getDaysBetweenPaymentRetries() {
         return 8;
     }
+
+    @Override
+    public EmailNotification getEnterStateEmailNotification() {
+        return enterStateEmailNotification;
+    }
 }
diff --git a/overdue/src/main/java/com/ning/billing/overdue/glue/DefaultOverdueModule.java b/overdue/src/main/java/com/ning/billing/overdue/glue/DefaultOverdueModule.java
index 2072e5e..baf8700 100644
--- a/overdue/src/main/java/com/ning/billing/overdue/glue/DefaultOverdueModule.java
+++ b/overdue/src/main/java/com/ning/billing/overdue/glue/DefaultOverdueModule.java
@@ -27,6 +27,7 @@ import com.ning.billing.overdue.OverdueProperties;
 import com.ning.billing.overdue.OverdueService;
 import com.ning.billing.overdue.OverdueUserApi;
 import com.ning.billing.overdue.api.DefaultOverdueUserApi;
+import com.ning.billing.overdue.applicator.OverdueEmailGenerator;
 import com.ning.billing.overdue.service.DefaultOverdueService;
 import com.ning.billing.overdue.service.ExtendedOverdueService;
 import com.ning.billing.overdue.wrapper.OverdueWrapperFactory;
@@ -42,6 +43,7 @@ public class DefaultOverdueModule extends AbstractModule implements OverdueModul
         // internal bindings
         installOverdueService();
         installOverdueWrapperFactory();
+        installOverdueEmail();
 
         final OverdueProperties config = new ConfigurationObjectFactory(System.getProperties()).build(OverdueProperties.class);
         bind(OverdueProperties.class).toInstance(config);
@@ -58,6 +60,10 @@ public class DefaultOverdueModule extends AbstractModule implements OverdueModul
         bind(OverdueWrapperFactory.class).asEagerSingleton();
     }
 
+    protected void installOverdueEmail() {
+        bind(OverdueEmailGenerator.class).asEagerSingleton();
+    }
+
     @Override
     public void installOverdueUserApi() {
         bind(OverdueUserApi.class).to(DefaultOverdueUserApi.class).asEagerSingleton();
diff --git a/overdue/src/test/java/com/ning/billing/overdue/config/TestOverdueConfig.java b/overdue/src/test/java/com/ning/billing/overdue/config/TestOverdueConfig.java
index 2b2c264..534f12f 100644
--- a/overdue/src/test/java/com/ning/billing/overdue/config/TestOverdueConfig.java
+++ b/overdue/src/test/java/com/ning/billing/overdue/config/TestOverdueConfig.java
@@ -19,45 +19,59 @@ package com.ning.billing.overdue.config;
 import java.io.ByteArrayInputStream;
 import java.io.InputStream;
 
+import org.testng.Assert;
 import org.testng.annotations.Test;
 
+import com.ning.billing.overdue.EmailNotification;
 import com.ning.billing.overdue.OverdueTestSuite;
 import com.ning.billing.util.config.XMLLoader;
 
 public class TestOverdueConfig extends OverdueTestSuite {
+
     @Test(groups = "fast")
     public void testParseConfig() throws Exception {
         final String xml = "<overdueConfig>" +
-                "   <bundleOverdueStates>" +
-                "       <state name=\"OD1\">" +
-                "           <condition>" +
-                "               <timeSinceEarliestUnpaidInvoiceEqualsOrExceeds>" +
-                "                   <unit>MONTHS</unit><number>1</number>" +
-                "               </timeSinceEarliestUnpaidInvoiceEqualsOrExceeds>" +
-                "           </condition>" +
-                "           <externalMessage>Reached OD1</externalMessage>" +
-                "           <blockChanges>true</blockChanges>" +
-                "           <disableEntitlementAndChangesBlocked>false</disableEntitlementAndChangesBlocked>" +
-                "           <autoReevaluationInterval>" +
-                "               <unit>DAYS</unit><number>15</number>" +
-                "           </autoReevaluationInterval>" +
-                "       </state>" +
-                "       <state name=\"OD2\">" +
-                "           <condition>" +
-                "               <timeSinceEarliestUnpaidInvoiceEqualsOrExceeds>" +
-                "                   <unit>MONTHS</unit><number>2</number>" +
-                "               </timeSinceEarliestUnpaidInvoiceEqualsOrExceeds>" +
-                "           </condition>" +
-                "           <externalMessage>Reached OD1</externalMessage>" +
-                "           <blockChanges>true</blockChanges>" +
-                "           <disableEntitlementAndChangesBlocked>true</disableEntitlementAndChangesBlocked>" +
-                "           <autoReevaluationInterval>" +
-                "               <unit>DAYS</unit><number>15</number>" +
-                "           </autoReevaluationInterval>" +
-                "       </state>" +
-                "   </bundleOverdueStates>" +
-                "</overdueConfig>";
+                           "   <bundleOverdueStates>" +
+                           "       <state name=\"OD1\">" +
+                           "           <condition>" +
+                           "               <timeSinceEarliestUnpaidInvoiceEqualsOrExceeds>" +
+                           "                   <unit>MONTHS</unit><number>1</number>" +
+                           "               </timeSinceEarliestUnpaidInvoiceEqualsOrExceeds>" +
+                           "           </condition>" +
+                           "           <externalMessage>Reached OD1</externalMessage>" +
+                           "           <blockChanges>true</blockChanges>" +
+                           "           <disableEntitlementAndChangesBlocked>false</disableEntitlementAndChangesBlocked>" +
+                           "           <autoReevaluationInterval>" +
+                           "               <unit>DAYS</unit><number>15</number>" +
+                           "           </autoReevaluationInterval>" +
+                           "       </state>" +
+                           "       <state name=\"OD2\">" +
+                           "           <condition>" +
+                           "               <timeSinceEarliestUnpaidInvoiceEqualsOrExceeds>" +
+                           "                   <unit>MONTHS</unit><number>2</number>" +
+                           "               </timeSinceEarliestUnpaidInvoiceEqualsOrExceeds>" +
+                           "           </condition>" +
+                           "           <externalMessage>Reached OD1</externalMessage>" +
+                           "           <blockChanges>true</blockChanges>" +
+                           "           <disableEntitlementAndChangesBlocked>true</disableEntitlementAndChangesBlocked>" +
+                           "           <autoReevaluationInterval>" +
+                           "               <unit>DAYS</unit><number>15</number>" +
+                           "           </autoReevaluationInterval>" +
+                           "           <enterStateEmailNotification>" +
+                           "               <subject>ToTo</subject><templateName>Titi</templateName>" +
+                           "           </enterStateEmailNotification>" +
+                           "       </state>" +
+                           "   </bundleOverdueStates>" +
+                           "</overdueConfig>";
         final InputStream is = new ByteArrayInputStream(xml.getBytes());
         final OverdueConfig c = XMLLoader.getObjectFromStreamNoValidation(is, OverdueConfig.class);
+        Assert.assertEquals(c.getBundleStateSet().size(), 2);
+
+        Assert.assertNull(c.getBundleStateSet().getStates()[0].getEnterStateEmailNotification());
+
+        final EmailNotification secondNotification = c.getBundleStateSet().getStates()[1].getEnterStateEmailNotification();
+        Assert.assertEquals(secondNotification.getSubject(), "ToTo");
+        Assert.assertEquals(secondNotification.getTemplateName(), "Titi");
+        Assert.assertFalse(secondNotification.isHTML());
     }
 }
diff --git a/overdue/src/test/java/com/ning/billing/overdue/notification/TestOverdueCheckNotifier.java b/overdue/src/test/java/com/ning/billing/overdue/notification/TestOverdueCheckNotifier.java
index d2cecf1..2cd0dc0 100644
--- a/overdue/src/test/java/com/ning/billing/overdue/notification/TestOverdueCheckNotifier.java
+++ b/overdue/src/test/java/com/ning/billing/overdue/notification/TestOverdueCheckNotifier.java
@@ -63,6 +63,8 @@ import com.ning.billing.util.clock.Clock;
 import com.ning.billing.util.clock.ClockMock;
 import com.ning.billing.util.customfield.dao.AuditedCustomFieldDao;
 import com.ning.billing.util.customfield.dao.CustomFieldDao;
+import com.ning.billing.util.email.EmailModule;
+import com.ning.billing.util.email.templates.TemplateModule;
 import com.ning.billing.util.globallocker.GlobalLocker;
 import com.ning.billing.util.globallocker.MySqlGlobalLocker;
 import com.ning.billing.util.glue.BusModule;
@@ -130,6 +132,8 @@ public class TestOverdueCheckNotifier extends OverdueTestSuiteWithEmbeddedDB {
                 bind(GlobalLocker.class).to(MySqlGlobalLocker.class).asEagerSingleton();
                 bind(ChargeThruApi.class).toInstance(Mockito.mock(ChargeThruApi.class));
                 install(new MockJunctionModule());
+                install(new EmailModule());
+                install(new TemplateModule());
             }
         });
 
diff --git a/overdue/src/test/java/com/ning/billing/overdue/OverdueTestBase.java b/overdue/src/test/java/com/ning/billing/overdue/OverdueTestBase.java
index 6b8d67e..477d1bb 100644
--- a/overdue/src/test/java/com/ning/billing/overdue/OverdueTestBase.java
+++ b/overdue/src/test/java/com/ning/billing/overdue/OverdueTestBase.java
@@ -60,11 +60,15 @@ import com.ning.billing.overdue.service.DefaultOverdueService;
 import com.ning.billing.overdue.wrapper.OverdueWrapperFactory;
 import com.ning.billing.util.bus.BusService;
 import com.ning.billing.util.clock.ClockMock;
+import com.ning.billing.util.email.EmailModule;
+import com.ning.billing.util.email.templates.TemplateModule;
 import com.ning.billing.util.glue.CallContextModule;
 import com.ning.billing.util.glue.NotificationQueueModule;
 import com.ning.billing.util.notificationq.NotificationQueueService.NotificationQueueAlreadyExists;
 
-@Guice(modules = {DefaultOverdueModule.class, OverdueListenerTesterModule.class, MockClockModule.class, ApplicatorMockJunctionModule.class, CallContextModule.class, CatalogModule.class, MockInvoiceModule.class, MockPaymentModule.class, NotificationQueueModule.class, TestDbiModule.class})
+@Guice(modules = {DefaultOverdueModule.class, OverdueListenerTesterModule.class, MockClockModule.class, ApplicatorMockJunctionModule.class,
+                  CallContextModule.class, CatalogModule.class, MockInvoiceModule.class, MockPaymentModule.class, NotificationQueueModule.class,
+                  EmailModule.class, TemplateModule.class, TestDbiModule.class})
 public abstract class OverdueTestBase extends OverdueTestSuiteWithEmbeddedDB {
     protected final String configXml =
             "<overdueConfig>" +
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
index 48d6594..a238c8c 100644
--- a/util/src/main/java/com/ning/billing/util/email/DefaultEmailSender.java
+++ b/util/src/main/java/com/ning/billing/util/email/DefaultEmailSender.java
@@ -16,17 +16,22 @@
 
 package com.ning.billing.util.email;
 
+import java.io.IOException;
 import java.util.List;
 
+import org.apache.commons.mail.Email;
 import org.apache.commons.mail.EmailException;
 import org.apache.commons.mail.HtmlEmail;
+import org.apache.commons.mail.SimpleEmail;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import com.google.inject.Inject;
 import com.ning.billing.ErrorCode;
 
+import com.google.inject.Inject;
+
 public class DefaultEmailSender implements EmailSender {
+
     private final Logger log = LoggerFactory.getLogger(EmailSender.class);
     private final EmailConfig config;
 
@@ -36,11 +41,31 @@ public class DefaultEmailSender implements EmailSender {
     }
 
     @Override
-    public void sendSecureEmail(final List<String> to, final List<String> cc, final String subject, final String htmlBody) throws EmailApiException {
-        final HtmlEmail email;
+    public void sendHTMLEmail(final List<String> to, final List<String> cc, final String subject, final String htmlBody) throws EmailApiException {
+        final HtmlEmail email = new HtmlEmail();
+        try {
+            email.setHtmlMsg(htmlBody);
+        } catch (EmailException e) {
+            throw new EmailApiException(e, ErrorCode.EMAIL_SENDING_FAILED);
+        }
+
+        sendEmail(to, cc, subject, email);
+    }
+
+    @Override
+    public void sendPlainTextEmail(final List<String> to, final List<String> cc, final String subject, final String body) throws IOException, EmailApiException {
+        final SimpleEmail email = new SimpleEmail();
         try {
-            email = new HtmlEmail();
+            email.setMsg(body);
+        } catch (EmailException e) {
+            throw new EmailApiException(e, ErrorCode.EMAIL_SENDING_FAILED);
+        }
 
+        sendEmail(to, cc, subject, email);
+    }
+
+    private void sendEmail(final List<String> to, final List<String> cc, final String subject, final Email email) throws EmailApiException {
+        try {
             email.setSmtpPort(config.getSmtpPort());
             if (config.useSmtpAuth()) {
                 email.setAuthentication(config.getSmtpUserName(), config.getSmtpPassword());
@@ -49,7 +74,6 @@ public class DefaultEmailSender implements EmailSender {
             email.setFrom(config.getDefaultFrom());
 
             email.setSubject(subject);
-            email.setHtmlMsg(htmlBody);
 
             if (to != null) {
                 for (final String recipient : to) {
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
index 81b40d9..0fb604f 100644
--- a/util/src/test/java/com/ning/billing/util/email/EmailSenderTest.java
+++ b/util/src/test/java/com/ning/billing/util/email/EmailSenderTest.java
@@ -39,6 +39,6 @@ public class EmailSenderTest extends UtilTestSuite {
         recipients.add("killbill.ning@gmail.com");
 
         final EmailSender sender = new DefaultEmailSender(config);
-        sender.sendSecureEmail(recipients, null, "Test message", html);
+        sender.sendHTMLEmail(recipients, null, "Test message", html);
     }
 }