You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@fineract.apache.org by na...@apache.org on 2017/11/20 13:33:54 UTC

[3/7] fineract git commit: Email Campaigns PR

http://git-wip-us.apache.org/repos/asf/fineract/blob/c338c175/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/service/EmailCampaignWritePlatformCommandHandlerImpl.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/service/EmailCampaignWritePlatformCommandHandlerImpl.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/service/EmailCampaignWritePlatformCommandHandlerImpl.java
new file mode 100644
index 0000000..66bd97b
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/service/EmailCampaignWritePlatformCommandHandlerImpl.java
@@ -0,0 +1,775 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF 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 org.apache.fineract.infrastructure.campaigns.email.service;
+
+import com.github.mustachejava.DefaultMustacheFactory;
+import com.github.mustachejava.Mustache;
+import com.github.mustachejava.MustacheFactory;
+import com.google.gson.Gson;
+import org.apache.commons.lang.StringUtils;
+import org.codehaus.jackson.map.ObjectMapper;
+import org.codehaus.jackson.type.TypeReference;
+import org.joda.time.DateTimeZone;
+import org.joda.time.LocalDate;
+import org.joda.time.LocalDateTime;
+import org.joda.time.format.DateTimeFormat;
+import org.joda.time.format.DateTimeFormatter;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.api.JsonQuery;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder;
+import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenant;
+import org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityException;
+import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
+import org.apache.fineract.infrastructure.core.service.DateUtils;
+import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
+import org.apache.fineract.infrastructure.dataqueries.data.GenericResultsetData;
+import org.apache.fineract.infrastructure.dataqueries.domain.*;
+import org.apache.fineract.infrastructure.dataqueries.exception.ReportNotFoundException;
+import org.apache.fineract.infrastructure.dataqueries.service.GenericDataService;
+import org.apache.fineract.infrastructure.dataqueries.service.ReadReportingService;
+import org.apache.fineract.infrastructure.documentmanagement.contentrepository.FileSystemContentRepository;
+import org.apache.fineract.infrastructure.reportmailingjob.data.ReportMailingJobEmailData;
+import org.apache.fineract.infrastructure.reportmailingjob.domain.ReportMailingJob;
+import org.apache.fineract.infrastructure.reportmailingjob.domain.ReportMailingJobEmailAttachmentFileFormat;
+import org.apache.fineract.infrastructure.reportmailingjob.helper.IPv4Helper;
+import org.apache.fineract.infrastructure.campaigns.email.data.EmailMessageWithAttachmentData;
+import org.apache.fineract.infrastructure.campaigns.email.domain.*;
+import org.apache.fineract.infrastructure.campaigns.email.exception.EmailCampaignMustBeClosedToBeDeletedException;
+import org.apache.fineract.infrastructure.campaigns.email.exception.EmailCampaignMustBeClosedToEditException;
+import org.apache.fineract.infrastructure.campaigns.email.exception.EmailCampaignNotFound;
+import org.apache.fineract.infrastructure.jobs.annotation.CronTarget;
+import org.apache.fineract.infrastructure.jobs.exception.JobExecutionException;
+import org.apache.fineract.infrastructure.jobs.service.JobName;
+import org.apache.fineract.infrastructure.jobs.service.SchedularWritePlatformService;
+import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
+import org.apache.fineract.infrastructure.campaigns.email.data.PreviewCampaignMessage;
+import org.apache.fineract.infrastructure.campaigns.email.data.EmailCampaignData;
+import org.apache.fineract.infrastructure.campaigns.email.data.EmailCampaignValidator;
+import org.apache.fineract.organisation.staff.domain.Staff;
+import org.apache.fineract.portfolio.calendar.service.CalendarUtils;
+import org.apache.fineract.portfolio.client.domain.Client;
+import org.apache.fineract.portfolio.client.domain.ClientRepositoryWrapper;
+import org.apache.fineract.portfolio.loanaccount.domain.Loan;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanRepository;
+import org.apache.fineract.portfolio.loanaccount.service.LoanReadPlatformService;
+import org.apache.fineract.portfolio.savings.domain.SavingsAccount;
+import org.apache.fineract.portfolio.savings.domain.SavingsAccountRepository;
+import org.apache.fineract.template.domain.TemplateRepository;
+import org.apache.fineract.template.service.TemplateMergeService;
+import org.apache.fineract.useradministration.domain.AppUser;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.dao.DataIntegrityViolationException;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import javax.mail.internet.AddressException;
+import javax.mail.internet.InternetAddress;
+import java.io.*;
+import java.util.*;
+
+@Service
+public class EmailCampaignWritePlatformCommandHandlerImpl implements EmailCampaignWritePlatformService {
+
+
+    private final static Logger logger = LoggerFactory.getLogger(EmailCampaignWritePlatformCommandHandlerImpl.class);
+
+    private final PlatformSecurityContext context;
+
+    private final EmailCampaignRepository emailCampaignRepository;
+    private final EmailCampaignValidator emailCampaignValidator;
+    private final EmailCampaignReadPlatformService emailCampaignReadPlatformService;
+    private final ReportRepository reportRepository;
+    private final TemplateRepository templateRepository;
+    private final TemplateMergeService templateMergeService;
+    private final EmailMessageRepository emailMessageRepository;
+    private final ClientRepositoryWrapper clientRepositoryWrapper;
+    private final SchedularWritePlatformService schedularWritePlatformService;
+    private final ReadReportingService readReportingService;
+    private final GenericDataService genericDataService;
+    private final FromJsonHelper fromJsonHelper;
+    private final ReportParameterUsageRepository reportParameterUsageRepository;
+    private final LoanRepository loanRepository;
+    private final SavingsAccountRepository savingsAccountRepository;
+    private final EmailMessageJobEmailService emailMessageJobEmailService;
+
+
+
+
+    @Autowired
+    public EmailCampaignWritePlatformCommandHandlerImpl(final PlatformSecurityContext context, final EmailCampaignRepository emailCampaignRepository,
+        final EmailCampaignValidator emailCampaignValidator,final EmailCampaignReadPlatformService emailCampaignReadPlatformService,final ReportParameterUsageRepository reportParameterUsageRepository,
+        final ReportRepository reportRepository,final TemplateRepository templateRepository, final TemplateMergeService templateMergeService,
+        final EmailMessageRepository emailMessageRepository,final ClientRepositoryWrapper clientRepositoryWrapper,final SchedularWritePlatformService schedularWritePlatformService,
+        final ReadReportingService readReportingService, final GenericDataService genericDataService,final FromJsonHelper fromJsonHelper,
+        final LoanRepository loanRepository,final SavingsAccountRepository savingsAccountRepository,final EmailMessageJobEmailService emailMessageJobEmailService) {
+        this.context = context;
+        this.emailCampaignRepository = emailCampaignRepository;
+        this.emailCampaignValidator = emailCampaignValidator;
+        this.emailCampaignReadPlatformService = emailCampaignReadPlatformService;
+        this.reportRepository = reportRepository;
+        this.templateRepository = templateRepository;
+        this.templateMergeService = templateMergeService;
+        this.emailMessageRepository = emailMessageRepository;
+        this.clientRepositoryWrapper = clientRepositoryWrapper;
+        this.schedularWritePlatformService = schedularWritePlatformService;
+        this.readReportingService = readReportingService;
+        this.genericDataService = genericDataService;
+        this.fromJsonHelper = fromJsonHelper;
+        this.reportParameterUsageRepository = reportParameterUsageRepository;
+        this.loanRepository = loanRepository;
+        this.savingsAccountRepository = savingsAccountRepository;
+        this.emailMessageJobEmailService = emailMessageJobEmailService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult create(JsonCommand command) {
+
+        final AppUser currentUser = this.context.authenticatedUser();
+
+        this.emailCampaignValidator.validateCreate(command.json());
+
+        final Long businessRuleId = command.longValueOfParameterNamed(EmailCampaignValidator.businessRuleId);
+
+        final Report businessRule  = this.reportRepository.findOne(businessRuleId);
+        if(businessRule == null){
+            throw new ReportNotFoundException(businessRuleId);
+        }
+
+        final Long reportId = command.longValueOfParameterNamed(EmailCampaignValidator.stretchyReportId);
+
+        final Report report  = this.reportRepository.findOne(reportId);
+        if(report == null){
+            throw new ReportNotFoundException(reportId);
+        }
+        //find all report parameters and store them as json string
+        final Set<ReportParameterUsage> reportParameterUsages = report.getReportParameterUsages();
+        final Map<String,String> stretchyReportParams = new HashMap<>();
+
+        if(reportParameterUsages !=null && !reportParameterUsages.isEmpty()){
+            for(final ReportParameterUsage reportParameterUsage : reportParameterUsages){
+               stretchyReportParams.put(reportParameterUsage.getReportParameterName(),"");
+            }
+        }
+
+
+        EmailCampaign emailCampaign = EmailCampaign.instance(currentUser,businessRule,report,command);
+        emailCampaign.setStretchyReportParamMap(new Gson().toJson(stretchyReportParams));
+
+        this.emailCampaignRepository.save(emailCampaign);
+
+        return new CommandProcessingResultBuilder() //
+                .withCommandId(command.commandId()) //
+                .withEntityId(emailCampaign.getId()) //
+                .build();
+    }
+    @Transactional
+    @Override
+    public CommandProcessingResult update(final Long resourceId, final JsonCommand command) {
+        try{
+            final AppUser currentUser = this.context.authenticatedUser();
+
+            this.emailCampaignValidator.validateForUpdate(command.json());
+            final EmailCampaign emailCampaign = this.emailCampaignRepository.findOne(resourceId);
+
+            if(emailCampaign == null){ throw new EmailCampaignNotFound(resourceId);}
+            if(emailCampaign.isActive()){ throw new EmailCampaignMustBeClosedToEditException(emailCampaign.getId());}
+            final Map<String, Object> changes = emailCampaign.update(command);
+
+            if(changes.containsKey(EmailCampaignValidator.businessRuleId)){
+                final Long newValue = command.longValueOfParameterNamed(EmailCampaignValidator.businessRuleId);
+                final Report reportId = this.reportRepository.findOne(newValue);
+                if(reportId == null){ throw new ReportNotFoundException(newValue);}
+                emailCampaign.updateBusinessRuleId(reportId);
+
+            }
+
+            if(!changes.isEmpty()){
+                this.emailCampaignRepository.saveAndFlush(emailCampaign);
+            }
+            return new CommandProcessingResultBuilder() //
+                    .withCommandId(command.commandId()) //
+                    .withEntityId(resourceId) //
+                    .with(changes) //
+                    .build();
+        }catch(final DataIntegrityViolationException dve){
+            handleDataIntegrityIssues(command, dve);
+            return CommandProcessingResult.empty();
+        }
+
+    }
+    @Transactional
+    @Override
+    public CommandProcessingResult delete(final Long resourceId) {
+        final AppUser currentUser = this.context.authenticatedUser();
+
+        final EmailCampaign emailCampaign = this.emailCampaignRepository.findOne(resourceId);
+
+        if(emailCampaign == null){ throw new EmailCampaignNotFound(resourceId);}
+        if(emailCampaign.isActive()){ throw new EmailCampaignMustBeClosedToBeDeletedException(emailCampaign.getId());}
+
+        /*
+          Do not delete but set a boolean is_visible to zero
+         */
+        emailCampaign.delete();
+        this.emailCampaignRepository.saveAndFlush(emailCampaign);
+
+        return new CommandProcessingResultBuilder() //
+                .withEntityId(emailCampaign.getId()) //
+                .build();
+
+    }
+
+
+    private void insertDirectCampaignIntoEmailOutboundTable(final String emailParams, final String emailSubject,
+                                                          final String messageTemplate,final String campaignName,final Long campaignId){
+        try{
+            HashMap<String,String> campaignParams = new ObjectMapper().readValue(emailParams, new TypeReference<HashMap<String,String>>(){});
+
+            HashMap<String,String> queryParamForRunReport =  new ObjectMapper().readValue(emailParams, new TypeReference<HashMap<String,String>>(){});
+
+            List<HashMap<String,Object>> runReportObject = this.getRunReportByServiceImpl(campaignParams.get("reportName"),queryParamForRunReport);
+
+            if(runReportObject !=null){
+                for(HashMap<String,Object> entry : runReportObject){
+                    String message = this.compileEmailTemplate(messageTemplate, campaignName, entry);
+                    Integer clientId = (Integer)entry.get("id");
+                    EmailCampaign emailCampaign = this.emailCampaignRepository.findOne(campaignId);
+                    Client client =  this.clientRepositoryWrapper.findOneWithNotFoundDetection(clientId.longValue());
+                    String emailAddress = client.emailAddress();
+
+                    if(emailAddress !=null && isValidEmail(emailAddress)) {
+                        EmailMessage emailMessage = EmailMessage.pendingEmail(null,client,null,emailCampaign,emailSubject,message,emailAddress,campaignName);
+                        this.emailMessageRepository.save(emailMessage);
+                    }
+                }
+            }
+        }catch(final IOException e){
+            // TODO throw something here
+        }
+
+    }
+
+    public static boolean isValidEmail(String email) {
+
+        boolean isValid = true;
+
+        try {
+
+            InternetAddress emailO = new InternetAddress(email);
+            emailO.validate();
+
+        } catch (AddressException ex) {
+
+            isValid = false;
+        }
+        return isValid;
+    }
+
+    @Override
+    @CronTarget(jobName = JobName.UPDATE_EMAIL_OUTBOUND_WITH_CAMPAIGN_MESSAGE)
+    public void storeTemplateMessageIntoEmailOutBoundTable() throws JobExecutionException {
+        final Collection<EmailCampaignData>  emailCampaignDataCollection = this.emailCampaignReadPlatformService.retrieveAllScheduleActiveCampaign();
+        if(emailCampaignDataCollection != null){
+            for(EmailCampaignData  emailCampaignData : emailCampaignDataCollection){
+                LocalDateTime tenantDateNow = tenantDateTime();
+                LocalDateTime nextTriggerDate = emailCampaignData.getNextTriggerDate().toLocalDateTime();
+
+                logger.info("tenant time " + tenantDateNow.toString() + " trigger time "+nextTriggerDate.toString());
+                if(nextTriggerDate.isBefore(tenantDateNow)){
+                    insertDirectCampaignIntoEmailOutboundTable(emailCampaignData.getParamValue(),emailCampaignData.getEmailSubject(), emailCampaignData.getMessage(),emailCampaignData.getCampaignName(),emailCampaignData.getId());
+                    this.updateTriggerDates(emailCampaignData.getId());
+                }
+            }
+        }
+    }
+
+    private void updateTriggerDates(Long campaignId){
+        final EmailCampaign emailCampaign = this.emailCampaignRepository.findOne(campaignId);
+        if(emailCampaign == null){
+            throw new EmailCampaignNotFound(campaignId);
+        }
+        LocalDateTime nextTriggerDate = emailCampaign.getNextTriggerDate();
+        emailCampaign.setLastTriggerDate(nextTriggerDate.toDate());
+        //calculate new trigger date and insert into next trigger date
+
+        /**
+         * next run time has to be in the future if not calculate a new future date
+         */
+        LocalDate nextRuntime = CalendarUtils.getNextRecurringDate(emailCampaign.getRecurrence(), emailCampaign.getNextTriggerDate().toLocalDate(), nextTriggerDate.toLocalDate()) ;
+        if(nextRuntime.isBefore(DateUtils.getLocalDateOfTenant())){ // means next run time is in the past calculate a new future date
+            nextRuntime = CalendarUtils.getNextRecurringDate(emailCampaign.getRecurrence(), emailCampaign.getNextTriggerDate().toLocalDate(),DateUtils.getLocalDateOfTenant()) ;
+        }
+        final LocalDateTime getTime = emailCampaign.getRecurrenceStartDateTime();
+        final String dateString = nextRuntime.toString() + " " + getTime.getHourOfDay()+":"+getTime.getMinuteOfHour()+":"+getTime.getSecondOfMinute();
+        final DateTimeFormatter simpleDateFormat = DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss");
+        final LocalDateTime newTriggerDateWithTime = LocalDateTime.parse(dateString,simpleDateFormat);
+
+
+        emailCampaign.setNextTriggerDate(newTriggerDateWithTime.toDate());
+        this.emailCampaignRepository.saveAndFlush(emailCampaign);
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult activateEmailCampaign(Long campaignId, JsonCommand command) {
+        final AppUser currentUser = this.context.authenticatedUser();
+
+        this.emailCampaignValidator.validateActivation(command.json());
+
+        final EmailCampaign emailCampaign = this.emailCampaignRepository.findOne(campaignId);
+
+        if(emailCampaign == null){
+            throw new EmailCampaignNotFound(campaignId);
+        }
+
+
+
+        final Locale locale = command.extractLocale();
+        final DateTimeFormatter fmt = DateTimeFormat.forPattern(command.dateFormat()).withLocale(locale);
+        final LocalDate activationDate = command.localDateValueOfParameterNamed("activationDate");
+
+        emailCampaign.activate(currentUser, fmt, activationDate);
+
+        this.emailCampaignRepository.saveAndFlush(emailCampaign);
+
+        if(emailCampaign.isDirect()){
+            insertDirectCampaignIntoEmailOutboundTable(emailCampaign.getParamValue(),emailCampaign.getEmailSubject(),emailCampaign.getEmailMessage(),emailCampaign.getCampaignName(), emailCampaign.getId());
+        }else {
+            if (emailCampaign.isSchedule()) {
+
+                /**
+                 * if recurrence start date is in the future calculate
+                 * next trigger date if not use recurrence start date us next trigger
+                 * date when activating
+                 */
+                LocalDate nextTriggerDate = null;
+                if(emailCampaign.getRecurrenceStartDateTime().isBefore(tenantDateTime())){
+                    nextTriggerDate = CalendarUtils.getNextRecurringDate(emailCampaign.getRecurrence(), emailCampaign.getRecurrenceStartDate(), DateUtils.getLocalDateOfTenant());
+                }else{
+                    nextTriggerDate = emailCampaign.getRecurrenceStartDate();
+                }
+                // to get time of tenant
+                final LocalDateTime getTime = emailCampaign.getRecurrenceStartDateTime();
+
+                final String dateString = nextTriggerDate.toString() + " " + getTime.getHourOfDay()+":"+getTime.getMinuteOfHour()+":"+getTime.getSecondOfMinute();
+                final DateTimeFormatter simpleDateFormat = DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss");
+                final LocalDateTime nextTriggerDateWithTime = LocalDateTime.parse(dateString,simpleDateFormat);
+
+                emailCampaign.setNextTriggerDate(nextTriggerDateWithTime.toDate());
+                this.emailCampaignRepository.saveAndFlush(emailCampaign);
+            }
+        }
+
+        /*
+          if campaign is direct insert campaign message into email outbound table
+          else if its a schedule create a job process for it
+         */
+        return new CommandProcessingResultBuilder() //
+                .withCommandId(command.commandId()) //
+                .withEntityId(emailCampaign.getId()) //
+                .build();
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult closeEmailCampaign(Long campaignId, JsonCommand command) {
+
+        final AppUser currentUser = this.context.authenticatedUser();
+        this.emailCampaignValidator.validateClosedDate(command.json());
+
+        final EmailCampaign emailCampaign = this.emailCampaignRepository.findOne(campaignId);
+        if(emailCampaign == null){
+            throw new EmailCampaignNotFound(campaignId);
+        }
+
+        final Locale locale = command.extractLocale();
+        final DateTimeFormatter fmt = DateTimeFormat.forPattern(command.dateFormat()).withLocale(locale);
+        final LocalDate closureDate = command.localDateValueOfParameterNamed("closureDate");
+
+        emailCampaign.close(currentUser,fmt,closureDate);
+
+        this.emailCampaignRepository.saveAndFlush(emailCampaign);
+
+        return new CommandProcessingResultBuilder() //
+                .withCommandId(command.commandId()) //
+                .withEntityId(emailCampaign.getId()) //
+                .build();
+    }
+
+    private String compileEmailTemplate(final String textMessageTemplate,final String campaignName , final Map<String, Object> emailParams)  {
+        final MustacheFactory mf = new DefaultMustacheFactory();
+        final Mustache mustache = mf.compile(new StringReader(textMessageTemplate), campaignName);
+
+        final StringWriter stringWriter = new StringWriter();
+        mustache.execute(stringWriter, emailParams);
+
+        return stringWriter.toString();
+    }
+
+    private List<HashMap<String,Object>> getRunReportByServiceImpl(final String reportName,final Map<String, String> queryParams) throws IOException {
+        final String reportType ="report";
+
+        List<HashMap<String, Object>> resultList = new ArrayList<HashMap<String, Object>>();
+        final GenericResultsetData results = this.readReportingService.retrieveGenericResultSetForSmsEmailCampaign(reportName,
+                reportType, queryParams);
+        final String response = this.genericDataService.generateJsonFromGenericResultsetData(results);
+        resultList = new ObjectMapper().readValue(response, new TypeReference<List<HashMap<String,Object>>>(){});
+        //loop changes array date to string date
+        for(HashMap<String,Object> entry : resultList){
+            for(Map.Entry<String,Object> map: entry.entrySet()){
+                String key = map.getKey();
+                Object ob  = map.getValue();
+                if(ob instanceof ArrayList && ((ArrayList) ob).size() == 3){
+                    String changeArrayDateToStringDate =  ((ArrayList) ob).get(2).toString() +"-"+((ArrayList) ob).get(1).toString() +"-"+((ArrayList) ob).get(0).toString();
+                    entry.put(key,changeArrayDateToStringDate);
+                }
+            }
+        }
+        return resultList;
+    }
+
+    @Override
+    public PreviewCampaignMessage previewMessage(final JsonQuery query) {
+        PreviewCampaignMessage campaignMessage = null;
+        final AppUser currentUser = this.context.authenticatedUser();
+        this.emailCampaignValidator.validatePreviewMessage(query.json());
+        final String emailParams = this.fromJsonHelper.extractStringNamed("paramValue", query.parsedJson()) ;
+        final String textMessageTemplate = this.fromJsonHelper.extractStringNamed("emailMessage", query.parsedJson());
+
+        try{
+            HashMap<String,String> campaignParams = new ObjectMapper().readValue(emailParams, new TypeReference<HashMap<String,String>>(){});
+
+            HashMap<String,String> queryParamForRunReport =  new ObjectMapper().readValue(emailParams, new TypeReference<HashMap<String,String>>(){});
+
+            List<HashMap<String,Object>> runReportObject = this.getRunReportByServiceImpl(campaignParams.get("reportName"),queryParamForRunReport);
+
+            if(runReportObject !=null){
+                for(HashMap<String,Object> entry : runReportObject){
+                    // add string object to campaignParam object
+                    String textMessage = this.compileEmailTemplate(textMessageTemplate,"EmailCampaign", entry);
+                    if(!textMessage.isEmpty()) {
+                        final Integer totalMessage = runReportObject.size();
+                        campaignMessage = new PreviewCampaignMessage(textMessage,totalMessage);
+                        break;
+                    }
+                }
+            }
+        }catch(final IOException e){
+            // TODO throw something here
+        }
+
+        return campaignMessage;
+
+    }
+    @Transactional
+    @Override
+    public CommandProcessingResult reactivateEmailCampaign(final Long campaignId, JsonCommand command) {
+
+        this.emailCampaignValidator.validateActivation(command.json());
+
+        final AppUser currentUser = this.context.authenticatedUser();
+
+        final EmailCampaign emailCampaign = this.emailCampaignRepository.findOne(campaignId);
+
+        if(emailCampaign == null){ throw new EmailCampaignNotFound(campaignId);}
+
+        final Locale locale = command.extractLocale();
+        final DateTimeFormatter fmt = DateTimeFormat.forPattern(command.dateFormat()).withLocale(locale);
+        final LocalDate reactivationDate = command.localDateValueOfParameterNamed("activationDate");
+        emailCampaign.reactivate(currentUser,fmt,reactivationDate);
+        if (emailCampaign.isSchedule()) {
+
+            /**
+             * if recurrence start date is in the future calculate
+             * next trigger date if not use recurrence start date us next trigger date when activating
+             */
+            LocalDate nextTriggerDate = null;
+            if(emailCampaign.getRecurrenceStartDateTime().isBefore(tenantDateTime())){
+                nextTriggerDate = CalendarUtils.getNextRecurringDate(emailCampaign.getRecurrence(), emailCampaign.getRecurrenceStartDate(), DateUtils.getLocalDateOfTenant());
+            }else{
+                nextTriggerDate = emailCampaign.getRecurrenceStartDate();
+            }
+            // to get time of tenant
+            final LocalDateTime getTime = emailCampaign.getRecurrenceStartDateTime();
+
+            final String dateString = nextTriggerDate.toString() + " " + getTime.getHourOfDay()+":"+getTime.getMinuteOfHour()+":"+getTime.getSecondOfMinute();
+            final DateTimeFormatter simpleDateFormat = DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss");
+            final LocalDateTime nextTriggerDateWithTime = LocalDateTime.parse(dateString,simpleDateFormat);
+
+            emailCampaign.setNextTriggerDate(nextTriggerDateWithTime.toDate());
+            this.emailCampaignRepository.saveAndFlush(emailCampaign);
+        }
+
+
+
+        return new CommandProcessingResultBuilder() //
+                .withEntityId(emailCampaign.getId()) //
+                .build();
+
+    }
+
+    private void handleDataIntegrityIssues(@SuppressWarnings("unused") final JsonCommand command, final DataIntegrityViolationException dve) {
+        final Throwable realCause = dve.getMostSpecificCause();
+
+        throw new PlatformDataIntegrityException("error.msg.email.campaign.unknown.data.integrity.issue",
+                "Unknown data integrity issue with resource: " + realCause.getMessage());
+    }
+
+    private LocalDateTime tenantDateTime(){
+        LocalDateTime today = new LocalDateTime();
+        final FineractPlatformTenant tenant = ThreadLocalContextUtil.getTenant();
+
+        if (tenant != null) {
+            final DateTimeZone zone = DateTimeZone.forID(tenant.getTimezoneId());
+            if (zone != null) {
+                today = new LocalDateTime(zone);
+            }
+        }
+        return  today;
+    }
+
+    @Override
+    @CronTarget(jobName = JobName.EXECUTE_EMAIL)
+    public void sendEmailMessage() throws JobExecutionException {
+        if (IPv4Helper.applicationIsNotRunningOnLocalMachine()){ //remove when testing locally
+            final List<EmailMessage> emailMessages = this.emailMessageRepository.findByStatusType(EmailMessageStatusType.PENDING.getValue()); //retrieve all pending message
+
+            for(final EmailMessage emailMessage : emailMessages) {
+
+
+                if (isValidEmail(emailMessage.getEmailAddress())) {
+
+
+                        final EmailCampaign emailCampaign = this.emailCampaignRepository.findOne(emailMessage.getEmailCampaign().getId()); //
+
+                        final ScheduledEmailAttachmentFileFormat emailAttachmentFileFormat = ScheduledEmailAttachmentFileFormat.instance(emailCampaign.getEmailAttachmentFileFormat());
+
+                        final List<File> attachmentList = new ArrayList<>();
+
+                        final StringBuilder errorLog = new StringBuilder();
+
+                        //check if email attachment format exist
+                        if (emailAttachmentFileFormat != null && Arrays.asList(ScheduledEmailAttachmentFileFormat.validValues()).
+                                contains(emailAttachmentFileFormat.getId())) {
+
+                            final Report stretchyReport = emailCampaign.getStretchyReport();
+
+                            final String reportName = (stretchyReport != null) ? stretchyReport.getReportName() : null;
+
+                            final HashMap<String, String> reportStretchyParams = this.validateStretchyReportParamMap(emailCampaign.getStretchyReportParamMap());
+
+                            // there is a probability that a client has one or more loans or savings therefore we need to send two or more attachments
+                            if (reportStretchyParams.containsKey("selectLoan") || reportStretchyParams.containsKey("loanId")) {
+                                //get all ids of the client loans
+                                if (emailMessage.getClient() != null) {
+
+                                    final List<Loan> loans = this.loanRepository.findLoanByClientId(emailMessage.getClient().getId());
+
+                                    HashMap<String, String> reportParams = this.replaceStretchyParamsWithActualClientParams(reportStretchyParams, emailMessage.getClient());
+
+                                    for (final Loan loan : loans) {
+                                        if (loan.isOpen()) { // only send attachment for active loan
+
+                                            if (reportStretchyParams.containsKey("selectLoan")) {
+
+                                                reportParams.put("SelectLoan", loan.getId().toString());
+
+                                            } else if (reportStretchyParams.containsKey("loanId")) {
+
+                                                reportParams.put("loanId", loan.getId().toString());
+                                            }
+                                            File file = this.generateAttachments(emailCampaign, emailAttachmentFileFormat, reportParams, reportName, errorLog);
+
+                                            if (file != null) {
+                                                attachmentList.add(file);
+                                            } else {
+                                                errorLog.append(reportParams.toString());
+                                            }
+                                        }
+                                    }
+
+                                }
+                            } else if (reportStretchyParams.containsKey("savingId")) {
+                                if (emailMessage.getClient() != null) {
+
+                                    final List<SavingsAccount> savingsAccounts = this.savingsAccountRepository.findSavingAccountByClientId(emailMessage.getClient().getId());
+
+                                    HashMap<String, String> reportParams = this.replaceStretchyParamsWithActualClientParams(reportStretchyParams, emailMessage.getClient());
+
+                                    for (final SavingsAccount savingsAccount : savingsAccounts) {
+
+                                        if (savingsAccount.isActive()) {
+
+                                            reportParams.put("savingId", savingsAccount.getId().toString());
+
+                                            File file = this.generateAttachments(emailCampaign, emailAttachmentFileFormat, reportParams, reportName, errorLog);
+
+                                            if (file != null) {
+                                                attachmentList.add(file);
+                                            } else {
+                                                errorLog.append(reportParams.toString());
+                                            }
+                                        }
+                                    }
+                                }
+                            } else {
+                                if (emailMessage.getClient() != null) {
+
+                                    HashMap<String, String> reportParams = this.replaceStretchyParamsWithActualClientParams(reportStretchyParams, emailMessage.getClient());
+
+                                    File file = this.generateAttachments(emailCampaign, emailAttachmentFileFormat, reportParams, reportName, errorLog);
+
+                                    if (file != null) {
+                                        attachmentList.add(file);
+                                    } else {
+                                        errorLog.append(reportParams.toString());
+                                    }
+                                }
+                            }
+
+                        }
+
+                        final EmailMessageWithAttachmentData emailMessageWithAttachmentData = EmailMessageWithAttachmentData.createNew(emailMessage.getEmailAddress(), emailMessage.getMessage(),
+                                emailMessage.getEmailSubject(), attachmentList);
+
+                        if (!attachmentList.isEmpty() && attachmentList.size() > 0) { // only send email message if there is an attachment to it
+
+                            this.emailMessageJobEmailService.sendEmailWithAttachment(emailMessageWithAttachmentData);
+
+                            emailMessage.setStatusType(EmailMessageStatusType.SENT.getValue());
+
+                            this.emailMessageRepository.save(emailMessage);
+                        } else {
+                            emailMessage.updateErrorMessage(errorLog.toString());
+
+                            emailMessage.setStatusType(EmailMessageStatusType.FAILED.getValue());
+
+                            this.emailMessageRepository.save(emailMessage);
+                        }
+                }
+            }
+
+        }
+
+
+    }
+
+    /**
+     * This generates the the report and converts it to a file by passing the parameters below
+     * @param emailCampaign
+     * @param emailAttachmentFileFormat
+     * @param reportParams
+     * @param reportName
+     * @param errorLog
+     * @return
+     */
+    private File generateAttachments(final EmailCampaign emailCampaign, final ScheduledEmailAttachmentFileFormat emailAttachmentFileFormat,
+                                     final Map<String, String> reportParams, final String reportName, final StringBuilder errorLog){
+
+        try{
+            final ByteArrayOutputStream byteArrayOutputStream = this.readReportingService.generatePentahoReportAsOutputStream(reportName,
+                    emailAttachmentFileFormat.getValue(), reportParams, null, emailCampaign.getApprovedBy(), errorLog);
+
+            final String fileLocation = FileSystemContentRepository.FINERACT_BASE_DIR + File.separator + "";
+            final String fileNameWithoutExtension = fileLocation + File.separator + reportName;
+
+            // check if file directory exists, if not create directory
+            if (!new File(fileLocation).isDirectory()) {
+                new File(fileLocation).mkdirs();
+            }
+
+            if (byteArrayOutputStream.size() == 0) {
+                errorLog.append("Pentaho report processing failed, empty output stream created");
+            }
+            else if (errorLog.length() == 0 && (byteArrayOutputStream.size() > 0)) {
+                final String fileName = fileNameWithoutExtension + "." + emailAttachmentFileFormat.getValue();
+
+                final File file = new File(fileName);
+                final FileOutputStream outputStream = new FileOutputStream(file);
+                byteArrayOutputStream.writeTo(outputStream);
+
+                return file;
+            }
+
+        }catch(IOException | PlatformDataIntegrityException e){
+            errorLog.append("The ReportMailingJobWritePlatformServiceImpl.executeReportMailingJobs threw an IOException "
+                    + "exception: " + e.getMessage() + " ---------- ");
+        }
+        return null;
+    }
+
+    /**
+     * This matches the the actual values to the key in the report stretchy parameters map
+     * @param stretchyParams
+     * @param client
+     * @return
+     */
+    private HashMap<String,String> replaceStretchyParamsWithActualClientParams(final HashMap<String,String> stretchyParams,final Client client){
+
+        HashMap<String,String> actualParams = new HashMap<>();
+
+        for (Map.Entry<String, String> entry : stretchyParams.entrySet()) {
+             if(entry.getKey().equals("selectOffice")){
+                 //most at times the reports are run by picking the office of the staff Id
+                 if(client.getStaff() !=null){
+                     actualParams.put(entry.getKey(),client.getStaff().officeId().toString());
+                 }else {
+                     actualParams.put(entry.getKey(), client.getOffice().getId().toString());
+                 }
+
+             }else if(entry.getKey().equals("selectClient")){
+
+                 actualParams.put(entry.getKey(),client.getId().toString());
+
+             }else if(entry.getKey().equals("selectLoanofficer")){
+
+                 actualParams.put(entry.getKey(),client.getStaff().getId().toString());
+
+             }else if(entry.getKey().equals("environementUrl")){
+
+                 actualParams.put(entry.getKey(),entry.getKey());
+             }
+        }
+        return actualParams;
+    }
+
+
+    private HashMap<String,String> validateStretchyReportParamMap(final String stretchyParams){
+
+        HashMap<String,String> stretchyReportParamHashMap = new HashMap<>();
+
+        if (!StringUtils.isEmpty(stretchyParams)) {
+            try {
+                stretchyReportParamHashMap = new ObjectMapper().readValue(stretchyParams, new TypeReference<HashMap<String,String>>(){});
+            }
+
+            catch(Exception e) {
+                stretchyReportParamHashMap = null;
+            }
+        }
+
+        return stretchyReportParamHashMap;
+    }
+
+
+}

http://git-wip-us.apache.org/repos/asf/fineract/blob/c338c175/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/service/EmailCampaignWritePlatformService.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/service/EmailCampaignWritePlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/service/EmailCampaignWritePlatformService.java
new file mode 100644
index 0000000..a86262e
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/service/EmailCampaignWritePlatformService.java
@@ -0,0 +1,47 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF 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 org.apache.fineract.infrastructure.campaigns.email.service;
+
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.api.JsonQuery;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.infrastructure.jobs.exception.JobExecutionException;
+import org.apache.fineract.infrastructure.campaigns.email.data.PreviewCampaignMessage;
+
+public interface EmailCampaignWritePlatformService {
+
+    CommandProcessingResult create(JsonCommand command);
+
+    CommandProcessingResult update(Long resourceId, JsonCommand command);
+
+    CommandProcessingResult delete(Long resourceId);
+
+    CommandProcessingResult activateEmailCampaign(Long campaignId, JsonCommand command);
+
+    CommandProcessingResult closeEmailCampaign(Long campaignId, JsonCommand command);
+
+    CommandProcessingResult reactivateEmailCampaign(Long campaignId, JsonCommand command);
+
+    void storeTemplateMessageIntoEmailOutBoundTable() throws JobExecutionException;
+
+    PreviewCampaignMessage previewMessage(JsonQuery query);
+
+    void sendEmailMessage() throws JobExecutionException;
+
+}

http://git-wip-us.apache.org/repos/asf/fineract/blob/c338c175/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/service/EmailConfigurationReadPlatformService.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/service/EmailConfigurationReadPlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/service/EmailConfigurationReadPlatformService.java
new file mode 100644
index 0000000..cc704e6
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/service/EmailConfigurationReadPlatformService.java
@@ -0,0 +1,30 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF 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 org.apache.fineract.infrastructure.campaigns.email.service;
+
+import org.apache.fineract.infrastructure.campaigns.email.data.EmailConfigurationData;
+
+import java.util.Collection;
+
+public interface EmailConfigurationReadPlatformService {
+	
+	Collection<EmailConfigurationData> retrieveAll();
+
+	EmailConfigurationData retrieveOne(String name);
+}

http://git-wip-us.apache.org/repos/asf/fineract/blob/c338c175/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/service/EmailConfigurationReadPlatformServiceImpl.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/service/EmailConfigurationReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/service/EmailConfigurationReadPlatformServiceImpl.java
new file mode 100644
index 0000000..394e082
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/service/EmailConfigurationReadPlatformServiceImpl.java
@@ -0,0 +1,99 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF 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 org.apache.fineract.infrastructure.campaigns.email.service;
+
+import org.apache.fineract.infrastructure.core.domain.JdbcSupport;
+import org.apache.fineract.infrastructure.core.service.RoutingDataSource;
+import org.apache.fineract.infrastructure.campaigns.email.exception.EmailConfigurationNotFoundException;
+import org.apache.fineract.infrastructure.campaigns.email.data.EmailConfigurationData;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.dao.EmptyResultDataAccessException;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.jdbc.core.RowMapper;
+import org.springframework.stereotype.Service;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.Collection;
+
+@Service
+public class EmailConfigurationReadPlatformServiceImpl implements EmailConfigurationReadPlatformService {
+	
+	private final JdbcTemplate jdbcTemplate;
+    private final EmailConfigurationRowMapper emailConfigurationRowMapper;
+    
+    @Autowired
+    public EmailConfigurationReadPlatformServiceImpl(final RoutingDataSource dataSource) {
+    	this.jdbcTemplate = new JdbcTemplate(dataSource);
+    	this.emailConfigurationRowMapper = new EmailConfigurationRowMapper();
+    	
+    }
+	
+	private static final class EmailConfigurationRowMapper implements RowMapper<EmailConfigurationData> {
+		
+		final String schema;
+		
+		public EmailConfigurationRowMapper() {
+			 final StringBuilder sql = new StringBuilder(300);
+	            sql.append("cnf.id as id, ");
+	            sql.append("cnf.name as name, ");
+	            sql.append("cnf.value as value ");
+	            sql.append("from scheduled_email_configuration cnf");
+	            
+	            this.schema = sql.toString();
+		}
+		
+		public String schema() {
+            return this.schema;
+        }
+
+		@Override
+		public EmailConfigurationData mapRow(ResultSet rs, @SuppressWarnings("unused") int rowNum) throws SQLException {
+			
+			final Long id = JdbcSupport.getLong(rs, "id");
+			final String name = rs.getString("name");
+			final String value = rs.getString("value");
+			
+			return EmailConfigurationData.instance(id, name, value);
+		}
+		
+	}
+
+	@Override
+	public Collection<EmailConfigurationData> retrieveAll() {
+		final String sql = "select " + this.emailConfigurationRowMapper.schema();
+
+        return this.jdbcTemplate.query(sql, this.emailConfigurationRowMapper, new Object[] {});
+	}
+
+	@Override
+	public EmailConfigurationData retrieveOne(String name) {
+		try {
+			final String sql = "select " + this.emailConfigurationRowMapper.schema() + " where cnf.name = ?";
+
+	        return this.jdbcTemplate.queryForObject(sql, this.emailConfigurationRowMapper, name);
+		}
+		
+		catch(final EmptyResultDataAccessException e) {
+			
+			throw new EmailConfigurationNotFoundException(name);
+		}
+	}
+
+}

http://git-wip-us.apache.org/repos/asf/fineract/blob/c338c175/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/service/EmailConfigurationWritePlatformService.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/service/EmailConfigurationWritePlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/service/EmailConfigurationWritePlatformService.java
new file mode 100644
index 0000000..3afa8d3
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/service/EmailConfigurationWritePlatformService.java
@@ -0,0 +1,28 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF 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 org.apache.fineract.infrastructure.campaigns.email.service;
+
+
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+
+public interface EmailConfigurationWritePlatformService {
+
+    CommandProcessingResult update(JsonCommand command);
+}

http://git-wip-us.apache.org/repos/asf/fineract/blob/c338c175/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/service/EmailConfigurationWritePlatformServiceImpl.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/service/EmailConfigurationWritePlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/service/EmailConfigurationWritePlatformServiceImpl.java
new file mode 100644
index 0000000..508e283
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/service/EmailConfigurationWritePlatformServiceImpl.java
@@ -0,0 +1,89 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF 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 org.apache.fineract.infrastructure.campaigns.email.service;
+
+import com.google.gson.JsonElement;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder;
+import org.apache.fineract.infrastructure.campaigns.email.data.EmailConfigurationData;
+import org.apache.fineract.infrastructure.campaigns.email.data.EmailConfigurationValidator;
+import org.apache.fineract.infrastructure.campaigns.email.domain.EmailConfiguration;
+import org.apache.fineract.infrastructure.campaigns.email.domain.EmailConfigurationRepository;
+import org.apache.fineract.infrastructure.campaigns.email.exception.EmailConfigurationSMTPUsernameNotValid;
+import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
+import org.apache.fineract.useradministration.domain.AppUser;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+@Service
+public class EmailConfigurationWritePlatformServiceImpl implements EmailConfigurationWritePlatformService {
+
+    private final PlatformSecurityContext context;
+    private final EmailConfigurationRepository repository;
+    private final EmailConfigurationValidator emailConfigurationValidator;
+
+    @Autowired
+    public EmailConfigurationWritePlatformServiceImpl(final PlatformSecurityContext context, final EmailConfigurationRepository repository,
+                                                      final EmailConfigurationValidator emailConfigurationValidator) {
+        this.context = context;
+        this.repository = repository;
+        this.emailConfigurationValidator = emailConfigurationValidator;
+    }
+
+    @Override
+    public CommandProcessingResult update(final JsonCommand command) {
+
+            final AppUser currentUser = this.context.authenticatedUser();
+
+            this.emailConfigurationValidator.validateUpdateConfiguration(command.json());
+            final String smtpUsername = command.stringValueOfParameterNamed("SMTP_USERNAME");
+
+            if(!this.emailConfigurationValidator.isValidEmail(smtpUsername)){
+                throw new EmailConfigurationSMTPUsernameNotValid(smtpUsername);
+            }
+
+
+            final Map<String,Object> changes = new HashMap<>(4);
+
+            Collection<EmailConfiguration> configurations = this.repository.findAll();
+            /**
+             *Default SMTP configuration added to flyway
+             */
+            for (EmailConfiguration config : configurations) {
+                if(config.getName() !=null){
+                    String value = command.stringValueOfParameterNamed(config.getName());
+                    config.setValue(value); changes.put(config.getName(),value);
+                    this.repository.saveAndFlush(config);
+                }
+            }
+
+            return new CommandProcessingResultBuilder() //
+                    .with(changes)
+                    .build();
+
+    }
+
+
+}

http://git-wip-us.apache.org/repos/asf/fineract/blob/c338c175/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/service/EmailMessageJobEmailService.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/service/EmailMessageJobEmailService.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/service/EmailMessageJobEmailService.java
new file mode 100644
index 0000000..37b193b
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/service/EmailMessageJobEmailService.java
@@ -0,0 +1,28 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF 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 org.apache.fineract.infrastructure.campaigns.email.service;
+
+
+import org.apache.fineract.infrastructure.campaigns.email.data.EmailMessageWithAttachmentData;
+
+public interface EmailMessageJobEmailService {
+
+    void sendEmailWithAttachment(EmailMessageWithAttachmentData emailMessageWithAttachmentData);
+
+}

http://git-wip-us.apache.org/repos/asf/fineract/blob/c338c175/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/service/EmailMessageJobEmailServiceImpl.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/service/EmailMessageJobEmailServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/service/EmailMessageJobEmailServiceImpl.java
new file mode 100644
index 0000000..c8854a2
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/service/EmailMessageJobEmailServiceImpl.java
@@ -0,0 +1,110 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF 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 org.apache.fineract.infrastructure.campaigns.email.service;
+
+
+import org.apache.fineract.infrastructure.campaigns.email.EmailApiConstants;
+import org.apache.fineract.infrastructure.campaigns.email.data.EmailMessageWithAttachmentData;
+import org.apache.fineract.infrastructure.campaigns.email.domain.EmailConfiguration;
+import org.apache.fineract.infrastructure.campaigns.email.domain.EmailConfigurationRepository;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.mail.javamail.JavaMailSenderImpl;
+import org.springframework.mail.javamail.MimeMessageHelper;
+import org.springframework.stereotype.Service;
+
+import javax.mail.MessagingException;
+import javax.mail.internet.MimeMessage;
+import java.io.File;
+import java.util.List;
+import java.util.Properties;
+import java.util.Set;
+
+@Service
+public class EmailMessageJobEmailServiceImpl implements EmailMessageJobEmailService {
+
+    private EmailConfigurationRepository emailConfigurationRepository;
+
+    @Autowired
+    private EmailMessageJobEmailServiceImpl(final EmailConfigurationRepository emailConfigurationRepository) {
+        this.emailConfigurationRepository = emailConfigurationRepository;
+    }
+
+    @Override
+    public void sendEmailWithAttachment(EmailMessageWithAttachmentData emailMessageWithAttachmentData) {
+        try{
+            JavaMailSenderImpl javaMailSenderImpl = new JavaMailSenderImpl();
+            javaMailSenderImpl.setHost(this.getGmailSmtpServer());
+            javaMailSenderImpl.setPort(this.getGmailSmtpPort());
+            javaMailSenderImpl.setUsername(this.getGmailSmtpUsername());
+            javaMailSenderImpl.setPassword(this.getGmailSmtpPassword());
+            javaMailSenderImpl.setJavaMailProperties(this.getJavaMailProperties());
+
+            MimeMessage mimeMessage = javaMailSenderImpl.createMimeMessage();
+
+            // use the true flag to indicate you need a multipart message
+            MimeMessageHelper mimeMessageHelper = new MimeMessageHelper(mimeMessage, true);
+
+            mimeMessageHelper.setTo(emailMessageWithAttachmentData.getTo());
+            mimeMessageHelper.setText(emailMessageWithAttachmentData.getText());
+            mimeMessageHelper.setSubject(emailMessageWithAttachmentData.getSubject());
+            final List<File> attachments = emailMessageWithAttachmentData.getAttachments();
+            if(attachments !=null && attachments.size() > 0){
+                for(final File attachment : attachments){
+                    if(attachment !=null){
+                        mimeMessageHelper.addAttachment(attachment.getName(),attachment);
+                    }
+                }
+            }
+
+            javaMailSenderImpl.send(mimeMessage);
+
+        }catch(MessagingException e){
+
+        }
+
+    }
+
+
+    private String getGmailSmtpServer(){
+        final EmailConfiguration gmailSmtpServer = this.emailConfigurationRepository.findByName(EmailApiConstants.SMTP_SERVER);
+        return (gmailSmtpServer !=null) ? gmailSmtpServer.getValue() : null;
+    }
+    private Integer getGmailSmtpPort(){
+        final EmailConfiguration gmailSmtpPort = this.emailConfigurationRepository.findByName(EmailApiConstants.SMTP_PORT);
+        return (gmailSmtpPort !=null) ? Integer.parseInt(gmailSmtpPort.getValue()) : null;
+    }
+    private String getGmailSmtpUsername(){
+        final EmailConfiguration gmailSmtpUsername = this.emailConfigurationRepository.findByName(EmailApiConstants.SMTP_USERNAME);
+        return (gmailSmtpUsername !=null) ? gmailSmtpUsername.getValue() : null;
+    }
+
+    private String getGmailSmtpPassword(){
+        final EmailConfiguration gmailSmtpPassword = this.emailConfigurationRepository.findByName(EmailApiConstants.SMTP_PASSWORD);
+        return (gmailSmtpPassword !=null) ? gmailSmtpPassword.getValue() : null;
+    }
+
+    private Properties getJavaMailProperties() {
+        Properties properties = new Properties();
+        properties.setProperty("mail.smtp.starttls.enable", "true");
+        properties.setProperty("mail.smtp.auth", "true");
+        properties.setProperty("mail.smtp.ssl.trust", this.getGmailSmtpServer());
+
+        return properties;
+    }
+}

http://git-wip-us.apache.org/repos/asf/fineract/blob/c338c175/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/service/EmailReadPlatformService.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/service/EmailReadPlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/service/EmailReadPlatformService.java
new file mode 100644
index 0000000..6348023
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/service/EmailReadPlatformService.java
@@ -0,0 +1,45 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF 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 org.apache.fineract.infrastructure.campaigns.email.service;
+
+import org.apache.fineract.infrastructure.core.service.Page;
+import org.apache.fineract.infrastructure.campaigns.email.data.EmailData;
+
+import java.util.Collection;
+import java.util.Date;
+import java.util.List;
+
+public interface EmailReadPlatformService {
+
+    Collection<EmailData> retrieveAll();
+
+    EmailData retrieveOne(Long resourceId);
+    
+    Collection<EmailData> retrieveAllPending(Integer limit);
+    
+    Collection<EmailData> retrieveAllSent(Integer limit);
+    
+    Collection<EmailData> retrieveAllDelivered(Integer limit);
+    
+    Collection<EmailData> retrieveAllFailed(Integer limit);
+
+    Page<EmailData> retrieveEmailByStatus(Integer limit, Integer status, Date dateFrom, Date dateTo);
+    
+    List<Long> retrieveExternalIdsOfAllSent(Integer limit);
+}

http://git-wip-us.apache.org/repos/asf/fineract/blob/c338c175/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/service/EmailReadPlatformServiceImpl.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/service/EmailReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/service/EmailReadPlatformServiceImpl.java
new file mode 100644
index 0000000..98d4dfe
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/service/EmailReadPlatformServiceImpl.java
@@ -0,0 +1,199 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF 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 org.apache.fineract.infrastructure.campaigns.email.service;
+
+import org.joda.time.LocalDate;
+import org.apache.fineract.infrastructure.core.data.EnumOptionData;
+import org.apache.fineract.infrastructure.core.domain.JdbcSupport;
+import org.apache.fineract.infrastructure.core.service.Page;
+import org.apache.fineract.infrastructure.core.service.PaginationHelper;
+import org.apache.fineract.infrastructure.core.service.RoutingDataSource;
+import org.apache.fineract.infrastructure.campaigns.email.exception.EmailNotFoundException;
+import org.apache.fineract.infrastructure.campaigns.email.data.EmailData;
+import org.apache.fineract.infrastructure.campaigns.email.domain.EmailMessageEnumerations;
+import org.apache.fineract.infrastructure.campaigns.email.domain.EmailMessageStatusType;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.dao.EmptyResultDataAccessException;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.jdbc.core.RowMapper;
+import org.springframework.stereotype.Service;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.Collection;
+import java.util.Date;
+import java.util.List;
+
+@Service
+public class EmailReadPlatformServiceImpl implements EmailReadPlatformService {
+
+    private final JdbcTemplate jdbcTemplate;
+    private final EmailMapper emailRowMapper = new EmailMapper();
+    private final PaginationHelper<EmailData> paginationHelper = new PaginationHelper<>();
+
+
+    @Autowired
+    public EmailReadPlatformServiceImpl(final RoutingDataSource dataSource) {
+        this.jdbcTemplate = new JdbcTemplate(dataSource);
+    }
+
+    private static final class EmailMapper implements RowMapper<EmailData> {
+
+        final String schema;
+
+        public EmailMapper() {
+            final StringBuilder sql = new StringBuilder(300);
+            sql.append(" emo.id as id, ");
+            sql.append("emo.group_id as groupId, ");
+            sql.append("emo.client_id as clientId, ");
+            sql.append("emo.staff_id as staffId, ");
+            sql.append("emo.campaign_name as campaignName, ");
+            sql.append("emo.status_enum as statusId, ");
+            sql.append("emo.email_address as emailAddress, ");
+            sql.append("emo.submittedon_date as sentDate, ");
+            sql.append("emo.email_subject as emailSubject, ");
+            sql.append("emo.message as message, ");
+            sql.append("emo.error_message as errorMessage ");
+            sql.append("from " + tableName() + " emo");
+
+            this.schema = sql.toString();
+        }
+
+        public String schema() {
+            return this.schema;
+        }
+        
+        public String tableName() {
+        	return "scheduled_email_messages_outbound";
+        }
+
+        @Override
+        public EmailData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
+
+            final Long id = JdbcSupport.getLong(rs, "id");
+            final Long groupId = JdbcSupport.getLong(rs, "groupId");
+            final Long clientId = JdbcSupport.getLong(rs, "clientId");
+            final Long staffId = JdbcSupport.getLong(rs, "staffId");
+
+            final String emailAddress = rs.getString("emailAddress");
+            final String emailSubject = rs.getString("emailSubject");
+            final String message = rs.getString("message");
+            final String campaignName = rs.getString("campaignName");
+
+            final Integer statusId = JdbcSupport.getInteger(rs, "statusId");
+            final LocalDate sentDate = JdbcSupport.getLocalDate(rs, "sentDate");
+            final String errorMessage = rs.getString("errorMessage");
+
+            final EnumOptionData status = EmailMessageEnumerations.status(statusId);
+
+            return EmailData.instance(id,groupId, clientId, staffId, status, emailAddress, emailSubject,
+                    message,null,null,null,null,null,campaignName,sentDate,errorMessage);
+        }
+    }
+
+    @Override
+    public Collection<EmailData> retrieveAll() {
+
+        final String sql = "select " + this.emailRowMapper.schema();
+
+        return this.jdbcTemplate.query(sql, this.emailRowMapper, new Object[] {});
+    }
+
+    @Override
+    public EmailData retrieveOne(final Long resourceId) {
+        try {
+            final String sql = "select " + this.emailRowMapper.schema() + " where emo.id = ?";
+
+            return this.jdbcTemplate.queryForObject(sql, this.emailRowMapper, resourceId);
+        } catch (final EmptyResultDataAccessException e) {
+            throw new EmailNotFoundException(resourceId);
+        }
+    }
+    
+    @Override
+	public Collection<EmailData> retrieveAllPending(final Integer limit) {
+    	final String sqlPlusLimit = (limit > 0) ? " limit 0, " + limit : "";
+    	final String sql = "select " + this.emailRowMapper.schema() + " where emo.status_enum = "
+    			+ EmailMessageStatusType.PENDING.getValue() + sqlPlusLimit;
+
+        return this.jdbcTemplate.query(sql, this.emailRowMapper, new Object[] {});
+    }
+
+	@Override
+	public Collection<EmailData> retrieveAllSent(final Integer limit) {
+		final String sqlPlusLimit = (limit > 0) ? " limit 0, " + limit : "";
+    	final String sql = "select " + this.emailRowMapper.schema() + " where emo.status_enum = "
+    			+ EmailMessageStatusType.SENT.getValue() + sqlPlusLimit;
+
+        return this.jdbcTemplate.query(sql, this.emailRowMapper, new Object[] {});
+	}
+
+	@Override
+	public List<Long> retrieveExternalIdsOfAllSent(final Integer limit) {
+		final String sqlPlusLimit = (limit > 0) ? " limit 0, " + limit : "";
+		final String sql = "select external_id from " + this.emailRowMapper.tableName() + " where status_enum = "
+    			+ EmailMessageStatusType.SENT.getValue() + sqlPlusLimit;
+		
+		return this.jdbcTemplate.queryForList(sql, Long.class);
+	}
+
+    @Override
+    public Collection<EmailData> retrieveAllDelivered(final Integer limit) {
+        final String sqlPlusLimit = (limit > 0) ? " limit 0, " + limit : "";
+        final String sql = "select " + this.emailRowMapper.schema() + " where emo.status_enum = "
+                + EmailMessageStatusType.DELIVERED.getValue() + sqlPlusLimit;
+
+        return this.jdbcTemplate.query(sql, this.emailRowMapper, new Object[] {});
+    }
+
+	@Override
+	public Collection<EmailData> retrieveAllFailed(final Integer limit) {
+		final String sqlPlusLimit = (limit > 0) ? " limit 0, " + limit : "";
+        final String sql = "select " + this.emailRowMapper.schema() + " where emo.status_enum = "
+                + EmailMessageStatusType.FAILED.getValue() + sqlPlusLimit;
+
+        return this.jdbcTemplate.query(sql, this.emailRowMapper, new Object[] {});
+	}
+
+    @Override
+    public Page<EmailData> retrieveEmailByStatus(final Integer limit, final Integer status,final Date dateFrom, final Date dateTo) {
+        final StringBuilder sqlBuilder = new StringBuilder(200);
+        sqlBuilder.append("select SQL_CALC_FOUND_ROWS ");
+        sqlBuilder.append(this.emailRowMapper.schema());
+        if(status !=null){
+            sqlBuilder.append(" where emo.status_enum= ? ");
+        }
+        String fromDateString = null;
+        String toDateString = null;
+        if(dateFrom !=null && dateTo !=null){
+            final DateFormat df = new SimpleDateFormat("yyyy-MM-dd");
+            fromDateString = df.format(dateFrom);
+            toDateString  = df.format(dateTo);
+            sqlBuilder.append(" and emo.submittedon_date >= ? and emo.submittedon_date <= ? ");
+        }
+        final String sqlPlusLimit = (limit > 0) ? " limit 0, " + limit : "";
+        if(!sqlPlusLimit.isEmpty()){
+            sqlBuilder.append(sqlPlusLimit);
+        }
+        final String sqlCountRows = "SELECT FOUND_ROWS()";
+        return this.paginationHelper.fetchPage(this.jdbcTemplate,sqlCountRows,sqlBuilder.toString(),new Object[]{status,fromDateString,toDateString},this.emailRowMapper);
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/fineract/blob/c338c175/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/service/EmailWritePlatformService.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/service/EmailWritePlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/service/EmailWritePlatformService.java
new file mode 100644
index 0000000..233427c
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/service/EmailWritePlatformService.java
@@ -0,0 +1,31 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF 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 org.apache.fineract.infrastructure.campaigns.email.service;
+
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+
+public interface EmailWritePlatformService {
+
+    CommandProcessingResult create(JsonCommand command);
+
+    CommandProcessingResult update(Long resourceId, JsonCommand command);
+
+    CommandProcessingResult delete(Long resourceId);
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/fineract/blob/c338c175/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/service/EmailWritePlatformServiceJpaRepositoryImpl.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/service/EmailWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/service/EmailWritePlatformServiceJpaRepositoryImpl.java
new file mode 100644
index 0000000..9e23bc6
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/service/EmailWritePlatformServiceJpaRepositoryImpl.java
@@ -0,0 +1,134 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF 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 org.apache.fineract.infrastructure.campaigns.email.service;
+
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder;
+import org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityException;
+import org.apache.fineract.infrastructure.campaigns.email.data.EmailDataValidator;
+import org.apache.fineract.infrastructure.campaigns.email.domain.EmailMessage;
+import org.apache.fineract.infrastructure.campaigns.email.domain.EmailMessageAssembler;
+import org.apache.fineract.infrastructure.campaigns.email.domain.EmailMessageRepository;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.dao.DataIntegrityViolationException;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.Map;
+
+@Service
+public class EmailWritePlatformServiceJpaRepositoryImpl implements EmailWritePlatformService {
+
+    private final static Logger logger = LoggerFactory.getLogger(EmailWritePlatformServiceJpaRepositoryImpl.class);
+
+    private final EmailMessageAssembler assembler;
+    private final EmailMessageRepository repository;
+    private final EmailDataValidator validator;
+
+    @Autowired
+    public EmailWritePlatformServiceJpaRepositoryImpl(final EmailMessageAssembler assembler, final EmailMessageRepository repository,
+            final EmailDataValidator validator) {
+        this.assembler = assembler;
+        this.repository = repository;
+        this.validator = validator;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult create(final JsonCommand command) {
+
+        try {
+            this.validator.validateCreateRequest(command);
+
+            final EmailMessage message = this.assembler.assembleFromJson(command);
+
+            // TODO: at this point we also want to fire off request using third
+            // party service to send Email.
+            // TODO: decision to be made on wheter we 'wait' for response or use
+            // 'future/promise' to capture response and update the EmailMessage
+            // table
+            this.repository.save(message);
+
+            return new CommandProcessingResultBuilder() //
+                    .withCommandId(command.commandId()) //
+                    .withEntityId(message.getId()) //
+                    .build();
+        } catch (final DataIntegrityViolationException dve) {
+            handleDataIntegrityIssues(command, dve);
+            return CommandProcessingResult.empty();
+        }
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult update(final Long resourceId, final JsonCommand command) {
+
+        try {
+            this.validator.validateUpdateRequest(command);
+
+            final EmailMessage message = this.assembler.assembleFromResourceId(resourceId);
+            final Map<String, Object> changes = message.update(command);
+            if (!changes.isEmpty()) {
+                this.repository.save(message);
+            }
+
+            return new CommandProcessingResultBuilder() //
+                    .withCommandId(command.commandId()) //
+                    .withEntityId(resourceId) //
+                    .with(changes) //
+                    .build();
+        } catch (final DataIntegrityViolationException dve) {
+            handleDataIntegrityIssues(command, dve);
+            return CommandProcessingResult.empty();
+        }
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult delete(final Long resourceId) {
+
+        try {
+            final EmailMessage message = this.assembler.assembleFromResourceId(resourceId);
+            this.repository.delete(message);
+            this.repository.flush();
+        } catch (final DataIntegrityViolationException dve) {
+            handleDataIntegrityIssues(null, dve);
+            return CommandProcessingResult.empty();
+        }
+        return new CommandProcessingResultBuilder().withEntityId(resourceId).build();
+    }
+
+    /*
+     * Guaranteed to throw an exception no matter what the data integrity issue
+     * is.
+     */
+    private void handleDataIntegrityIssues(@SuppressWarnings("unused") final JsonCommand command, final DataIntegrityViolationException dve) {
+        final Throwable realCause = dve.getMostSpecificCause();
+
+        if (realCause.getMessage().contains("email_address")) { throw new PlatformDataIntegrityException("error.msg.email.no.email.address.exists",
+                "The group, client or staff provided has no email address.", "id"); }
+
+        logger.error(dve.getMessage(), dve);
+        throw new PlatformDataIntegrityException("error.msg.email.unknown.data.integrity.issue",
+                "Unknown data integrity issue with resource: " + realCause.getMessage());
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/fineract/blob/c338c175/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/api/SmsCampaignApiResource.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/api/SmsCampaignApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/api/SmsCampaignApiResource.java
index 54e714c..8de1bbf 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/api/SmsCampaignApiResource.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/api/SmsCampaignApiResource.java
@@ -126,7 +126,7 @@ public class SmsCampaignApiResource {
 
     @GET
     @Produces({ MediaType.APPLICATION_JSON })
-    public String retrieveAllCampaigns(@QueryParam("sqlSearch") final String sqlSearch,
+    public String retrieveAllEmails(@QueryParam("sqlSearch") final String sqlSearch,
             @QueryParam("offset") final Integer offset, @QueryParam("limit") final Integer limit,
             @QueryParam("orderBy") final String orderBy, @QueryParam("sortOrder") final String sortOrder, @Context final UriInfo uriInfo) {
         this.platformSecurityContext.authenticatedUser().validateHasReadPermission(SmsCampaignConstants.RESOURCE_NAME);

http://git-wip-us.apache.org/repos/asf/fineract/blob/c338c175/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/service/SmsCampaignWritePlatformServiceJpaImpl.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/service/SmsCampaignWritePlatformServiceJpaImpl.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/service/SmsCampaignWritePlatformServiceJpaImpl.java
index 0084ed6..8c9dd30 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/service/SmsCampaignWritePlatformServiceJpaImpl.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/service/SmsCampaignWritePlatformServiceJpaImpl.java
@@ -514,7 +514,7 @@ public class SmsCampaignWritePlatformServiceJpaImpl implements SmsCampaignWriteP
         final String reportType = "report";
 
         List<HashMap<String, Object>> resultList = new ArrayList<>();
-        final GenericResultsetData results = this.readReportingService.retrieveGenericResultSetForSmsCampaign(reportName, reportType,
+        final GenericResultsetData results = this.readReportingService.retrieveGenericResultSetForSmsEmailCampaign(reportName, reportType,
                 queryParams);
 
         try {