From ead89791e8aa687727edfacd86f776401f4e27e2 Mon Sep 17 00:00:00 2001
From: Shinsuke Sugaya <shinsuke@yahoo.co.jp>
Date: Thu, 29 Oct 2015 22:33:43 +0900
Subject: [PATCH] replace with mailflute

---
 pom.xml                                       |  22 +-
 .../java/org/codelibs/fess/exec/Crawler.java  |  37 +--
 .../org/codelibs/fess/helper/MailHelper.java  |  79 ------
 .../fess/mylasta/action/FessLabels.java       |  20 +-
 .../fess/mylasta/direction/FessConfig.java    |  29 ++
 .../fess/mylasta/mail/CrawlerPostcard.java    | 268 ++++++++++++++++++
 .../fess/mylasta/mail/EsStatusPostcard.java   | 139 +++++++++
 .../org/codelibs/fess/util/ComponentUtil.java |   7 -
 src/main/resources/fess.xml                   |   9 -
 src/main/resources/fess_config.properties     |   8 +
 .../resources/fess_env_production.properties  |   3 +-
 src/main/resources/mail/crawler.dfmail        |  43 +++
 src/main/resources/mail/es_status.dfmail      |  13 +
 src/main/webapp/WEB-INF/mail/crawler.hbs      |  37 ---
 src/main/webapp/WEB-INF/mail/solr_status.hbs  |   8 -
 15 files changed, 539 insertions(+), 183 deletions(-)
 delete mode 100644 src/main/java/org/codelibs/fess/helper/MailHelper.java
 create mode 100644 src/main/java/org/codelibs/fess/mylasta/mail/CrawlerPostcard.java
 create mode 100644 src/main/java/org/codelibs/fess/mylasta/mail/EsStatusPostcard.java
 create mode 100644 src/main/resources/mail/crawler.dfmail
 create mode 100644 src/main/resources/mail/es_status.dfmail
 delete mode 100644 src/main/webapp/WEB-INF/mail/crawler.hbs
 delete mode 100644 src/main/webapp/WEB-INF/mail/solr_status.hbs

diff --git a/pom.xml b/pom.xml
index d54c3f322..c05150aa6 100644
--- a/pom.xml
+++ b/pom.xml
@@ -43,6 +43,7 @@
 		<lasta.taglib.version>0.6.0</lasta.taglib.version>
 		<servlet.version>3.1.0</servlet.version>
 		<jsp.version>2.3.1</jsp.version>
+		<mailflute.version>0.4.2</mailflute.version>
 
 		<!-- Partner Library -->
 		<slf4j.version>1.7.12</slf4j.version>
@@ -434,6 +435,11 @@
 			<artifactId>corelib</artifactId>
 			<version>0.3.1-SNAPSHOT</version>
 		</dependency>
+		<dependency>
+			<groupId>org.apache.commons</groupId>
+			<artifactId>commons-lang3</artifactId>
+			<version>3.4</version>
+		</dependency>
 		<dependency>
 			<groupId>com.ibm.icu</groupId>
 			<artifactId>icu4j</artifactId>
@@ -457,16 +463,9 @@
 
 		<!-- mail -->
 		<dependency>
-			<!-- TODO remove -->
-			<groupId>com.sun.mail</groupId>
-			<artifactId>javax.mail</artifactId>
-			<version>1.5.2</version>
-		</dependency>
-		<dependency>
-			<!-- TODO remove -->
-			<groupId>javax.activation</groupId>
-			<artifactId>activation</artifactId>
-			<version>1.1.1</version>
+			<groupId>org.dbflute.mail</groupId>
+			<artifactId>mailflute</artifactId>
+			<version>${mailflute.version}</version>
 		</dependency>
 
 		<!-- csv -->
@@ -503,10 +502,9 @@
 
 		<!-- template -->
 		<dependency>
-			<!-- TODO remove with lasta -->
 			<groupId>com.github.jknack</groupId>
 			<artifactId>handlebars</artifactId>
-			<version>2.2.2</version>
+			<version>2.3.2</version>
 		</dependency>
 
 		<!-- groovy -->
diff --git a/src/main/java/org/codelibs/fess/exec/Crawler.java b/src/main/java/org/codelibs/fess/exec/Crawler.java
index 15c759886..e007054cd 100644
--- a/src/main/java/org/codelibs/fess/exec/Crawler.java
+++ b/src/main/java/org/codelibs/fess/exec/Crawler.java
@@ -27,10 +27,12 @@ import java.util.Date;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.stream.Stream;
 
 import javax.annotation.Resource;
 
 import org.codelibs.core.CoreLibConstants;
+import org.codelibs.core.beans.util.BeanUtil;
 import org.codelibs.core.lang.StringUtil;
 import org.codelibs.core.misc.DynamicProperties;
 import org.codelibs.fess.Constants;
@@ -41,27 +43,23 @@ import org.codelibs.fess.es.client.FessEsClient;
 import org.codelibs.fess.helper.CrawlingSessionHelper;
 import org.codelibs.fess.helper.DataIndexHelper;
 import org.codelibs.fess.helper.FieldHelper;
-import org.codelibs.fess.helper.MailHelper;
 import org.codelibs.fess.helper.OverlappingHostHelper;
 import org.codelibs.fess.helper.PathMappingHelper;
 import org.codelibs.fess.helper.WebFsIndexHelper;
+import org.codelibs.fess.mylasta.direction.FessConfig;
+import org.codelibs.fess.mylasta.mail.CrawlerPostcard;
 import org.codelibs.fess.util.ComponentUtil;
-import org.codelibs.fess.util.ResourceUtil;
 import org.elasticsearch.index.query.QueryBuilder;
 import org.elasticsearch.index.query.QueryBuilders;
 import org.kohsuke.args4j.CmdLineException;
 import org.kohsuke.args4j.CmdLineParser;
 import org.kohsuke.args4j.Option;
+import org.lastaflute.core.mail.Postbox;
 import org.lastaflute.di.core.SingletonLaContainer;
 import org.lastaflute.di.core.factory.SingletonLaContainerFactory;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import com.github.jknack.handlebars.Context;
-import com.github.jknack.handlebars.Handlebars;
-import com.github.jknack.handlebars.Template;
-import com.github.jknack.handlebars.io.FileTemplateLoader;
-
 public class Crawler implements Serializable {
 
     private static final long serialVersionUID = 1L;
@@ -72,8 +70,6 @@ public class Crawler implements Serializable {
 
     private static final String DATA_CRAWLING_PROCESS = "DataStoreCrawler";
 
-    private static final String MAIL_TEMPLATE_NAME = "crawler";
-
     @Resource
     protected FessEsClient fessEsClient;
 
@@ -92,11 +88,6 @@ public class Crawler implements Serializable {
     @Resource
     protected DynamicProperties crawlerProperties;
 
-    @Resource
-    protected MailHelper mailHelper;
-
-    public String notificationSubject = "[FESS] Crawler completed";
-
     protected static class Options {
 
         @Option(name = "-s", aliases = "--sessionId", metaVar = "sessionId", usage = "Session ID")
@@ -304,15 +295,17 @@ public class Crawler implements Serializable {
                 // ignore
             }
 
-            final FileTemplateLoader loader = new FileTemplateLoader(ResourceUtil.getMailTemplatePath(StringUtil.EMPTY).toFile());
-            final Handlebars handlebars = new Handlebars(loader);
-
             try {
-                final Template template = handlebars.compile(MAIL_TEMPLATE_NAME);
-                final Context hbsContext = Context.newContext(dataMap);
-                final String body = template.apply(hbsContext);
-
-                mailHelper.send(toAddresses, notificationSubject, body);
+                FessConfig fessConfig = ComponentUtil.getComponent(FessConfig.class);
+                Postbox postbox = ComponentUtil.getComponent(Postbox.class);
+                CrawlerPostcard.droppedInto(postbox, postcard -> {
+                    postcard.setFrom(fessConfig.getMailFromAddress(), fessConfig.getMailFromName());
+                    postcard.addReplyTo(fessConfig.getMailReturnPath());
+                    Stream.of(toAddresses).forEach(address -> {
+                        postcard.addTo(address);
+                    });
+                    BeanUtil.copyMapToBean(dataMap, postcard);
+                });
             } catch (final Exception e) {
                 logger.warn("Failed to send the notification.", e);
             }
diff --git a/src/main/java/org/codelibs/fess/helper/MailHelper.java b/src/main/java/org/codelibs/fess/helper/MailHelper.java
deleted file mode 100644
index c5da358eb..000000000
--- a/src/main/java/org/codelibs/fess/helper/MailHelper.java
+++ /dev/null
@@ -1,79 +0,0 @@
-/*
- * Copyright 2009-2015 the CodeLibs Project and the Others.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
- * either express or implied. See the License for the specific language
- * governing permissions and limitations under the License.
- */
-
-package org.codelibs.fess.helper;
-
-import java.io.Serializable;
-import java.util.Arrays;
-import java.util.Date;
-import java.util.Properties;
-
-import javax.mail.Message;
-import javax.mail.MessagingException;
-import javax.mail.Session;
-import javax.mail.Transport;
-import javax.mail.internet.InternetAddress;
-import javax.mail.internet.MimeMessage;
-
-import org.codelibs.fess.Constants;
-import org.codelibs.fess.exception.FessSystemException;
-
-public class MailHelper implements Serializable {
-
-    private static final long serialVersionUID = 1L;
-
-    public String from = Constants.DEFAULT_FROM_EMAIL;
-
-    private final boolean debug = false;
-
-    Properties props = new Properties();
-
-    public void addProperty(final String key, final String value) {
-        props.put(key, value);
-    }
-
-    public void send(final String[] toAddresses, final String subject, final String text) {
-        if (toAddresses == null || toAddresses.length == 0) {
-            throw new FessSystemException("TO address is empty.");
-        }
-
-        if (debug) {
-            props.put("mail.debug", Constants.TRUE);
-        }
-
-        final Session session = Session.getInstance(props, null);
-        session.setDebug(debug);
-
-        try {
-            // create a message
-            final MimeMessage msg = new MimeMessage(session);
-            msg.setFrom(new InternetAddress(from));
-            final InternetAddress[] address = new InternetAddress[toAddresses.length];
-            for (int i = 0; i < toAddresses.length; i++) {
-                address[i] = new InternetAddress(toAddresses[i]);
-            }
-            msg.setRecipients(Message.RecipientType.TO, address);
-            msg.setSubject(subject);
-            msg.setSentDate(new Date());
-            msg.setText(text);
-
-            Transport.send(msg);
-        } catch (final MessagingException e) {
-            throw new FessSystemException("Failed to send " + Arrays.toString(toAddresses), e);
-        }
-    }
-
-}
diff --git a/src/main/java/org/codelibs/fess/mylasta/action/FessLabels.java b/src/main/java/org/codelibs/fess/mylasta/action/FessLabels.java
index b87f7ef61..98e7fe43b 100644
--- a/src/main/java/org/codelibs/fess/mylasta/action/FessLabels.java
+++ b/src/main/java/org/codelibs/fess/mylasta/action/FessLabels.java
@@ -509,7 +509,7 @@ public class FessLabels extends ActionMessages {
     /** The key of the message: General */
     public static final String LABELS_menu_crawl_config = "{labels.menu_crawl_config}";
 
-    /** The key of the message: Scheduled Job */
+    /** The key of the message: Scheduler */
     public static final String LABELS_menu_scheduled_job_config = "{labels.menu_scheduled_job_config}";
 
     /** The key of the message: System */
@@ -518,7 +518,7 @@ public class FessLabels extends ActionMessages {
     /** The key of the message: Index */
     public static final String LABELS_menu_document_config = "{labels.menu_document_config}";
 
-    /** The key of the message: Design */
+    /** The key of the message: Page Design */
     public static final String LABELS_menu_design = "{labels.menu_design}";
 
     /** The key of the message: Dictionary */
@@ -629,6 +629,9 @@ public class FessLabels extends ActionMessages {
     /** The key of the message: Help */
     public static final String LABELS_HEADER_HELP = "{labels.header.help}";
 
+    /** The key of the message: Search... */
+    public static final String LABELS_SIDEBAR_placeholder_search = "{labels.sidebar.placeholder_search}";
+
     /** The key of the message: Copyright(C) 2009-2015 CodeLibs Project. <span class="br-phone"></span>All Rights Reserved. */
     public static final String LABELS_FOOTER_COPYRIGHT = "{labels.footer.copyright}";
 
@@ -1517,7 +1520,7 @@ public class FessLabels extends ActionMessages {
     /** The key of the message: Next */
     public static final String LABELS_crawling_session_next_page = "{labels.crawling_session_next_page}";
 
-    /** The key of the message: Session ID:  */
+    /** The key of the message: Session ID */
     public static final String LABELS_crawling_session_session_id_search = "{labels.crawling_session_session_id_search}";
 
     /** The key of the message: Session ID */
@@ -2051,7 +2054,7 @@ public class FessLabels extends ActionMessages {
     /** The key of the message: Edit */
     public static final String LABELS_key_match_button_edit = "{labels.key_match_button_edit}";
 
-    /** The key of the message: Design */
+    /** The key of the message: Page Design */
     public static final String LABELS_design_configuration = "{labels.design_configuration}";
 
     /** The key of the message: File Upload */
@@ -2273,7 +2276,7 @@ public class FessLabels extends ActionMessages {
     /** The key of the message: Crawling Configuration */
     public static final String LABELS_wizard_crawling_config_title = "{labels.wizard_crawling_config_title}";
 
-    /** The key of the message: Crawling Setting */
+    /** The key of the message: Crawling Settings */
     public static final String LABELS_wizard_crawling_setting_title = "{labels.wizard_crawling_setting_title}";
 
     /** The key of the message: Name */
@@ -2333,7 +2336,7 @@ public class FessLabels extends ActionMessages {
     /** The key of the message: Skip */
     public static final String LABELS_wizard_button_finish = "{labels.wizard_button_finish}";
 
-    /** The key of the message: Search Result */
+    /** The key of the message: Search */
     public static final String LABELS_search_list_configuration = "{labels.search_list_configuration}";
 
     /** The key of the message: Type a search query. */
@@ -2471,6 +2474,9 @@ public class FessLabels extends ActionMessages {
     /** The key of the message: Confirm */
     public static final String LABELS_failure_url_link_confirm = "{labels.failure_url_link_confirm}";
 
+    /** The key of the message: Delete All */
+    public static final String LABELS_failure_url_delete_all_link = "{labels.failure_url_delete_all_link}";
+
     /** The key of the message: Do you want to delete all? */
     public static final String LABELS_failure_url_delete_all_confirmation = "{labels.failure_url_delete_all_confirmation}";
 
@@ -3607,7 +3613,7 @@ public class FessLabels extends ActionMessages {
     /** The key of the message: Fess */
     public static final String LABELS_admin_brand_title = "{labels.admin_brand_title}";
 
-    /** The key of the message: Fess Dashboard */
+    /** The key of the message: Dashboard */
     public static final String LABELS_admin_dashboard_title = "{labels.admin_dashboard_title}";
 
     /** The key of the message: Toggle navigation */
diff --git a/src/main/java/org/codelibs/fess/mylasta/direction/FessConfig.java b/src/main/java/org/codelibs/fess/mylasta/direction/FessConfig.java
index fe4526280..c4713d63b 100644
--- a/src/main/java/org/codelibs/fess/mylasta/direction/FessConfig.java
+++ b/src/main/java/org/codelibs/fess/mylasta/direction/FessConfig.java
@@ -31,6 +31,12 @@ public interface FessConfig extends FessEnv {
     /** The key of the configuration. e.g. true */
     String PAGING_PAGE_RANGE_FILL_LIMIT = "paging.page.range.fill.limit";
 
+    /** The key of the configuration. e.g. Administrator */
+    String MAIL_FROM_NAME = "mail.from.name";
+
+    /** The key of the configuration. e.g. root@localhost */
+    String MAIL_FROM_ADDRESS = "mail.from.address";
+
     /**
      * Get the value of property as {@link String}.
      * @param propertyKey The key of the property. (NotNull)
@@ -155,6 +161,21 @@ public interface FessConfig extends FessEnv {
      */
     boolean isPagingPageRangeFillLimit();
 
+    /**
+     * Get the value for the key 'mail.from.name'. <br>
+     * The value is, e.g. Administrator <br>
+     * comment: From
+     * @return The value of found property. (NotNull: if not found, exception but basically no way)
+     */
+    String getMailFromName();
+
+    /**
+     * Get the value for the key 'mail.from.address'. <br>
+     * The value is, e.g. root@localhost <br>
+     * @return The value of found property. (NotNull: if not found, exception but basically no way)
+     */
+    String getMailFromAddress();
+
     /**
      * The simple implementation for configuration.
      * @author FreeGen
@@ -215,5 +236,13 @@ public interface FessConfig extends FessEnv {
         public boolean isPagingPageRangeFillLimit() {
             return is(FessConfig.PAGING_PAGE_RANGE_FILL_LIMIT);
         }
+
+        public String getMailFromName() {
+            return get(FessConfig.MAIL_FROM_NAME);
+        }
+
+        public String getMailFromAddress() {
+            return get(FessConfig.MAIL_FROM_ADDRESS);
+        }
     }
 }
diff --git a/src/main/java/org/codelibs/fess/mylasta/mail/CrawlerPostcard.java b/src/main/java/org/codelibs/fess/mylasta/mail/CrawlerPostcard.java
new file mode 100644
index 000000000..62a6ec027
--- /dev/null
+++ b/src/main/java/org/codelibs/fess/mylasta/mail/CrawlerPostcard.java
@@ -0,0 +1,268 @@
+package org.codelibs.fess.mylasta.mail;
+
+import org.lastaflute.core.mail.LaTypicalPostcard;
+import org.lastaflute.core.mail.MPCall;
+import org.lastaflute.core.mail.Postbox;
+
+/**
+ * The postcard for MailFlute on LastaFlute.
+ * @author FreeGen
+ */
+public class CrawlerPostcard extends LaTypicalPostcard {
+
+    // ===================================================================================
+    //                                                                          Definition
+    //                                                                          ==========
+    public static final String PATH = "crawler.dfmail";
+
+    // ===================================================================================
+    //                                                                         Entry Point
+    //                                                                         ===========
+    public static CrawlerPostcard droppedInto(Postbox postbox, MPCall<CrawlerPostcard> postcardLambda) {
+        CrawlerPostcard postcard = new CrawlerPostcard();
+        postcardLambda.write(postcard);
+        postbox.post(postcard);
+        return postcard;
+    }
+
+    // ===================================================================================
+    //                                                                           Meta Data
+    //                                                                           =========
+    @Override
+    protected String getBodyFile() {
+        return PATH;
+    }
+
+    @Override
+    protected String[] getPropertyNames() {
+        return new String[] { "hostname", "webFsCrawlStartTime", "webFsCrawlEndTime", "webFsCrawlExecTime", "webFsIndexExecTime",
+                "webFsIndexSize", "dataCrawlStartTime", "dataCrawlEndTime", "dataCrawlExecTime", "dataIndexExecTime", "dataFsIndexSize",
+                "commitStartTime", "commitEndTime", "commitExecTime", "optimizeStartTime", "optimizeEndTime", "optimizeExecTime",
+                "crawlerStartTime", "crawlerEndTime", "crawlerExecTime" };
+    }
+
+    // ===================================================================================
+    //                                                                    Postcard Request
+    //                                                                    ================
+    // -----------------------------------------------------
+    //                                          Mail Address
+    //                                          ------------
+    public void setFrom(String from, String personal) {
+        doSetFrom(from, personal);
+    }
+
+    public void addTo(String to) {
+        doAddTo(to);
+    }
+
+    public void addTo(String to, String personal) {
+        doAddTo(to, personal);
+    }
+
+    public void addCc(String cc) {
+        doAddCc(cc);
+    }
+
+    public void addCc(String cc, String personal) {
+        doAddCc(cc, personal);
+    }
+
+    public void addBcc(String bcc) {
+        doAddBcc(bcc);
+    }
+
+    public void addBcc(String bcc, String personal) {
+        doAddBcc(bcc, personal);
+    }
+
+    public void addReplyTo(String replyTo) {
+        doAddReplyTo(replyTo);
+    }
+
+    public void addReplyTo(String replyTo, String personal) {
+        doAddReplyTo(replyTo, personal);
+    }
+
+    // -----------------------------------------------------
+    //                                  Application Variable
+    //                                  --------------------
+    /**
+     * Set the value of hostname, used in parameter comment. <br>
+     * Even if empty string, treated as empty plainly. So "IF pmb != null" is false if empty.
+     * @param hostname The parameter value of hostname. (NotNull)
+     */
+    public void setHostname(String hostname) {
+        registerVariable("hostname", hostname);
+    }
+
+    /**
+     * Set the value of webFsCrawlStartTime, used in parameter comment. <br>
+     * Even if empty string, treated as empty plainly. So "IF pmb != null" is false if empty.
+     * @param webFsCrawlStartTime The parameter value of webFsCrawlStartTime. (NotNull)
+     */
+    public void setWebFsCrawlStartTime(String webFsCrawlStartTime) {
+        registerVariable("webFsCrawlStartTime", webFsCrawlStartTime);
+    }
+
+    /**
+     * Set the value of webFsCrawlEndTime, used in parameter comment. <br>
+     * Even if empty string, treated as empty plainly. So "IF pmb != null" is false if empty.
+     * @param webFsCrawlEndTime The parameter value of webFsCrawlEndTime. (NotNull)
+     */
+    public void setWebFsCrawlEndTime(String webFsCrawlEndTime) {
+        registerVariable("webFsCrawlEndTime", webFsCrawlEndTime);
+    }
+
+    /**
+     * Set the value of webFsCrawlExecTime, used in parameter comment. <br>
+     * Even if empty string, treated as empty plainly. So "IF pmb != null" is false if empty.
+     * @param webFsCrawlExecTime The parameter value of webFsCrawlExecTime. (NotNull)
+     */
+    public void setWebFsCrawlExecTime(String webFsCrawlExecTime) {
+        registerVariable("webFsCrawlExecTime", webFsCrawlExecTime);
+    }
+
+    /**
+     * Set the value of webFsIndexExecTime, used in parameter comment. <br>
+     * Even if empty string, treated as empty plainly. So "IF pmb != null" is false if empty.
+     * @param webFsIndexExecTime The parameter value of webFsIndexExecTime. (NotNull)
+     */
+    public void setWebFsIndexExecTime(String webFsIndexExecTime) {
+        registerVariable("webFsIndexExecTime", webFsIndexExecTime);
+    }
+
+    /**
+     * Set the value of webFsIndexSize, used in parameter comment. <br>
+     * Even if empty string, treated as empty plainly. So "IF pmb != null" is false if empty.
+     * @param webFsIndexSize The parameter value of webFsIndexSize. (NotNull)
+     */
+    public void setWebFsIndexSize(String webFsIndexSize) {
+        registerVariable("webFsIndexSize", webFsIndexSize);
+    }
+
+    /**
+     * Set the value of dataCrawlStartTime, used in parameter comment. <br>
+     * Even if empty string, treated as empty plainly. So "IF pmb != null" is false if empty.
+     * @param dataCrawlStartTime The parameter value of dataCrawlStartTime. (NotNull)
+     */
+    public void setDataCrawlStartTime(String dataCrawlStartTime) {
+        registerVariable("dataCrawlStartTime", dataCrawlStartTime);
+    }
+
+    /**
+     * Set the value of dataCrawlEndTime, used in parameter comment. <br>
+     * Even if empty string, treated as empty plainly. So "IF pmb != null" is false if empty.
+     * @param dataCrawlEndTime The parameter value of dataCrawlEndTime. (NotNull)
+     */
+    public void setDataCrawlEndTime(String dataCrawlEndTime) {
+        registerVariable("dataCrawlEndTime", dataCrawlEndTime);
+    }
+
+    /**
+     * Set the value of dataCrawlExecTime, used in parameter comment. <br>
+     * Even if empty string, treated as empty plainly. So "IF pmb != null" is false if empty.
+     * @param dataCrawlExecTime The parameter value of dataCrawlExecTime. (NotNull)
+     */
+    public void setDataCrawlExecTime(String dataCrawlExecTime) {
+        registerVariable("dataCrawlExecTime", dataCrawlExecTime);
+    }
+
+    /**
+     * Set the value of dataIndexExecTime, used in parameter comment. <br>
+     * Even if empty string, treated as empty plainly. So "IF pmb != null" is false if empty.
+     * @param dataIndexExecTime The parameter value of dataIndexExecTime. (NotNull)
+     */
+    public void setDataIndexExecTime(String dataIndexExecTime) {
+        registerVariable("dataIndexExecTime", dataIndexExecTime);
+    }
+
+    /**
+     * Set the value of dataFsIndexSize, used in parameter comment. <br>
+     * Even if empty string, treated as empty plainly. So "IF pmb != null" is false if empty.
+     * @param dataFsIndexSize The parameter value of dataFsIndexSize. (NotNull)
+     */
+    public void setDataFsIndexSize(String dataFsIndexSize) {
+        registerVariable("dataFsIndexSize", dataFsIndexSize);
+    }
+
+    /**
+     * Set the value of commitStartTime, used in parameter comment. <br>
+     * Even if empty string, treated as empty plainly. So "IF pmb != null" is false if empty.
+     * @param commitStartTime The parameter value of commitStartTime. (NotNull)
+     */
+    public void setCommitStartTime(String commitStartTime) {
+        registerVariable("commitStartTime", commitStartTime);
+    }
+
+    /**
+     * Set the value of commitEndTime, used in parameter comment. <br>
+     * Even if empty string, treated as empty plainly. So "IF pmb != null" is false if empty.
+     * @param commitEndTime The parameter value of commitEndTime. (NotNull)
+     */
+    public void setCommitEndTime(String commitEndTime) {
+        registerVariable("commitEndTime", commitEndTime);
+    }
+
+    /**
+     * Set the value of commitExecTime, used in parameter comment. <br>
+     * Even if empty string, treated as empty plainly. So "IF pmb != null" is false if empty.
+     * @param commitExecTime The parameter value of commitExecTime. (NotNull)
+     */
+    public void setCommitExecTime(String commitExecTime) {
+        registerVariable("commitExecTime", commitExecTime);
+    }
+
+    /**
+     * Set the value of optimizeStartTime, used in parameter comment. <br>
+     * Even if empty string, treated as empty plainly. So "IF pmb != null" is false if empty.
+     * @param optimizeStartTime The parameter value of optimizeStartTime. (NotNull)
+     */
+    public void setOptimizeStartTime(String optimizeStartTime) {
+        registerVariable("optimizeStartTime", optimizeStartTime);
+    }
+
+    /**
+     * Set the value of optimizeEndTime, used in parameter comment. <br>
+     * Even if empty string, treated as empty plainly. So "IF pmb != null" is false if empty.
+     * @param optimizeEndTime The parameter value of optimizeEndTime. (NotNull)
+     */
+    public void setOptimizeEndTime(String optimizeEndTime) {
+        registerVariable("optimizeEndTime", optimizeEndTime);
+    }
+
+    /**
+     * Set the value of optimizeExecTime, used in parameter comment. <br>
+     * Even if empty string, treated as empty plainly. So "IF pmb != null" is false if empty.
+     * @param optimizeExecTime The parameter value of optimizeExecTime. (NotNull)
+     */
+    public void setOptimizeExecTime(String optimizeExecTime) {
+        registerVariable("optimizeExecTime", optimizeExecTime);
+    }
+
+    /**
+     * Set the value of crawlerStartTime, used in parameter comment. <br>
+     * Even if empty string, treated as empty plainly. So "IF pmb != null" is false if empty.
+     * @param crawlerStartTime The parameter value of crawlerStartTime. (NotNull)
+     */
+    public void setCrawlerStartTime(String crawlerStartTime) {
+        registerVariable("crawlerStartTime", crawlerStartTime);
+    }
+
+    /**
+     * Set the value of crawlerEndTime, used in parameter comment. <br>
+     * Even if empty string, treated as empty plainly. So "IF pmb != null" is false if empty.
+     * @param crawlerEndTime The parameter value of crawlerEndTime. (NotNull)
+     */
+    public void setCrawlerEndTime(String crawlerEndTime) {
+        registerVariable("crawlerEndTime", crawlerEndTime);
+    }
+
+    /**
+     * Set the value of crawlerExecTime, used in parameter comment. <br>
+     * Even if empty string, treated as empty plainly. So "IF pmb != null" is false if empty.
+     * @param crawlerExecTime The parameter value of crawlerExecTime. (NotNull)
+     */
+    public void setCrawlerExecTime(String crawlerExecTime) {
+        registerVariable("crawlerExecTime", crawlerExecTime);
+    }
+}
diff --git a/src/main/java/org/codelibs/fess/mylasta/mail/EsStatusPostcard.java b/src/main/java/org/codelibs/fess/mylasta/mail/EsStatusPostcard.java
new file mode 100644
index 000000000..52ceabbe5
--- /dev/null
+++ b/src/main/java/org/codelibs/fess/mylasta/mail/EsStatusPostcard.java
@@ -0,0 +1,139 @@
+package org.codelibs.fess.mylasta.mail;
+
+import org.lastaflute.core.mail.LaTypicalPostcard;
+import org.lastaflute.core.mail.MPCall;
+import org.lastaflute.core.mail.Postbox;
+
+/**
+ * The postcard for MailFlute on LastaFlute.
+ * @author FreeGen
+ */
+public class EsStatusPostcard extends LaTypicalPostcard {
+
+    // ===================================================================================
+    //                                                                          Definition
+    //                                                                          ==========
+    public static final String PATH = "es_status.dfmail";
+
+    // ===================================================================================
+    //                                                                         Entry Point
+    //                                                                         ===========
+    public static EsStatusPostcard droppedInto(Postbox postbox, MPCall<EsStatusPostcard> postcardLambda) {
+        EsStatusPostcard postcard = new EsStatusPostcard();
+        postcardLambda.write(postcard);
+        postbox.post(postcard);
+        return postcard;
+    }
+
+    // ===================================================================================
+    //                                                                           Meta Data
+    //                                                                           =========
+    @Override
+    protected String getBodyFile() {
+        return PATH;
+    }
+
+    @Override
+    protected String[] getPropertyNames() {
+        return new String[] { "hostname", "server", "statusBefore", "statusAfter", "indexBefore", "indexAfter" };
+    }
+
+    // ===================================================================================
+    //                                                                    Postcard Request
+    //                                                                    ================
+    // -----------------------------------------------------
+    //                                          Mail Address
+    //                                          ------------
+    public void setFrom(String from, String personal) {
+        doSetFrom(from, personal);
+    }
+
+    public void addTo(String to) {
+        doAddTo(to);
+    }
+
+    public void addTo(String to, String personal) {
+        doAddTo(to, personal);
+    }
+
+    public void addCc(String cc) {
+        doAddCc(cc);
+    }
+
+    public void addCc(String cc, String personal) {
+        doAddCc(cc, personal);
+    }
+
+    public void addBcc(String bcc) {
+        doAddBcc(bcc);
+    }
+
+    public void addBcc(String bcc, String personal) {
+        doAddBcc(bcc, personal);
+    }
+
+    public void addReplyTo(String replyTo) {
+        doAddReplyTo(replyTo);
+    }
+
+    public void addReplyTo(String replyTo, String personal) {
+        doAddReplyTo(replyTo, personal);
+    }
+
+    // -----------------------------------------------------
+    //                                  Application Variable
+    //                                  --------------------
+    /**
+     * Set the value of hostname, used in parameter comment. <br>
+     * Even if empty string, treated as empty plainly. So "IF pmb != null" is false if empty.
+     * @param hostname The parameter value of hostname. (NotNull)
+     */
+    public void setHostname(String hostname) {
+        registerVariable("hostname", hostname);
+    }
+
+    /**
+     * Set the value of server, used in parameter comment. <br>
+     * Even if empty string, treated as empty plainly. So "IF pmb != null" is false if empty.
+     * @param server The parameter value of server. (NotNull)
+     */
+    public void setServer(String server) {
+        registerVariable("server", server);
+    }
+
+    /**
+     * Set the value of statusBefore, used in parameter comment. <br>
+     * Even if empty string, treated as empty plainly. So "IF pmb != null" is false if empty.
+     * @param statusBefore The parameter value of statusBefore. (NotNull)
+     */
+    public void setStatusBefore(String statusBefore) {
+        registerVariable("statusBefore", statusBefore);
+    }
+
+    /**
+     * Set the value of statusAfter, used in parameter comment. <br>
+     * Even if empty string, treated as empty plainly. So "IF pmb != null" is false if empty.
+     * @param statusAfter The parameter value of statusAfter. (NotNull)
+     */
+    public void setStatusAfter(String statusAfter) {
+        registerVariable("statusAfter", statusAfter);
+    }
+
+    /**
+     * Set the value of indexBefore, used in parameter comment. <br>
+     * Even if empty string, treated as empty plainly. So "IF pmb != null" is false if empty.
+     * @param indexBefore The parameter value of indexBefore. (NotNull)
+     */
+    public void setIndexBefore(String indexBefore) {
+        registerVariable("indexBefore", indexBefore);
+    }
+
+    /**
+     * Set the value of indexAfter, used in parameter comment. <br>
+     * Even if empty string, treated as empty plainly. So "IF pmb != null" is false if empty.
+     * @param indexAfter The parameter value of indexAfter. (NotNull)
+     */
+    public void setIndexAfter(String indexAfter) {
+        registerVariable("indexAfter", indexAfter);
+    }
+}
diff --git a/src/main/java/org/codelibs/fess/util/ComponentUtil.java b/src/main/java/org/codelibs/fess/util/ComponentUtil.java
index fe2a8b481..459adc777 100644
--- a/src/main/java/org/codelibs/fess/util/ComponentUtil.java
+++ b/src/main/java/org/codelibs/fess/util/ComponentUtil.java
@@ -37,7 +37,6 @@ import org.codelibs.fess.helper.IntervalControlHelper;
 import org.codelibs.fess.helper.JobHelper;
 import org.codelibs.fess.helper.KeyMatchHelper;
 import org.codelibs.fess.helper.LabelTypeHelper;
-import org.codelibs.fess.helper.MailHelper;
 import org.codelibs.fess.helper.OverlappingHostHelper;
 import org.codelibs.fess.helper.PathMappingHelper;
 import org.codelibs.fess.helper.QueryHelper;
@@ -67,8 +66,6 @@ public final class ComponentUtil {
 
     private static final String INDEX_UPDATER = "indexUpdater";
 
-    private static final String MAIL_HELPER = "mailHelper";
-
     private static final String FILE_TYPE_HELPER = "fileTypeHelper";
 
     private static final String EXTRACTOR_FACTORY = "extractorFactory";
@@ -216,10 +213,6 @@ public final class ComponentUtil {
         return SingletonLaContainer.getComponent(name + JOB_EXECUTOR_SUFFIX);
     }
 
-    public static MailHelper getMailHelper() {
-        return SingletonLaContainer.getComponent(MAIL_HELPER);
-    }
-
     public static FileTypeHelper getFileTypeHelper() {
         return SingletonLaContainer.getComponent(FILE_TYPE_HELPER);
     }
diff --git a/src/main/resources/fess.xml b/src/main/resources/fess.xml
index 0bbf083c0..30c495589 100644
--- a/src/main/resources/fess.xml
+++ b/src/main/resources/fess.xml
@@ -121,15 +121,6 @@
 	</component>
 	<component name="crawlingSessionHelper" class="org.codelibs.fess.helper.CrawlingSessionHelper">
 	</component>
-	<component name="mailHelper" class="org.codelibs.fess.helper.MailHelper">
-		<postConstruct name="addProperty">
-			<arg>"mail.smtp.host"</arg>
-			<arg>"localhost"</arg>
-		</postConstruct>
-		<!--
-		<property name="from">"Administrator &lt;root@localhost&gt;"</property>
-		-->
-	</component>
 	<component name="roleQueryHelper" class="org.codelibs.fess.helper.impl.RoleQueryHelperImpl">
 		<!-- ex. parameter: fessRoles=123%0aadmin -->
 		<!-- 
diff --git a/src/main/resources/fess_config.properties b/src/main/resources/fess_config.properties
index 745ca8914..3d2358ba9 100644
--- a/src/main/resources/fess_config.properties
+++ b/src/main/resources/fess_config.properties
@@ -43,3 +43,11 @@ paging.page.range.size = 3
 
 # The option 'fillLimit' of page range for paging
 paging.page.range.fill.limit = true
+
+# ----------------------------------------------------------
+#                                                      Mail
+#                                                     ------
+# From
+mail.from.name = Administrator
+mail.from.address = root@localhost
+
diff --git a/src/main/resources/fess_env_production.properties b/src/main/resources/fess_env_production.properties
index 00561aebb..683bba2ed 100644
--- a/src/main/resources/fess_env_production.properties
+++ b/src/main/resources/fess_env_production.properties
@@ -25,7 +25,7 @@ time.adjust.time.millis = 0
 #                                                      Mail
 #                                                     ------
 # Does it send mock mail? (true: no send actually, logging only)
-mail.send.mock = true
+mail.send.mock = false
 
 # SMTP server settings for main: host:port
 mail.smtp.server.main.host.and.port = localhost:25
@@ -36,7 +36,6 @@ mail.subject.test.prefix =
 # The common return path of all mail
 mail.return.path = root@localhost
 
-
 # ========================================================================================
 #                                                                                      DB
 #                                                                                     ====
diff --git a/src/main/resources/mail/crawler.dfmail b/src/main/resources/mail/crawler.dfmail
new file mode 100644
index 000000000..5d04f9ac4
--- /dev/null
+++ b/src/main/resources/mail/crawler.dfmail
@@ -0,0 +1,43 @@
+/*
+ [Crawler Notification]
+ Crawler notification mail.
+*/
+subject: [FESS] Crawler completed: /*pmb.hostname*/
+>>>
+--- Server Info ---
+Host Name: /*IF pmb.hostname != null*//*pmb.hostname*//*END*//*IF pmb.hostname == null*/Unknown/*END*/
+/*IF pmb.webFsIndexSize != null*/
+--- Web/FileSystem Crawler ---
+Start Time: /*pmb.webFsCrawlStartTime*/
+End Time:   /*pmb.webFsCrawlEndTime*/
+Exec Time:  /*pmb.webFsCrawlExecTime*/ms
+
+--- Web/FileSystem Indexer ---
+Exec Time:  /*pmb.webFsIndexExecTime*/
+Num of Doc: /*pmb.webFsIndexSize*/ docs
+/*END*//*IF pmb.dataFsIndexSize != null*/
+--- Data Store Crawler ---
+Start Time: /*pmb.dataCrawlStartTime*/
+End Time:   /*pmb.dataCrawlEndTime*/
+Exec Time:  /*pmb.dataCrawlExecTime*/ms
+
+--- Data Store Indexer ---
+Exec Time:  /*pmb.dataIndexExecTime*/
+Num of Doc: /*pmb.dataFsIndexSize*/ docs
+/*END*//*IF pmb.commitExecTime != null*/
+--- Indexer(Commit) ---
+Start Time: /*pmb.commitStartTime*/
+End Time:   /*pmb.commitEndTime*/
+Exec Time:  /*pmb.commitExecTime*/ms
+/*END*//*IF pmb.optimizeEndTime*/
+--- Indexer(Optimize) ---
+Start Time: /*pmb.optimizeStartTime*/
+End Time:   /*pmb.optimizeEndTime*/
+Exec Time:  /*pmb.optimizeExecTime*/ms
+/*END*/
+--- Total ---
+Start Time: /*pmb.crawlerStartTime*/
+End Time:   /*pmb.crawlerEndTime*/
+Exec Time:  /*pmb.crawlerExecTime*/ms
+Status:     /*IF pmb.success != null*/Success/*END*//*IF pmb.success == null*/Fail/*END*/
+
diff --git a/src/main/resources/mail/es_status.dfmail b/src/main/resources/mail/es_status.dfmail
new file mode 100644
index 000000000..dc6274328
--- /dev/null
+++ b/src/main/resources/mail/es_status.dfmail
@@ -0,0 +1,13 @@
+/*
+ [Crawler Notification]
+*/
+subject: [FESS] Status Change: /*pmb.hostname*/
+>>>
+--- Server Info ---
+Host Name: /*IF pmb.hostname != null*//*pmb.hostname*//*END*//*IF pmb.hostname == null*/Unknown/*END*/
+Elasticsearch: /*pmb.server*/
+
+--- Status ---
+/*IF pmb.statusBefore != null*/Status: /*pmb.statusBefore*/ -> /*pmb.statusAfter*//*END*/
+/*IF pmb.indexBefore != null*/Index: /*pmb.indexBefore*/ -> /*pmb.indexAfter*//*END*/
+
diff --git a/src/main/webapp/WEB-INF/mail/crawler.hbs b/src/main/webapp/WEB-INF/mail/crawler.hbs
deleted file mode 100644
index 687ebb6f9..000000000
--- a/src/main/webapp/WEB-INF/mail/crawler.hbs
+++ /dev/null
@@ -1,37 +0,0 @@
---- Server Info ---
-{{#if hostname}}Host Name:  {{hostname}}{{/if}}
-{{#if webFsIndexSize}}
---- Web/FileSystem Crawler ---
-Start Time: {{webFsCrawlStartTime}}
-End Time:   {{webFsCrawlEndTime}}
-Exec Time:  {{webFsCrawlExecTime}}ms
-
---- Web/FileSystem Indexer ---
-Exec Time:  {{webFsIndexExecTime}}
-Num of Doc: {{webFsIndexSize}} docs
-{{/if}}{{#if dataFsIndexSize}}
---- Data Store Crawler ---
-Start Time: {{dataCrawlStartTime}}
-End Time:   {{dataCrawlEndTime}}
-Exec Time:  {{dataCrawlExecTime}}ms
-
---- Data Store Indexer ---
-Exec Time:  {{dataIndexExecTime}}
-Num of Doc: {{dataFsIndexSize}} docs
-{{/if}}{{#if commitExecTime}}
---- Indexer(Commit) ---
-Start Time: {{commitStartTime}}
-End Time:   {{commitEndTime}}
-Exec Time:  {{commitExecTime}}ms
-{{/if}}{{#if optimizeEndTime}}
---- Indexer(Optimize) ---
-Start Time: {{optimizeStartTime}}
-End Time:   {{optimizeEndTime}}
-Exec Time:  {{optimizeExecTime}}ms
-{{/if}}
---- Total ---
-Start Time: {{crawlerStartTime}}
-End Time:   {{crawlerEndTime}}
-Exec Time:  {{crawlerExecTime}}ms
-Status:     {{#if success}}Success{{else}}Fail{{/if}}
-
diff --git a/src/main/webapp/WEB-INF/mail/solr_status.hbs b/src/main/webapp/WEB-INF/mail/solr_status.hbs
deleted file mode 100644
index 5e8d7ebe2..000000000
--- a/src/main/webapp/WEB-INF/mail/solr_status.hbs
+++ /dev/null
@@ -1,8 +0,0 @@
---- Server Info ---
-{{#if hostname}}Host Name:  {{hostname}}{{/if}}
-Solr Server: {{server}}
-
---- Status ---
-{{#if statusBefore}}Status: {{statusBefore}} -> {{statusAfter}}{{/if}}
-{{#if indexBefore}}Index: {{indexBefore}} -> {{indexAfter}}{{/if}}
-
-- 
GitLab