You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@kylin.apache.org by xx...@apache.org on 2023/01/05 04:31:35 UTC

[kylin] branch kylin5.0.0-alpha-release created (now e27552561e)

This is an automated email from the ASF dual-hosted git repository.

xxyu pushed a change to branch kylin5.0.0-alpha-release
in repository https://gitbox.apache.org/repos/asf/kylin.git


      at e27552561e Update version from 5.0.0 to 5.0.0-alpha

This branch includes the following new commits:

     new 4fefcc059c [KYLIN-5361] modify email notification function and adjust  email hard code to config file
     new 501da3b275 [KYLIN-5361] modify email notification function and adjust  email hard code to config file
     new e27552561e Update version from 5.0.0 to 5.0.0-alpha

The 3 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.



[kylin] 02/03: [KYLIN-5361] modify email notification function and adjust email hard code to config file

Posted by xx...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

xxyu pushed a commit to branch kylin5.0.0-alpha-release
in repository https://gitbox.apache.org/repos/asf/kylin.git

commit 501da3b275bfdc17296f9b86c22a6bb01a512292
Author: cli2 <ss...@pku.edu.cn>
AuthorDate: Tue Jan 3 15:38:57 2023 +0800

    [KYLIN-5361] modify email notification function and adjust  email hard code to config file
---
 .../apache/kylin/common/constant/NonCustomProjectLevelConfig.java  | 7 ++++---
 1 file changed, 4 insertions(+), 3 deletions(-)

diff --git a/src/core-common/src/main/java/org/apache/kylin/common/constant/NonCustomProjectLevelConfig.java b/src/core-common/src/main/java/org/apache/kylin/common/constant/NonCustomProjectLevelConfig.java
index 5712084642..7158a8bb7f 100644
--- a/src/core-common/src/main/java/org/apache/kylin/common/constant/NonCustomProjectLevelConfig.java
+++ b/src/core-common/src/main/java/org/apache/kylin/common/constant/NonCustomProjectLevelConfig.java
@@ -40,9 +40,10 @@ public enum NonCustomProjectLevelConfig {
     PUSH_DOWN_ENABLED("kylin.query.pushdown-enabled"),
 
     JOB_DATA_LOAD_EMPTY_NOTIFICATION_ENABLED(
-            "kylin.job.notification-on-empty-data-load"), JOB_ERROR_NOTIFICATION_ENABLED(
-                    "kylin.job.notification-on-job-error"), NOTIFICATION_ADMIN_EMAILS(
-                            "kylin.job.notification-admin-emails"),
+            "kylin.job.notification-on-empty-data-load"), JOB_NOTIFICATION_ENABLED_STATES(
+                    "kylin.job.notification-enable-states"), NOTIFICATION_USER_EMAILS(
+                            "kylin.job.notification-user-emails"), NOTIFICATION_ON_METADATA_PERSIST(
+                                    "kylin.job.notification-on-metadata-persist"),
 
     ENGINE_SPARK_YARN_QUEUE("kylin.engine.spark-conf.spark.yarn.queue"),
 


[kylin] 01/03: [KYLIN-5361] modify email notification function and adjust email hard code to config file

Posted by xx...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

xxyu pushed a commit to branch kylin5.0.0-alpha-release
in repository https://gitbox.apache.org/repos/asf/kylin.git

commit 4fefcc059c8edad89b9b8445e1f4405a2743f700
Author: cli2 <cl...@ebay.com>
AuthorDate: Tue Dec 27 16:13:59 2022 +0800

    [KYLIN-5361] modify email notification function and adjust  email hard code to config file
---
 .../setting/SettingAdvanced/SettingAdvanced.vue    |  40 ++-
 .../components/setting/SettingAdvanced/handler.js  |  11 +-
 .../components/setting/SettingAdvanced/locales.js  |   6 +-
 pom.xml                                            |   5 +
 .../rest/request/JobNotificationConfigRequest.java |   6 +-
 .../kylin/rest/response/ProjectConfigResponse.java |   8 +-
 .../apache/kylin/rest/service/ProjectService.java  |  18 +-
 src/core-common/pom.xml                            |   5 +-
 .../org/apache/kylin/common/KylinConfigBase.java   |  15 +-
 .../common/util/BasicEmailNotificationContent.java |  62 ----
 .../org/apache/kylin/common/util/MailHelper.java   |  65 ++--
 .../kylin/common/util/MailTemplateProvider.java    |  93 +++++
 .../org/apache/kylin/common/util/StringUtil.java   |   4 +
 .../apache/kylin/common/KylinConfigBaseTest.java   |   6 +-
 .../apache/kylin/common/util/MailServiceTest.java  |   2 +-
 .../apache/kylin/job/constant/JobIssueEnum.java    |   4 +-
 .../kylin/job/execution/AbstractExecutable.java    | 128 ++++++-
 .../kylin/job/execution/DefaultExecutable.java     |  15 +-
 .../job/execution/EmailNotificationContent.java    | 195 ++++++++---
 .../kylin/job/execution/ExecutableState.java       |  15 +
 .../kylin/job/execution/NExecutableManager.java    |   4 +-
 .../kylin/job/util/MailNotificationUtil.java       | 109 ++++++
 .../main/resources/mail_templates/JOB_DISCARD.ftl  | 274 +++++++++++++++
 .../main/resources/mail_templates/JOB_ERROR.ftl    | 390 +++++++++++++++++++++
 .../main/resources/mail_templates/JOB_SUCCEED.ftl  | 273 +++++++++++++++
 .../resources/mail_templates/LOAD_EMPTY_DATA.ftl   | 200 +++++++++++
 .../mail_templates/METADATA_PERSIST_FAIL.ftl       | 232 ++++++++++++
 .../mail_templates/OVER_CAPACITY_THRESHOLD.ftl     | 200 +++++++++++
 .../mail_templates/SOURCE_RECORDS_CHANGE.ftl       | 205 +++++++++++
 .../kylin/job/execution/ErrorTestExecutable.java   |   3 +-
 .../job/execution/NExecutableManagerTest.java      |  37 +-
 .../kylin/job/execution/SucceedTestExecutable.java |   5 +-
 .../job/impl/threadpool/NDefaultSchedulerTest.java |   3 +-
 .../kylin/metadata/project/ProjectInstance.java    |  10 +
 .../metadata/sourceusage/SourceUsageManager.java   |   6 +-
 .../test_case_data/localmeta/kylin.properties      |  23 +-
 .../apache/kylin/rest/service/JobServiceTest.java  |   7 +-
 .../kylin/rest/controller/NProjectController.java  |   9 +-
 .../rest/controller/NProjectControllerTest.java    |   2 +-
 .../kylin/rest/service/ProjectServiceTest.java     |  12 +-
 40 files changed, 2490 insertions(+), 217 deletions(-)

diff --git a/kystudio/src/components/setting/SettingAdvanced/SettingAdvanced.vue b/kystudio/src/components/setting/SettingAdvanced/SettingAdvanced.vue
index 031a03f2de..0ea34355a8 100644
--- a/kystudio/src/components/setting/SettingAdvanced/SettingAdvanced.vue
+++ b/kystudio/src/components/setting/SettingAdvanced/SettingAdvanced.vue
@@ -46,13 +46,28 @@
         </span>
         <div class="setting-desc">{{$t('emptyDataLoadDesc')}}</div>
         <div class="split"></div>
-        <span class="setting-label font-medium">{{$t('errorJob')}}</span><span class="setting-value fixed">
+        <span class="setting-label font-medium">{{$t('metaDataPersist')}}</span><span class="setting-value fixed">
           <el-switch
-            v-model="form.job_error_notification_enabled"
+            v-model="form.metadata_persist_notification_enabled"
             :active-text="$t('kylinLang.common.OFF')"
             :inactive-text="$t('kylinLang.common.ON')">
           </el-switch>
         </span>
+        <div class="setting-desc">{{$t('metaDataPersistDesc')}}</div>
+        <div class="split"></div>
+        <span class="setting-label font-medium">{{$t('jobState')}} :</span><span class="setting-value fixed">
+        </span>
+         <span class="setting-value">
+              {{form.job_notification_states.map(states => $t(states)).join(', ')}}
+            </span>
+            <el-checkbox-group class="setting-input" :value="form.job_notification_states" @input="handleInputJobState">
+              <el-checkbox
+                v-for="jobState in jobNotificationStateTypes"
+                :key="jobState"
+                :label="jobState">
+                {{$t(jobState)}}
+              </el-checkbox>
+            </el-checkbox-group>
         <div class="setting-desc">{{$t('errorJobDesc')}}</div>
       </div>
       <div class="setting-item">
@@ -308,7 +323,17 @@ import { Component, Watch } from 'vue-property-decorator'
 import { handleError, handleSuccessAsync, objectArraySort } from '../../../util'
 import { kylinConfirm } from 'util/business'
 import { apiUrl } from '../../../config'
-import { validate, _getJobAlertSettings, _getDefaultDBSettings, _getYarnNameSetting, _getSecStorageSetting, _getExposeCCSetting, _getSnapshotSetting, _getKerberosSettings } from './handler'
+import {
+  validate,
+  _getJobAlertSettings,
+  _getDefaultDBSettings,
+  _getYarnNameSetting,
+  _getSecStorageSetting,
+  _getExposeCCSetting,
+  _getSnapshotSetting,
+  _getKerberosSettings,
+  jobNotificationStateTypes
+} from './handler'
 import EditableBlock from '../../common/EditableBlock/EditableBlock.vue'
 import { pageRefTags, pageCount } from 'config'
 
@@ -377,6 +402,7 @@ export default class SettingAdvanced extends Vue {
   pageSize = pageCount
   convertedProperties = []
   pageRefTags = pageRefTags
+  jobNotificationStateTypes=jobNotificationStateTypes
 
   dbList = []
   nodes = []
@@ -385,8 +411,9 @@ export default class SettingAdvanced extends Vue {
     project: '',
     // tips_enabled: true,
     // threshold: 20,
-    job_error_notification_enabled: true,
+    job_notification_states: [],
     data_load_empty_notification_enabled: true,
+    metadata_persist_notification_enabled: false,
     job_notification_emails: [],
     default_database: this.$store.state.project.projectDefaultDB || '',
     yarn_queue: this.$store.state.project.yarn_queue || '',
@@ -1129,6 +1156,11 @@ export default class SettingAdvanced extends Vue {
     this.pageSize = pageSize
     this.convertedProperties = this.configList.slice(this.pageSize * currentPage, this.pageSize * (currentPage + 1))
   }
+  handleInputJobState (value) {
+    if (value.length >= 0) {
+      this.form.job_notification_states = value
+    }
+  }
 }
 </script>
 
diff --git a/kystudio/src/components/setting/SettingAdvanced/handler.js b/kystudio/src/components/setting/SettingAdvanced/handler.js
index 61ee70b374..3ab1a8c307 100644
--- a/kystudio/src/components/setting/SettingAdvanced/handler.js
+++ b/kystudio/src/components/setting/SettingAdvanced/handler.js
@@ -36,9 +36,10 @@ export function _getJobAlertSettings (data, isArrayDefaultValue, isSort) {
 
   return {
     project: data.project,
-    job_error_notification_enabled: data.job_error_notification_enabled,
+    metadata_persist_notification_enabled: data.metadata_persist_notification_enabled,
     data_load_empty_notification_enabled: data.data_load_empty_notification_enabled,
-    job_notification_emails: jobEmails
+    job_notification_emails: jobEmails,
+    job_notification_states: data.job_notification_states
   }
 }
 
@@ -84,3 +85,9 @@ export function _getKerberosSettings (data) {
     principal: data.principal
   }
 }
+
+export const jobNotificationStateTypes = [
+  'Succeed',
+  'Error',
+  'Discard'
+]
diff --git a/kystudio/src/components/setting/SettingAdvanced/locales.js b/kystudio/src/components/setting/SettingAdvanced/locales.js
index 31e384bf1a..0afc2e478e 100644
--- a/kystudio/src/components/setting/SettingAdvanced/locales.js
+++ b/kystudio/src/components/setting/SettingAdvanced/locales.js
@@ -7,8 +7,10 @@ export default {
     jobAlert: 'Email Notification',
     emptyDataLoad: 'Empty Data Job',
     emptyDataLoadDesc: 'Email to the following email address(es) if there is a job loading empty data.',
-    errorJob: 'Error Job',
-    errorJobDesc: 'Email to the following email address(es) if an error occured for the job.',
+    metaDataPersist: 'MetaData Persist To HDFS',
+    metaDataPersistDesc: 'Email to the following email address(es) if metadata persist to HDFS error.',
+    jobState: 'Job State',
+    errorJobDesc: 'Email to the following email address(es) when the issue occurred of the job with the according chosen state(s).',
     emails: 'Email Address:',
     noData: 'No Data',
     pleaseInputEmail: 'Please enter email',
diff --git a/pom.xml b/pom.xml
index d9401bc208..dfb1f7c464 100644
--- a/pom.xml
+++ b/pom.xml
@@ -525,6 +525,11 @@
                 <artifactId>kylin-integration-service</artifactId>
                 <version>${project.version}</version>
             </dependency>
+            <dependency>
+                <groupId>org.freemarker</groupId>
+                <artifactId>freemarker</artifactId>
+                <version>2.3.31</version>
+            </dependency>
 
             <!-- arthas -->
             <dependency>
diff --git a/src/common-service/src/main/java/org/apache/kylin/rest/request/JobNotificationConfigRequest.java b/src/common-service/src/main/java/org/apache/kylin/rest/request/JobNotificationConfigRequest.java
index 7d2ef5875c..89885aaf12 100644
--- a/src/common-service/src/main/java/org/apache/kylin/rest/request/JobNotificationConfigRequest.java
+++ b/src/common-service/src/main/java/org/apache/kylin/rest/request/JobNotificationConfigRequest.java
@@ -26,11 +26,13 @@ import lombok.Data;
 
 @Data
 public class JobNotificationConfigRequest {
-    @JsonProperty("job_error_notification_enabled")
-    private Boolean jobErrorNotificationEnabled;
     @JsonProperty("data_load_empty_notification_enabled")
     private Boolean dataLoadEmptyNotificationEnabled;
+    @JsonProperty("job_notification_states")
+    private List<String> jobNotificationStates;
     @JsonProperty("job_notification_emails")
     private List<String> jobNotificationEmails;
+    @JsonProperty("metadata_persist_notification_enabled")
+    private Boolean metadataPersistNotificationEnabled;
 
 }
diff --git a/src/common-service/src/main/java/org/apache/kylin/rest/response/ProjectConfigResponse.java b/src/common-service/src/main/java/org/apache/kylin/rest/response/ProjectConfigResponse.java
index c2c724f490..784731ec9b 100644
--- a/src/common-service/src/main/java/org/apache/kylin/rest/response/ProjectConfigResponse.java
+++ b/src/common-service/src/main/java/org/apache/kylin/rest/response/ProjectConfigResponse.java
@@ -88,8 +88,12 @@ public class ProjectConfigResponse {
     @JsonProperty("retention_range")
     private RetentionRange retentionRange;
 
-    @JsonProperty("job_error_notification_enabled")
-    private boolean jobErrorNotificationEnabled;
+    @JsonProperty("job_notification_states")
+    private List<String> jobNotificationStates;
+
+    @JsonProperty("metadata_persist_notification_enabled")
+    private boolean metadataPersistNotificationEnabled;
+
     @JsonProperty("data_load_empty_notification_enabled")
     private boolean dataLoadEmptyNotificationEnabled;
     @JsonProperty("job_notification_emails")
diff --git a/src/common-service/src/main/java/org/apache/kylin/rest/service/ProjectService.java b/src/common-service/src/main/java/org/apache/kylin/rest/service/ProjectService.java
index 10c5b19f6f..d57e80bbe8 100644
--- a/src/common-service/src/main/java/org/apache/kylin/rest/service/ProjectService.java
+++ b/src/common-service/src/main/java/org/apache/kylin/rest/service/ProjectService.java
@@ -451,10 +451,12 @@ public class ProjectService extends BasicService {
         Map<String, String> overrideKylinProps = Maps.newHashMap();
         overrideKylinProps.put("kylin.job.notification-on-empty-data-load",
                 String.valueOf(jobNotificationConfigRequest.getDataLoadEmptyNotificationEnabled()));
-        overrideKylinProps.put("kylin.job.notification-on-job-error",
-                String.valueOf(jobNotificationConfigRequest.getJobErrorNotificationEnabled()));
-        overrideKylinProps.put("kylin.job.notification-admin-emails",
+        overrideKylinProps.put("kylin.job.notification-enable-states",
+                String.join(",", Sets.newHashSet(jobNotificationConfigRequest.getJobNotificationStates())));
+        overrideKylinProps.put("kylin.job.notification-user-emails",
                 convertToString(jobNotificationConfigRequest.getJobNotificationEmails()));
+        overrideKylinProps.put("kylin.job.notification-on-metadata-persist",
+                String.valueOf(jobNotificationConfigRequest.getMetadataPersistNotificationEnabled()));
         updateProjectOverrideKylinProps(project, overrideKylinProps);
     }
 
@@ -571,8 +573,9 @@ public class ProjectService extends BasicService {
         response.setFavoriteQueryTipsEnabled(config.getFavoriteQueryAccelerateTipsEnabled());
 
         response.setDataLoadEmptyNotificationEnabled(config.getJobDataLoadEmptyNotificationEnabled());
-        response.setJobErrorNotificationEnabled(config.getJobErrorNotificationEnabled());
-        response.setJobNotificationEmails(Lists.newArrayList(config.getAdminDls()));
+        response.setJobNotificationEmails(projectInstance.getEmailUsers());
+        response.setJobNotificationStates(Lists.newArrayList(config.getJobNotificationStates()));
+        response.setMetadataPersistNotificationEnabled(config.getJobMetadataPersistNotificationEnabled());
 
         response.setFrequencyTimeWindow(config.getFrequencyTimeWindowInDays());
 
@@ -1045,8 +1048,9 @@ public class ProjectService extends BasicService {
     private void resetJobNotificationConfig(String project) {
         Set<String> toBeRemovedProps = Sets.newHashSet();
         toBeRemovedProps.add("kylin.job.notification-on-empty-data-load");
-        toBeRemovedProps.add("kylin.job.notification-on-job-error");
-        toBeRemovedProps.add("kylin.job.notification-admin-emails");
+        toBeRemovedProps.add("kylin.job.notification-enable-states");
+        toBeRemovedProps.add("kylin.job.notification-user-emails");
+        toBeRemovedProps.add("kylin.job.notification-on-metadata-persist");
         removeProjectOveridedProps(project, toBeRemovedProps);
     }
 
diff --git a/src/core-common/pom.xml b/src/core-common/pom.xml
index 6c1211efe8..d9877d8606 100644
--- a/src/core-common/pom.xml
+++ b/src/core-common/pom.xml
@@ -71,7 +71,10 @@
             <groupId>org.apache.tomcat.embed</groupId>
             <artifactId>tomcat-embed-core</artifactId>
         </dependency>
-
+        <dependency>
+            <groupId>org.freemarker</groupId>
+            <artifactId>freemarker</artifactId>
+        </dependency>
         <dependency>
             <groupId>com.google.guava</groupId>
             <artifactId>guava</artifactId>
diff --git a/src/core-common/src/main/java/org/apache/kylin/common/KylinConfigBase.java b/src/core-common/src/main/java/org/apache/kylin/common/KylinConfigBase.java
index 64606df279..bc1072ef28 100644
--- a/src/core-common/src/main/java/org/apache/kylin/common/KylinConfigBase.java
+++ b/src/core-common/src/main/java/org/apache/kylin/common/KylinConfigBase.java
@@ -1146,6 +1146,17 @@ public abstract class KylinConfigBase implements Serializable {
         return getOptionalStringArray("kylin.job.notification-admin-emails", new String[0]);
     }
 
+    public String[] getJobNotificationStates() {
+        return getOptionalStringArray("kylin.job.notification-enable-states", new String[0]);
+    }
+
+    public int getJobMetadataPersistRetry() {
+        return Integer.parseInt(this.getOptional("kylin.job.metadata-persist-retry", "5"));
+    }
+
+    public Boolean getJobMetadataPersistNotificationEnabled() {
+        return Boolean.parseBoolean(this.getOptional("kylin.job.notification-on-metadata-persist", FALSE));
+    }
     public int getJobRetry() {
         return Integer.parseInt(getOptional("kylin.job.retry", "0"));
     }
@@ -2482,10 +2493,6 @@ public abstract class KylinConfigBase implements Serializable {
         return Boolean.parseBoolean(getOptional("kylin.job.notification-on-empty-data-load", FALSE));
     }
 
-    public boolean getJobErrorNotificationEnabled() {
-        return Boolean.parseBoolean(getOptional("kylin.job.notification-on-job-error", FALSE));
-    }
-
     public Long getStorageResourceSurvivalTimeThreshold() {
         return TimeUtil.timeStringAs(this.getOptional("kylin.storage.resource-survival-time-threshold", "7d"),
                 TimeUnit.MILLISECONDS);
diff --git a/src/core-common/src/main/java/org/apache/kylin/common/util/BasicEmailNotificationContent.java b/src/core-common/src/main/java/org/apache/kylin/common/util/BasicEmailNotificationContent.java
deleted file mode 100644
index e164197324..0000000000
--- a/src/core-common/src/main/java/org/apache/kylin/common/util/BasicEmailNotificationContent.java
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * 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.kylin.common.util;
-
-import lombok.Getter;
-import lombok.Setter;
-
-@Getter
-@Setter
-public class BasicEmailNotificationContent {
-
-    public static final String NOTIFY_EMAIL_TITLE_TEMPLATE = "[Apache Kylin System Notification] ${issue}";
-    public static final String NOTIFY_EMAIL_BODY_TEMPLATE = "<div style='display:block;word-wrap:break-word;width:80%;font-size:16px;font-family:Microsoft YaHei;'><b>Dear Apache Kylin User,</b><pre><p>"
-            + "<p>${conclusion}</p>" + "<p>Issue: ${issue}<br>" + "Type: ${type}<br>" + "Time: ${time}<br>"
-            + "Project: ${project}<br>" + "Solution: ${solution}</p>" + "<p>Yours sincerely,<br>" + "Apache Kylin Community</p>"
-            + "</pre><div/>";
-
-    public static final String CONCLUSION_FOR_JOB_ERROR = "We found an error job happened in your Apache Kylin system as below. It won't affect your system stability and you may repair it by following instructions.";
-    public static final String CONCLUSION_FOR_LOAD_EMPTY_DATA = "We found a job has loaded empty data in your Apache Kylin system as below. It won't affect your system stability and you may reload data by following instructions.";
-    public static final String CONCLUSION_FOR_SOURCE_RECORDS_CHANGE = "We found some source records updated in your Apache Kylin system. You can reload updated records by following instructions. Ignore this issue may cause query result inconsistency over different indexes.";
-    public static final String CONCLUSION_FOR_OVER_CAPACITY_THRESHOLD = "The amount of data volume used (${volume_used}/${volume_total}) has reached ${capacity_threshold}% of the license’s limit.";
-
-    public static final String SOLUTION_FOR_JOB_ERROR = "You may resume the job first. If still won't work, please send the job's diagnostic package to kyligence technical support.";
-    public static final String SOLUTION_FOR_LOAD_EMPTY_DATA = "You may refresh the empty segment of the model ${model_name} to reload data.";
-    public static final String SOLUTION_FOR_SOURCE_RECORDS_CHANGE = "You may refresh the segment from ${start_time} to ${end_time} to apply source records change.";
-    public static final String SOLUTION_FOR_OVER_CAPACITY_THRESHOLD = "To ensure the availability of your service, please contact Kyligence to get a new license, or try deleting some segments.";
-
-    private String conclusion;
-    private String issue;
-    private String time;
-    private String solution;
-
-    private String jobType;
-    private String project;
-
-    public String getEmailTitle() {
-        return NOTIFY_EMAIL_TITLE_TEMPLATE.replaceAll("\\$\\{issue\\}", issue);
-    }
-
-    public String getEmailBody() {
-        return NOTIFY_EMAIL_BODY_TEMPLATE.replaceAll("\\$\\{conclusion\\}", conclusion)
-                .replaceAll("\\$\\{issue\\}", issue).replaceAll("\\$\\{type\\}", jobType)
-                .replaceAll("\\$\\{time\\}", time).replaceAll("\\$\\{project\\}", project)
-                .replaceAll("\\$\\{solution\\}", solution);
-    }
-}
diff --git a/src/core-common/src/main/java/org/apache/kylin/common/util/MailHelper.java b/src/core-common/src/main/java/org/apache/kylin/common/util/MailHelper.java
index 29875bdb12..2caa3c6e90 100644
--- a/src/core-common/src/main/java/org/apache/kylin/common/util/MailHelper.java
+++ b/src/core-common/src/main/java/org/apache/kylin/common/util/MailHelper.java
@@ -18,20 +18,22 @@
 
 package org.apache.kylin.common.util;
 
-import java.math.BigDecimal;
-import java.time.Clock;
-import java.time.LocalDate;
-import java.util.Collections;
-import java.util.List;
-
+import com.google.common.base.Joiner;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
 import org.apache.kylin.common.KylinConfig;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import com.google.common.collect.Lists;
+import java.math.BigDecimal;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
 
 public class MailHelper {
 
+    public static final String OVER_CAPACITY_THRESHOLD = "OVER_CAPACITY_THRESHOLD";
+    public static final String CAPACITY = "CAPACITY";
     protected static final Logger logger = LoggerFactory.getLogger(MailHelper.class);
 
     public static List<String> getOverCapacityMailingUsers(KylinConfig kylinConfig) {
@@ -47,24 +49,13 @@ public class MailHelper {
         return users;
     }
 
-    public static Pair<String, String> formatNotifications(BasicEmailNotificationContent content) {
-        if (content == null) {
-            return null;
-        }
-        String title = content.getEmailTitle();
-        String body = content.getEmailBody();
-        return Pair.newPair(title, body);
-    }
-
-    public static boolean notifyUser(KylinConfig kylinConfig, BasicEmailNotificationContent content,
-            List<String> users) {
+    public static boolean notifyUser(KylinConfig kylinConfig, Pair<String, String> mail, List<String> users) {
         try {
             if (users.isEmpty()) {
                 logger.debug("no need to send email, user list is empty.");
                 return false;
             }
-            final Pair<String, String> email = MailHelper.formatNotifications(content);
-            return doSendMail(kylinConfig, users, email);
+            return doSendMail(kylinConfig, users, mail);
         } catch (Exception e) {
             logger.error("error send email", e);
             return false;
@@ -81,27 +72,29 @@ public class MailHelper {
         return new MailService(kylinConfig).sendMail(users, email.getFirst(), email.getSecond());
     }
 
-    public static BasicEmailNotificationContent creatContentForCapacityUsage(Long licenseVolume, Long currentCapacity) {
-        BasicEmailNotificationContent content = new BasicEmailNotificationContent();
-        content.setIssue("Over capacity threshold");
-        content.setTime(LocalDate.now(Clock.systemDefaultZone()).toString());
-        content.setJobType("CHECK_USAGE");
-        content.setProject("NULL");
+    public static Pair<String, String> creatContentForCapacityUsage(Long licenseVolume, Long currentCapacity, String resourceName) {
 
         String readableCurrentCapacity = SizeConvertUtil.getReadableFileSize(currentCapacity);
         String readableLicenseVolume = SizeConvertUtil.getReadableFileSize(licenseVolume);
         double overCapacityThreshold = KylinConfig.getInstanceFromEnv().getOverCapacityThreshold() * 100;
-        content.setConclusion(BasicEmailNotificationContent.CONCLUSION_FOR_OVER_CAPACITY_THRESHOLD
-                .replaceAll("\\$\\{volume_used\\}", readableCurrentCapacity)
-                .replaceAll("\\$\\{volume_total\\}", readableLicenseVolume)
-                .replaceAll("\\$\\{capacity_threshold\\}", BigDecimal.valueOf(overCapacityThreshold).toString()));
-        content.setSolution(BasicEmailNotificationContent.SOLUTION_FOR_OVER_CAPACITY_THRESHOLD);
-        return content;
+        KylinConfig env = KylinConfig.getInstanceFromEnv();
+
+        Map<String, Object> dataMap = Maps.newHashMap();
+        dataMap.put("resource_name", resourceName);
+        dataMap.put("volume_used", readableCurrentCapacity);
+        dataMap.put("volume_total", readableLicenseVolume);
+        dataMap.put("capacity_threshold", BigDecimal.valueOf(overCapacityThreshold).toString());
+        dataMap.put("env_name", KylinConfig.getInstanceFromEnv().getDeployEnv());
+        String title = getMailTitle(CAPACITY,
+                OVER_CAPACITY_THRESHOLD,
+                env.getMetadataUrlPrefix(),
+                env.getDeployEnv());
+        String content = MailTemplateProvider.getInstance().buildMailContent(OVER_CAPACITY_THRESHOLD, dataMap);
+        return Pair.newPair(title, content);
     }
 
-    public static boolean notifyUserForOverCapacity(Long licenseVolume, Long currentCapacity) {
-        KylinConfig kylinConfig = KylinConfig.getInstanceFromEnv();
-        List<String> users = getOverCapacityMailingUsers(kylinConfig);
-        return notifyUser(kylinConfig, creatContentForCapacityUsage(licenseVolume, currentCapacity), users);
+
+    public static String getMailTitle(String... titleParts) {
+        return "[" + Joiner.on("]-[").join(titleParts) + "]";
     }
 }
diff --git a/src/core-common/src/main/java/org/apache/kylin/common/util/MailTemplateProvider.java b/src/core-common/src/main/java/org/apache/kylin/common/util/MailTemplateProvider.java
new file mode 100644
index 0000000000..562ae3becc
--- /dev/null
+++ b/src/core-common/src/main/java/org/apache/kylin/common/util/MailTemplateProvider.java
@@ -0,0 +1,93 @@
+/*
+ * 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.kylin.common.util;
+
+import freemarker.template.Configuration;
+import freemarker.template.Template;
+import org.apache.commons.lang.StringUtils;
+import com.google.common.base.Joiner;
+import org.apache.kylin.common.KylinConfig;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.StringWriter;
+import java.io.Writer;
+import java.util.List;
+import java.util.Map;
+
+import static org.apache.kylin.common.util.MailHelper.creatContentForCapacityUsage;
+import static org.apache.kylin.common.util.MailHelper.getOverCapacityMailingUsers;
+import static org.apache.kylin.common.util.MailHelper.notifyUser;
+
+/**
+ * Use a key to find a template for email.
+ *
+ * The template file is [KEY].ftl file under /mail_templates directory with classloader.
+ */
+public class MailTemplateProvider {
+
+    private static final Logger logger = LoggerFactory.getLogger(MailTemplateProvider.class);
+
+    private static MailTemplateProvider DEFAULT_INSTANCE = new MailTemplateProvider();
+
+    public static MailTemplateProvider getInstance() {
+        return DEFAULT_INSTANCE;
+    }
+
+    public static String getMailTitle(String... titleParts) {
+        return "[" + Joiner.on("]-[").join(titleParts) + "]";
+    }
+
+    private final Configuration configuration;
+
+    private MailTemplateProvider() {
+        configuration = new Configuration(Configuration.getVersion());
+        configuration.setClassForTemplateLoading(MailTemplateProvider.class, "/mail_templates");
+        configuration.setDefaultEncoding("UTF-8");
+    }
+
+    public String buildMailContent(String tplKey, Map<String, Object> data) {
+        try {
+            Template template = getTemplate(tplKey);
+            if (template == null) {
+                return "Cannot find email template for " + tplKey;
+            }
+
+            try (Writer out = new StringWriter()) {
+                template.process(data, out);
+                return out.toString();
+            }
+        } catch (Throwable e) {
+            return e.getLocalizedMessage();
+        }
+    }
+
+    private Template getTemplate(String tplKey) throws Throwable {
+        if (StringUtils.isEmpty(tplKey)) {
+            return null;
+        }
+        return configuration.getTemplate(tplKey + ".ftl");
+    }
+
+    public static boolean notifyUserForOverCapacity(Long licenseVolume, Long currentCapacity, String resourceName) {
+        KylinConfig kylinConfig = KylinConfig.getInstanceFromEnv();
+        List<String> users = getOverCapacityMailingUsers(kylinConfig);
+        return notifyUser(kylinConfig, creatContentForCapacityUsage(licenseVolume, currentCapacity, resourceName), users);
+    }
+}
diff --git a/src/core-common/src/main/java/org/apache/kylin/common/util/StringUtil.java b/src/core-common/src/main/java/org/apache/kylin/common/util/StringUtil.java
index 32af714687..5776a5895b 100644
--- a/src/core-common/src/main/java/org/apache/kylin/common/util/StringUtil.java
+++ b/src/core-common/src/main/java/org/apache/kylin/common/util/StringUtil.java
@@ -209,4 +209,8 @@ public class StringUtil {
         return "true".equals(s) || "false".equals(s);
     }
 
+    public static String[] split(String str, String splitBy) {
+        return str.split(splitBy);
+    }
+
 }
diff --git a/src/core-common/src/test/java/org/apache/kylin/common/KylinConfigBaseTest.java b/src/core-common/src/test/java/org/apache/kylin/common/KylinConfigBaseTest.java
index 16f653d46b..c4ebf9d02f 100644
--- a/src/core-common/src/test/java/org/apache/kylin/common/KylinConfigBaseTest.java
+++ b/src/core-common/src/test/java/org/apache/kylin/common/KylinConfigBaseTest.java
@@ -627,9 +627,11 @@ class KylinConfigBaseTest {
 
         map.put("getJobDataLoadEmptyNotificationEnabled",
                 new PropertiesEntity("kylin.job.notification-on-empty-data-load", "false", false));
+        map.put("getJobNotificationStates",
+                new PropertiesEntity("kylin.job.notification-enable-states", "", new String[0]));
 
-        map.put("getJobErrorNotificationEnabled",
-                new PropertiesEntity("kylin.job.notification-on-job-error", "false", false));
+        map.put("getMetaDataPersistNotificationEnabled",
+                new PropertiesEntity("kylin.job.notification-on-metadata-persist", "false", false));
 
         map.put("getStorageResourceSurvivalTimeThreshold",
                 new PropertiesEntity("kylin.storage.resource-survival-time-threshold", "7d", 7L * 24 * 60 * 60 * 1000));
diff --git a/src/core-common/src/test/java/org/apache/kylin/common/util/MailServiceTest.java b/src/core-common/src/test/java/org/apache/kylin/common/util/MailServiceTest.java
index b9767afc59..8a1ce6e54f 100644
--- a/src/core-common/src/test/java/org/apache/kylin/common/util/MailServiceTest.java
+++ b/src/core-common/src/test/java/org/apache/kylin/common/util/MailServiceTest.java
@@ -65,7 +65,7 @@ public class MailServiceTest extends AbstractTestCase {
     public void testMailHelper() {
         overwriteSystemProp("kylin.capacity.notification-enabled", "true");
         overwriteSystemProp("kylin.capacity.notification-emails", "foobar@foobar.com");
-        boolean sent = MailHelper.notifyUserForOverCapacity(100L, 81L);
+        boolean sent = MailTemplateProvider.notifyUserForOverCapacity(100L, 81L, "abc");
         assert sent;
     }
 
diff --git a/src/core-job/src/main/java/org/apache/kylin/job/constant/JobIssueEnum.java b/src/core-job/src/main/java/org/apache/kylin/job/constant/JobIssueEnum.java
index bc848a436b..60ee3afeb0 100644
--- a/src/core-job/src/main/java/org/apache/kylin/job/constant/JobIssueEnum.java
+++ b/src/core-job/src/main/java/org/apache/kylin/job/constant/JobIssueEnum.java
@@ -21,8 +21,8 @@ package org.apache.kylin.job.constant;
 import lombok.Getter;
 
 public enum JobIssueEnum {
-    JOB_ERROR("Error Job"), LOAD_EMPTY_DATA("Load Empty Data"), SOURCE_RECORDS_CHANGE(
-            "Source Records Change"), OVER_CAPACITY_THRESHOLD("Over capacity threshold");
+    LOAD_EMPTY_DATA("Load Empty Data"), SOURCE_RECORDS_CHANGE(
+            "Source Records Change");
 
     @Getter
     private final String displayName;
diff --git a/src/core-job/src/main/java/org/apache/kylin/job/execution/AbstractExecutable.java b/src/core-job/src/main/java/org/apache/kylin/job/execution/AbstractExecutable.java
index 767b1ba183..d9eff3215c 100644
--- a/src/core-job/src/main/java/org/apache/kylin/job/execution/AbstractExecutable.java
+++ b/src/core-job/src/main/java/org/apache/kylin/job/execution/AbstractExecutable.java
@@ -59,6 +59,7 @@ import org.apache.commons.lang.StringUtils;
 import org.apache.commons.lang3.ArrayUtils;
 import org.apache.commons.lang3.exception.ExceptionUtils;
 import org.apache.kylin.common.KylinConfig;
+import org.apache.kylin.common.constant.NonCustomProjectLevelConfig;
 import org.apache.kylin.common.metrics.MetricsCategory;
 import org.apache.kylin.common.metrics.MetricsGroup;
 import org.apache.kylin.common.metrics.MetricsName;
@@ -73,6 +74,7 @@ import org.apache.kylin.job.dao.ExecutablePO;
 import org.apache.kylin.job.exception.ExecuteException;
 import org.apache.kylin.job.exception.JobStoppedException;
 import org.apache.kylin.job.exception.JobStoppedNonVoluntarilyException;
+import org.apache.kylin.job.exception.PersistentException;
 import org.apache.kylin.metadata.cube.model.NBatchConstants;
 import org.apache.kylin.metadata.cube.model.NDataLayout;
 import org.apache.kylin.metadata.model.NDataModel;
@@ -319,17 +321,17 @@ public abstract class AbstractExecutable implements Executable {
     }
 
     public void updateJobOutput(String project, String jobId, ExecutableState newStatus, Map<String, String> info,
-            String output, Consumer<String> hook) {
+            String output, Consumer<String> hook) throws ExecuteException, PersistentException {
         updateJobOutput(project, jobId, newStatus, info, output, null, hook);
     }
 
     public void updateJobOutput(String project, String jobId, ExecutableState newStatus, Map<String, String> info,
-            String output, String failedMsg, Consumer<String> hook) {
+            String output, String failedMsg, Consumer<String> hook) throws ExecuteException, PersistentException {
         updateJobOutput(project, jobId, newStatus, info, output, this.getLogPath(), failedMsg, hook);
     }
 
     public void updateJobOutput(String project, String jobId, ExecutableState newStatus, Map<String, String> info,
-            String output, String logPath, String failedMsg, Consumer<String> hook) {
+            String output, String logPath, String failedMsg, Consumer<String> hook) throws ExecuteException, PersistentException {
         EnhancedUnitOfWork.doInTransactionWithCheckAndRetry(() -> {
             NExecutableManager executableManager = getExecutableManager(project);
             val existedInfo = executableManager.getOutput(jobId).getExtra();
@@ -353,10 +355,11 @@ public abstract class AbstractExecutable implements Executable {
         }, project, UnitOfWork.DEFAULT_MAX_RETRY, getEpochId(), getTempLockName());
 
         //write output to HDFS
-        updateJobOutputToHDFS(project, jobId, output, logPath);
+        updateJobOutputWithPersistCheck(project, jobId, output, logPath);
     }
 
-    private static void updateJobOutputToHDFS(String project, String jobId, String output, String logPath) {
+    private static void updateJobOutputToHDFS(String project, String jobId, String output, String logPath)
+            throws PersistentException {
         NExecutableManager nExecutableManager = getExecutableManager(project);
         ExecutableOutputPO jobOutput = nExecutableManager.getJobOutput(jobId);
         if (null != output) {
@@ -544,7 +547,7 @@ public abstract class AbstractExecutable implements Executable {
     }
 
     // Ensure metadata compatibility
-    public abstract ExecuteResult doWork(ExecutableContext context) throws ExecuteException;
+    protected abstract ExecuteResult doWork(ExecutableContext context) throws ExecuteException, PersistentException;
 
     @Override
     public boolean isRunnable() {
@@ -595,13 +598,12 @@ public abstract class AbstractExecutable implements Executable {
         val projectConfig = NProjectManager.getInstance(getConfig()).getProject(project).getConfig();
         boolean needNotification = true;
         switch (jobIssue) {
-        case JOB_ERROR:
-            needNotification = projectConfig.getJobErrorNotificationEnabled();
-            break;
         case LOAD_EMPTY_DATA:
             needNotification = projectConfig.getJobDataLoadEmptyNotificationEnabled();
+            String state = checkStateIfOverride(NonCustomProjectLevelConfig.JOB_DATA_LOAD_EMPTY_NOTIFICATION_ENABLED.getValue());
+            needNotification = state == null ? needNotification : Boolean.parseBoolean(state);
             break;
-        case SOURCE_RECORDS_CHANGE:
+        case SOURCE_RECORDS_CHANGE: //todo source record change
             needNotification = projectConfig.getJobSourceRecordsChangeNotificationEnabled();
             break;
         default:
@@ -611,7 +613,7 @@ public abstract class AbstractExecutable implements Executable {
             return;
         }
         List<String> users;
-        users = getAllNotifyUsers(projectConfig);
+        users = getOverrideNotifyUsers();
         if (this instanceof DefaultExecutable) {
             MailHelper.notifyUser(projectConfig, EmailNotificationContent.createContent(jobIssue, this), users);
         } else {
@@ -620,6 +622,21 @@ public abstract class AbstractExecutable implements Executable {
         }
     }
 
+    public final void notifyUserStatusChange(ExecutableState state) {
+        Preconditions.checkState(
+                (this instanceof DefaultExecutable) || this.getParent() instanceof DefaultExecutable);
+        val projectConfig = NProjectManager.getInstance(getConfig()).getProject(project).getConfig();
+        List<String> users = getOverrideNotifyUsers();
+        if (this instanceof DefaultExecutable) {
+            MailHelper.notifyUser(projectConfig, EmailNotificationContent.createContent(state,
+                    this, ((DefaultExecutable) this).getTasks()), users);
+        } else {
+            MailHelper.notifyUser(projectConfig, EmailNotificationContent.createContent(state,
+                            this.getParent(), ((DefaultExecutable) this.getParent()).getTasks()), users);
+        }
+    }
+
+
     public void setSparkYarnQueueIfEnabled(String project, String yarnQueue) {
         ProjectInstance proj = NProjectManager.getInstance(KylinConfig.getInstanceFromEnv()).getProject(project);
         KylinConfig config = proj.getConfig();
@@ -939,4 +956,93 @@ public abstract class AbstractExecutable implements Executable {
         }
         return getParentId();
     }
+
+    private void updateJobOutputWithPersistCheck(String project, String jobId, String output, String logPath)
+            throws ExecuteException, PersistentException {
+        Throwable exception;
+        int retry = 0;
+        do {
+            exception = null;
+            retry++;
+            try {
+                updateJobOutputToHDFS(project, jobId, output, logPath);
+            } catch (Exception e) {
+                logger.error("update Job Output failed due to {}", e);
+                if (isMetaDataPersistException(e, 5)) {
+                    exception = e;
+                    try {
+                        Thread.sleep(1000L * (long) Math.pow(4, retry));
+                    } catch (InterruptedException e1) {
+                        throw new IllegalStateException(e1);
+                    }
+                } else {
+                    throw e;
+                }
+            }
+        } while (exception != null && retry <= context.getConfig().getJobMetadataPersistRetry());
+
+        if (exception != null) {
+            String state = checkStateIfOverride(NonCustomProjectLevelConfig.NOTIFICATION_ON_METADATA_PERSIST.getValue());
+            if((state == null && context.getConfig().getJobMetadataPersistNotificationEnabled())
+                    || (Boolean.parseBoolean(state))) { //if override then check override prop
+                handleMetadataPersistException(exception);
+                throw new ExecuteException(exception);
+            }
+        }
+    }
+
+    protected void handleMetadataPersistException(Throwable exception) {
+
+        List<String> notifyUsers = getOverrideNotifyUsers();
+        if (notifyUsers == null || notifyUsers.size() < 1) {
+            logger.warn("no need to send email, user list is empty.");
+            return;
+        }
+        if (this instanceof DefaultExecutable) {
+            MailHelper.notifyUser(getConfig(), EmailNotificationContent.createMetadataPersistExceptionContent(
+                    exception, this), notifyUsers);
+        } else {
+            MailHelper.notifyUser(getConfig(), EmailNotificationContent.createMetadataPersistExceptionContent(
+                    exception, this.getParent()), notifyUsers);
+        }
+    }
+
+    public static boolean isMetaDataPersistException(Exception e, final int maxDepth) {
+        if (e instanceof PersistentException) {
+            return true;
+        }
+        Throwable t = e.getCause();
+        int depth = 0;
+        while (t != null && depth < maxDepth) {
+            depth++;
+            if (t instanceof PersistentException) {
+                return true;
+            }
+            t = t.getCause();
+        }
+        return false;
+    }
+
+    private String checkStateIfOverride(String state) {
+        String overrideState;
+        if (this instanceof DefaultExecutable) {
+            overrideState = EmailNotificationContent.checkOverrideConfig(this.getProject(),
+                    state);
+        } else {
+            overrideState = EmailNotificationContent.checkOverrideConfig(this.getParent().getProject(),
+                    state);
+        }
+
+        return overrideState;
+    }
+
+    private List<String> getOverrideNotifyUsers() {
+        String overrideNotifyUsers = checkStateIfOverride(
+                NonCustomProjectLevelConfig.NOTIFICATION_USER_EMAILS.getValue());
+        List<String> notifyUsers = getAllNotifyUsers(getConfig());
+        if(overrideNotifyUsers != null) {
+            notifyUsers.addAll(Arrays.asList(StringUtils.split(overrideNotifyUsers, ",")));
+        }
+        return notifyUsers;
+    }
 }
diff --git a/src/core-job/src/main/java/org/apache/kylin/job/execution/DefaultExecutable.java b/src/core-job/src/main/java/org/apache/kylin/job/execution/DefaultExecutable.java
index 6cbeefee56..a93349df3a 100644
--- a/src/core-job/src/main/java/org/apache/kylin/job/execution/DefaultExecutable.java
+++ b/src/core-job/src/main/java/org/apache/kylin/job/execution/DefaultExecutable.java
@@ -35,10 +35,10 @@ import org.apache.kylin.common.KylinConfig;
 import org.apache.kylin.common.persistence.transaction.UnitOfWork;
 import org.apache.kylin.common.scheduler.EventBusFactory;
 import org.apache.kylin.common.scheduler.JobFinishedNotifier;
-import org.apache.kylin.job.constant.JobIssueEnum;
 import org.apache.kylin.job.exception.ExecuteException;
 import org.apache.kylin.job.exception.ExecuteRuntimeException;
 import org.apache.kylin.job.exception.JobStoppedException;
+import org.apache.kylin.job.exception.PersistentException;
 import org.apache.kylin.metadata.project.EnhancedUnitOfWork;
 
 import io.kyligence.kap.guava20.shaded.common.collect.Lists;
@@ -276,12 +276,15 @@ public class DefaultExecutable extends AbstractExecutable implements ChainedExec
             switch (state) {
             case SUCCEED:
                 updateToFinalState(ExecutableState.SUCCEED, this::afterUpdateOutput, result.getShortErrMsg());
+                onStatusChange(ExecutableState.SUCCEED);
                 break;
             case DISCARDED:
                 updateToFinalState(ExecutableState.DISCARDED, this::onExecuteDiscardHook, result.getShortErrMsg());
+                onStatusChange(ExecutableState.DISCARDED);
                 break;
             case SUICIDAL:
                 updateToFinalState(ExecutableState.SUICIDAL, this::onExecuteSuicidalHook, result.getShortErrMsg());
+                onStatusChange(ExecutableState.SUICIDAL);
                 break;
             case ERROR:
             case PAUSED:
@@ -297,13 +300,15 @@ public class DefaultExecutable extends AbstractExecutable implements ChainedExec
                 String shortErrMsg = null;
                 if (state == ExecutableState.ERROR) {
                     logger.warn("[UNEXPECTED_THINGS_HAPPENED] Unexpected ERROR state discovered here!!!");
-                    notifyUserJobIssue(JobIssueEnum.JOB_ERROR);
                     info = result.getExtraInfo();
                     output = result.getErrorMsg();
                     hook = this::onExecuteErrorHook;
                     shortErrMsg = result.getShortErrMsg();
                 }
                 updateJobOutput(getProject(), getId(), state, info, output, shortErrMsg, hook);
+                if (state == ExecutableState.ERROR) {
+                    onStatusChange(ExecutableState.ERROR);
+                }
                 break;
             default:
                 throw new IllegalArgumentException("Illegal state when job finished: " + state);
@@ -355,7 +360,8 @@ public class DefaultExecutable extends AbstractExecutable implements ChainedExec
         // Hook method, default action is doing nothing
     }
 
-    private void updateToFinalState(ExecutableState finalState, Consumer<String> hook, String failedMsg) {
+    private void updateToFinalState(ExecutableState finalState, Consumer<String> hook, String failedMsg)
+            throws PersistentException, ExecuteException {
         //to final state, regardless of isStoppedNonVoluntarily, otherwise a paused job might fail to suicide
         if (!getOutput().getState().isFinalState()) {
             updateJobOutput(getProject(), getId(), finalState, null, null, failedMsg, hook);
@@ -397,4 +403,7 @@ public class DefaultExecutable extends AbstractExecutable implements ChainedExec
         // just implement it
     }
 
+    protected void onStatusChange(ExecutableState state) {
+        super.notifyUserStatusChange(state);
+    }
 }
diff --git a/src/core-job/src/main/java/org/apache/kylin/job/execution/EmailNotificationContent.java b/src/core-job/src/main/java/org/apache/kylin/job/execution/EmailNotificationContent.java
index f40f5a45ef..8c6d7949fd 100644
--- a/src/core-job/src/main/java/org/apache/kylin/job/execution/EmailNotificationContent.java
+++ b/src/core-job/src/main/java/org/apache/kylin/job/execution/EmailNotificationContent.java
@@ -18,52 +18,167 @@
 
 package org.apache.kylin.job.execution;
 
-import java.time.Clock;
-import java.time.LocalDate;
-import java.util.Locale;
-
-import org.apache.kylin.common.util.BasicEmailNotificationContent;
+import com.google.common.base.Preconditions;
+import com.google.common.collect.Maps;
+import lombok.Getter;
+import lombok.Setter;
+import org.apache.commons.lang.StringUtils;
+import org.apache.kylin.common.KylinConfig;
+import org.apache.kylin.common.constant.NonCustomProjectLevelConfig;
 import org.apache.kylin.common.util.DateFormat;
+import org.apache.kylin.common.util.Pair;
+import org.apache.kylin.common.util.StringUtil;
+import org.apache.kylin.job.constant.ExecutableConstants;
 import org.apache.kylin.job.constant.JobIssueEnum;
+import org.apache.kylin.job.util.MailNotificationUtil;
+import org.apache.kylin.metadata.project.NProjectManager;
+import org.apache.kylin.metadata.project.ProjectInstance;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
-import lombok.Getter;
-import lombok.Setter;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Matcher;
 
 @Getter
 @Setter
-public class EmailNotificationContent extends BasicEmailNotificationContent {
-
-    private AbstractExecutable executable;
-
-    public static EmailNotificationContent createContent(JobIssueEnum issue, AbstractExecutable executable) {
-        EmailNotificationContent content = new EmailNotificationContent();
-        content.setIssue(issue.getDisplayName());
-        content.setTime(LocalDate.now(Clock.systemDefaultZone()).toString());
-        content.setJobType(executable.getJobType().toString());
-        content.setProject(executable.getProject());
-        content.setExecutable(executable);
-        switch (issue) {
-        case JOB_ERROR:
-            content.setConclusion(CONCLUSION_FOR_JOB_ERROR);
-            content.setSolution(SOLUTION_FOR_JOB_ERROR);
-            break;
-        case LOAD_EMPTY_DATA:
-            content.setConclusion(CONCLUSION_FOR_LOAD_EMPTY_DATA);
-            content.setSolution(
-                    SOLUTION_FOR_LOAD_EMPTY_DATA.replaceAll("\\$\\{model_name\\}", executable.getTargetModelAlias()));
-            break;
-        case SOURCE_RECORDS_CHANGE:
-            content.setConclusion(CONCLUSION_FOR_SOURCE_RECORDS_CHANGE);
-            content.setSolution(SOLUTION_FOR_SOURCE_RECORDS_CHANGE
-                    .replaceAll("\\$\\{start_time\\}",
-                            DateFormat.formatToDateStr(executable.getDataRangeStart(),
-                                    DateFormat.DEFAULT_DATETIME_PATTERN_WITHOUT_MILLISECONDS))
-                    .replaceAll("\\$\\{end_time\\}", DateFormat.formatToDateStr(executable.getDataRangeEnd(),
-                            DateFormat.DEFAULT_DATETIME_PATTERN_WITHOUT_MILLISECONDS)));
-            break;
-        default:
-            throw new IllegalArgumentException(String.format(Locale.ROOT, "no process for jobIssue: %s.", issue));
+public class EmailNotificationContent {
+
+    protected static final Logger logger = LoggerFactory.getLogger(EmailNotificationContent.class);
+
+    public static Pair<String, String> createContent(JobIssueEnum jobIssue, AbstractExecutable executable) {
+        if (!checkState(jobIssue)) {
+            logger.info("issue state: " + jobIssue.getDisplayName() + "not need to notify users");
+            return null;
+        }
+        logger.info("notify on jobIssue change : {}", jobIssue);
+        Map<String, Object> dataMap = getDataMap(executable);
+        if (JobIssueEnum.SOURCE_RECORDS_CHANGE.equals(jobIssue)) {
+            dataMap.put("start_time", DateFormat.formatToDateStr(executable.getStartTime(),
+                    DateFormat.DEFAULT_DATETIME_PATTERN_WITHOUT_MILLISECONDS));
+            dataMap.put("end_time", DateFormat.formatToDateStr(executable.getEndTime(),
+                    DateFormat.DEFAULT_DATETIME_PATTERN_WITHOUT_MILLISECONDS));
         }
-        return content;
+        return Pair.newPair(getMailTitle(jobIssue, executable), getMailContent(jobIssue, dataMap));
+    }
+
+    public static Pair<String, String> createContent(ExecutableState state, AbstractExecutable executable,
+                                                     List<AbstractExecutable> tasks) {
+        final Output output = executable.getManager().getOutput(executable.getId());
+        if (!state.isFinalState() && state != ExecutableState.ERROR) {
+            logger.info("state: " + state + "is not right,not need to notify users");
+            return null;
+        }
+        logger.info("notify on execute change state: {}", state);
+        String states = checkOverrideConfig(executable.getProject(),
+                NonCustomProjectLevelConfig.JOB_NOTIFICATION_ENABLED_STATES.getValue());
+        String[] notificationStates;
+        if(states != null) {
+            notificationStates = StringUtils.split(states, ",");
+        } else {
+            notificationStates = executable.getConfig().getJobNotificationStates();
+        }
+
+        if(notificationStates.length < 1 || !Arrays.asList(notificationStates).contains(state.toStringState())) {
+            logger.info("state: " + state + " is not set,not need to notify users");
+            return null;
+        }
+
+        Map<String, Object> dataMap = getDataMap(executable);
+        dataMap.put("source_byte_size", String.valueOf(executable.getByteSize()));
+        dataMap.put("start_time", new Date(executable.getStartTime()).toString());
+        dataMap.put("duration", executable.getDuration() / 60000 + "mins");
+        dataMap.put("last_update_time", new Date(executable.getLastModified()).toString());
+
+        if (state == ExecutableState.ERROR) {
+            AbstractExecutable errorTask = null;
+            Output errorOutput = null;
+            for (AbstractExecutable task : tasks) {
+                errorOutput = executable.getManager().getOutput(task.getId());
+                if (errorOutput.getState() == ExecutableState.ERROR) {
+                    errorTask = task;
+                    break;
+                }
+            }
+            Preconditions.checkNotNull(errorTask,
+                    "None of the sub tasks of cubing job " + executable.getId() + " is error and this job should become success.");
+            dataMap.put("error_step", errorTask.getName());
+            if (errorTask.getOutput().getExtra().containsKey(ExecutableConstants.MR_JOB_ID)) {
+                final String mrJobId = errorOutput.getExtra().get(ExecutableConstants.MR_JOB_ID);
+                dataMap.put("mr_job_id", StringUtil.noBlank(mrJobId, "Not initialized"));
+            } else {
+                dataMap.put("mr_job_id", MailNotificationUtil.NA);
+            }
+            dataMap.put("error_log",
+                    Matcher.quoteReplacement(StringUtil.noBlank(output.getShortErrMsg(), "no error message")));
+        }
+
+        return Pair.newPair(getMailTitle(state, executable), getMailContent(state, dataMap));
+    }
+
+    public static Pair<String, String> createMetadataPersistExceptionContent(Throwable exception,
+                                                                             AbstractExecutable executable) {
+        logger.info("notify on metadata persist exception: {}", exception);
+        Map<String, Object> dataMap = getDataMap(executable);
+        dataMap.put("error_log", Matcher.quoteReplacement(StringUtil.noBlank(
+                exception.getMessage(), "no error message")));
+
+        String content = MailNotificationUtil.getMailContent(MailNotificationUtil.METADATA_PERSIST_FAIL, dataMap);
+        String title = MailNotificationUtil.getMailTitle("METADATA PERSIST", "FAIL",
+                executable.getConfig().getDeployEnv(), executable.getProject(), executable.getTargetSubjectAlias());
+        return Pair.newPair(title, content);
+    }
+
+    private static Map<String, Object> getDataMap(AbstractExecutable executable) {
+        Map<String, Object> dataMap = Maps.newHashMap();
+        dataMap.put("job_name", executable.getName());
+        dataMap.put("env_name", executable.getConfig().getDeployEnv());
+        dataMap.put("submitter", StringUtil.noBlank(executable.getSubmitter(), "missing submitter"));
+        dataMap.put("job_engine", MailNotificationUtil.getLocalHostName());
+        dataMap.put("project_name", executable.getProject());
+        dataMap.put("model_name", executable.getTargetSubjectAlias());
+        return dataMap;
+    }
+
+    private static boolean checkState(JobIssueEnum jobIssue) {
+        return JobIssueEnum.LOAD_EMPTY_DATA.equals(jobIssue)
+                || JobIssueEnum.SOURCE_RECORDS_CHANGE.equals(jobIssue);
+    }
+
+    private static String getMailContent(ExecutableState state, Map<String, Object> dataMap) {
+        return MailNotificationUtil.getMailContent(state, dataMap);
+    }
+
+    private static String getMailContent(JobIssueEnum jobIssueEnum, Map<String, Object> dataMap) {
+        return MailNotificationUtil.getMailContent(jobIssueEnum, dataMap);
     }
+
+
+    private static String getMailTitle(ExecutableState state, AbstractExecutable executable) {
+        return MailNotificationUtil.getMailTitle("JOB",
+                state.toString(),
+                executable.getConfig().getMetadataUrlPrefix(),
+                executable.getConfig().getDeployEnv(),
+                executable.getProject(),
+                executable.getTargetSubjectAlias());
+    }
+
+    private static String getMailTitle(JobIssueEnum issue, AbstractExecutable executable) {
+        return MailNotificationUtil.getMailTitle("JOB",
+                issue.getDisplayName(),
+                executable.getConfig().getMetadataUrlPrefix(),
+                executable.getConfig().getDeployEnv(),
+                executable.getProject(),
+                executable.getTargetSubjectAlias());
+    }
+
+    public static String checkOverrideConfig(String project, String overrideNotificationName) {
+        NProjectManager projectManager = NProjectManager.getInstance(KylinConfig.getInstanceFromEnv());
+        ProjectInstance projectInstance = projectManager.getProject(project);
+        return projectInstance.getOverrideKylinProps().get(overrideNotificationName);
+    }
+
+
 }
diff --git a/src/core-job/src/main/java/org/apache/kylin/job/execution/ExecutableState.java b/src/core-job/src/main/java/org/apache/kylin/job/execution/ExecutableState.java
index b10bb311b1..05f96b3d2b 100644
--- a/src/core-job/src/main/java/org/apache/kylin/job/execution/ExecutableState.java
+++ b/src/core-job/src/main/java/org/apache/kylin/job/execution/ExecutableState.java
@@ -119,4 +119,19 @@ public enum ExecutableState {
             throw new RuntimeException("invalid state:" + this);
         }
     }
+
+    public String toStringState() {
+        switch (this) {
+            case SUCCEED:
+                return "Succeed";
+            case ERROR:
+                return "Error";
+            case DISCARDED:
+                return "Discard";
+            case SUICIDAL:
+                return "Suicidal";
+            default:
+                throw new RuntimeException("invalid Executable state:" + this);
+        }
+    }
 }
diff --git a/src/core-job/src/main/java/org/apache/kylin/job/execution/NExecutableManager.java b/src/core-job/src/main/java/org/apache/kylin/job/execution/NExecutableManager.java
index a161751468..e7875e2300 100644
--- a/src/core-job/src/main/java/org/apache/kylin/job/execution/NExecutableManager.java
+++ b/src/core-job/src/main/java/org/apache/kylin/job/execution/NExecutableManager.java
@@ -79,6 +79,7 @@ import org.apache.kylin.job.constant.ExecutableConstants;
 import org.apache.kylin.job.dao.ExecutableOutputPO;
 import org.apache.kylin.job.dao.ExecutablePO;
 import org.apache.kylin.job.dao.NExecutableDao;
+import org.apache.kylin.job.exception.PersistentException;
 import org.apache.kylin.job.impl.threadpool.NDefaultScheduler;
 import org.apache.kylin.metadata.cube.model.NBatchConstants;
 import org.apache.kylin.metadata.cube.model.NDataSegment;
@@ -1507,7 +1508,7 @@ public class NExecutableManager {
                 || to == ExecutableState.ERROR || to == ExecutableState.SUICIDAL;
     }
 
-    public void updateJobOutputToHDFS(String resPath, ExecutableOutputPO obj) {
+    public void updateJobOutputToHDFS(String resPath, ExecutableOutputPO obj) throws PersistentException {
         DataOutputStream dout = null;
         try {
             Path path = new Path(resPath);
@@ -1517,6 +1518,7 @@ public class NExecutableManager {
         } catch (Exception e) {
             // the operation to update output to hdfs failed, next task should not be interrupted.
             logger.error("update job output [{}] to HDFS failed.", resPath, e);
+            throw new PersistentException("update job output: " + resPath + " to HDFS failed", e);
         } finally {
             IOUtils.closeQuietly(dout);
         }
diff --git a/src/core-job/src/main/java/org/apache/kylin/job/util/MailNotificationUtil.java b/src/core-job/src/main/java/org/apache/kylin/job/util/MailNotificationUtil.java
new file mode 100644
index 0000000000..f875ca63f6
--- /dev/null
+++ b/src/core-job/src/main/java/org/apache/kylin/job/util/MailNotificationUtil.java
@@ -0,0 +1,109 @@
+/*
+ * 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.kylin.job.util;
+
+import com.google.common.base.Joiner;
+import org.apache.kylin.common.util.MailTemplateProvider;
+import org.apache.kylin.job.constant.JobIssueEnum;
+import org.apache.kylin.job.execution.ExecutableState;
+
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.Map;
+
+public class MailNotificationUtil {
+    public static final String JOB_ERROR = "JOB_ERROR";
+    public static final String JOB_DISCARD = "JOB_DISCARD";
+    public static final String JOB_SUCCEED = "JOB_SUCCEED";
+    public static final String JOB_SUICIDAL = "JOB_SUICIDAL";
+    public static final String JOB_LOAD_EMPTY_DATA = "LOAD_EMPTY_DATA";
+    public static final String JOB_SOURCE_RECORDS_CHANGE = "SOURCE_RECORDS_CHANGE";
+    public static final String METADATA_PERSIST_FAIL = "METADATA_PERSIST_FAIL";
+
+    public static final String NA = "NA";
+
+    private static String localHostName;
+
+    static {
+        try {
+            localHostName = InetAddress.getLocalHost().getCanonicalHostName();
+        } catch (UnknownHostException e) {
+            localHostName = "UNKNOWN";
+        }
+    }
+
+    private MailNotificationUtil() {
+        throw new IllegalStateException("Class MailNotificationUtil is an utility class !");
+    }
+
+    private static String getMailTemplateKey(ExecutableState state) {
+        switch (state) {
+            case ERROR:
+                return JOB_ERROR;
+            case DISCARDED:
+                return JOB_DISCARD;
+            case SUCCEED:
+                return JOB_SUCCEED;
+            case SUICIDAL:
+                return JOB_SUICIDAL;
+            default:
+                return null;
+        }
+    }
+
+    private static String getMailTemplateKey(JobIssueEnum jobIssue) {
+        switch (jobIssue) {
+            case LOAD_EMPTY_DATA:
+                return JOB_LOAD_EMPTY_DATA;
+            case SOURCE_RECORDS_CHANGE:
+                return JOB_SOURCE_RECORDS_CHANGE;
+            default:
+                return null;
+        }
+    }
+
+
+    public static String getLocalHostName() {
+        return localHostName;
+    }
+
+    public static String getMailContent(ExecutableState state, Map<String, Object> dataMap) {
+        return MailTemplateProvider.getInstance().buildMailContent(MailNotificationUtil.getMailTemplateKey(state),
+                dataMap);
+    }
+
+    public static String getMailContent(JobIssueEnum jobIssue, Map<String, Object> dataMap) {
+        return MailTemplateProvider.getInstance().buildMailContent(MailNotificationUtil.getMailTemplateKey(jobIssue),
+                dataMap);
+    }
+
+    public static String getMailContent(String key, Map<String, Object> dataMap) {
+        return MailTemplateProvider.getInstance().buildMailContent(key, dataMap);
+    }
+
+    public static String getMailTitle(String... titleParts) {
+        return "[" + Joiner.on("]-[").join(titleParts) + "]";
+    }
+
+    public static boolean hasMailNotification(ExecutableState state) {
+        return getMailTemplateKey(state) != null;
+    }
+
+
+}
diff --git a/src/core-job/src/main/resources/mail_templates/JOB_DISCARD.ftl b/src/core-job/src/main/resources/mail_templates/JOB_DISCARD.ftl
new file mode 100644
index 0000000000..1fe1dff080
--- /dev/null
+++ b/src/core-job/src/main/resources/mail_templates/JOB_DISCARD.ftl
@@ -0,0 +1,274 @@
+<!--
+* 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.
+-->
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+        "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+
+<head>
+    <meta http-equiv="Content-Type" content="Multipart/Alternative; charset=UTF-8"/>
+    <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
+</head>
+
+<style>
+    html {
+        font-size: 10px;
+    }
+
+    * {
+        box-sizing: border-box;
+    }
+
+    a:hover,
+    a:focus {
+        color: #23527c;
+        text-decoration: underline;
+    }
+
+    a:focus {
+        outline: 5px auto -webkit-focus-ring-color;
+        outline-offset: -2px;
+    }
+</style>
+
+<body>
+<div style="margin-left:5%;margin-right:5%;font-family: 'Trebuchet MS ', Arial, Helvetica, sans-serif;">
+<span style="
+    line-height: 1.1;font-size: 18px;">
+    <p style="text-align:left;">Dear Kylin5 user,</p>
+    <p>It's a pity that the job is discarded. Thank you for using Kylin.</p>
+</span>
+    <hr style="margin-top: 20px;
+    margin-bottom: 20px;
+    border: 0;
+    border-top: 1px solid #eee;">
+    <h1>
+    <span style="display: inline;
+            background-color: #607D8B;
+            color: #fff;
+            line-height: 1;
+            font-weight: 700;
+            font-size:36px;
+            text-align: center;">&nbsp;Discarded&nbsp;</span>
+    </h1>
+    <hr style="margin-top: 20px;
+            margin-bottom: 20px;
+            border: 0;
+            border-top: 1px solid #eee;">
+    <table cellpadding="0" cellspacing="0" width="100%" style="border-collapse: collapse;border:1px solid #f8f8f8">
+
+        <tr>
+
+            <td style="padding: 10px 15px;
+                    background-color: #eeeeee;
+                    border:1px solid #f8f8f8;">
+                <h4 style="margin-top: 0;
+                        margin-bottom: 0;
+                        font-size: 16px;
+                        color: inherit;
+                        color: #404040;
+                        font-family: 'Trebuchet MS ', Arial, Helvetica, sans-serif;">
+                ${job_name}
+                </h4>
+            </td>
+        </tr>
+
+        <tr>
+
+            <td style="padding: 10px 15px;
+                    background-color: #eeeeee;
+                    border:1px solid #f8f8f8;">
+                <h4 style="margin-top: 0;
+                        margin-bottom: 0;
+                        font-size: 16px;
+                        color: inherit;
+                        color: #404040;
+                        font-family: 'Trebuchet MS ', Arial, Helvetica, sans-serif;">
+                ${env_name}
+                </h4>
+            </td>
+        </tr>
+
+        <tr>
+
+            <td style="padding: 15px;">
+                <table cellpadding="0" cellspacing="0" width="100%"
+                       style="margin-bottom: 20px;border:1 solid #ddd;border-collapse: collapse;font-family: 'Trebuchet MS ', Arial, Helvetica, sans-serif;">
+                    <tr>
+                        <th width="30%" style="padding: 8px;
+                                            line-height: 1.42857143;
+                                            vertical-align: top;
+                                            border: 1px solid #ddd;
+                                            text-align: left;
+                                            font-size: medium;
+                                            font-style: normal;">Submitter
+                        </th>
+                        <td style="padding: 8px;
+                                line-height: 1.42857143;
+                                vertical-align: top;
+                                border: 1px solid #ddd;
+                                font-size: medium;
+                                font-style: normal;">
+                        ${submitter}
+                        </td>
+                    </tr>
+                    <tr>
+                        <th width="30%" style="padding: 8px;
+                                            line-height: 1.42857143;
+                                            vertical-align: top;
+                                            border: 1px solid #ddd;
+                                            text-align: left;
+                                            font-size: medium;
+                                            font-style: normal;">Job Engine
+                        </th>
+                        <td style="padding: 8px;
+                                line-height: 1.42857143;
+                                vertical-align: top;
+                                border: 1px solid #ddd;
+                                font-size: medium;
+                                font-style: normal;">
+                        ${job_engine}
+                        </td>
+                    </tr>
+                    <tr>
+                        <th width="30%" style="padding: 8px;
+                                            line-height: 1.42857143;
+                                            vertical-align: top;
+                                            border: 1px solid #ddd;
+                                            text-align: left;
+                                            font-size: medium;
+                                            font-style: normal;">Project
+                        </th>
+                        <td style="padding: 8px;
+                                line-height: 1.42857143;
+                                vertical-align: top;
+                                border: 1px solid #ddd;
+                                font-size: medium;
+                                font-style: normal;">
+                        ${project_name}
+                        </td>
+                    </tr>
+                    <tr>
+                        <th width="30%" style="padding: 8px;
+                                            line-height: 1.42857143;
+                                            vertical-align: top;
+                                            border: 1px solid #ddd;
+                                            text-align: left;
+                                            font-size: medium;
+                                            font-style: normal;">Model Name
+                        </th>
+                        <td style="padding: 8px;
+                                line-height: 1.42857143;
+                                vertical-align: top;
+                                border: 1px solid #ddd;
+                                font-size: medium;
+                                font-style: normal;">
+                        ${model_name}
+                        </td>
+                    </tr>
+                    <tr>
+                        <th width="30%" style="padding: 8px;
+                                            line-height: 1.42857143;
+                                            vertical-align: top;
+                                            border: 1px solid #ddd;
+                                            text-align: left;
+                                            font-size: medium;
+                                            font-style: normal;">
+                            Source Byte Size
+                        </th>
+                        <td style="padding: 8px;
+                                line-height: 1.42857143;
+                                vertical-align: top;
+                                border: 1px solid #ddd;
+                                font-size: medium;
+                                font-style: normal;">
+                        ${source_byte_size}
+                        </td>
+                    </tr>
+                    <tr>
+                        <th width="30%" style="padding: 8px;
+                                            line-height: 1.42857143;
+                                            vertical-align: top;
+                                            border: 1px solid #ddd;
+                                            text-align: left;
+                                            font-size: medium;
+                                            font-style: normal;">Start Time
+                        </th>
+                        <td style="padding: 8px;
+                                line-height: 1.42857143;
+                                vertical-align: top;
+                                border: 1px solid #ddd;
+                                font-size: medium;
+                                font-style: normal;">
+                        ${start_time}
+                        </td>
+                    </tr>
+                    <tr>
+                        <th width="30%" style="padding: 8px;
+                                            line-height: 1.42857143;
+                                            vertical-align: top;
+                                            border: 1px solid #ddd;
+                                            text-align: left;
+                                            font-size: medium;
+                                            font-style: normal;">
+                            Duration
+                        </th>
+                        <td style="padding: 8px;
+                                line-height: 1.42857143;
+                                vertical-align: top;
+                                border: 1px solid #ddd;
+                                font-size: medium;
+                                font-style: normal;">
+                        ${duration}
+                        </td>
+                    </tr>
+                    <tr>
+                        <th width="30%" style="padding: 8px;
+                                            line-height: 1.42857143;
+                                            vertical-align: top;
+                                            border: 1px solid #ddd;
+                                            text-align: left;
+                                            font-size: medium;
+                                            font-style: normal;">
+                            Last Update Time
+                        </th>
+                        <td style="padding: 8px;
+                                line-height: 1.42857143;
+                                vertical-align: top;
+                                border: 1px solid #ddd;
+                                font-size: medium;
+                                font-style: normal;">
+                        ${last_update_time}
+                        </td>
+                    </tr>
+                </table>
+            </td>
+        </tr>
+    </table>
+    <hr style="margin-top: 20px;
+    margin-bottom: 20px;
+    border: 0;
+    border-top: 1px solid #eee;">
+    <h4 style="font-weight: 500;
+    line-height: 1.1;font-size:18px;">
+        <p>Best Wishes!</p>
+        <p style="margin: 0 0 10px;"><b>Kylin Team</b></p>
+    </h4>
+</div>
+</body>
+
+</html>
\ No newline at end of file
diff --git a/src/core-job/src/main/resources/mail_templates/JOB_ERROR.ftl b/src/core-job/src/main/resources/mail_templates/JOB_ERROR.ftl
new file mode 100644
index 0000000000..65733d12ec
--- /dev/null
+++ b/src/core-job/src/main/resources/mail_templates/JOB_ERROR.ftl
@@ -0,0 +1,390 @@
+<!--
+* 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.
+-->
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+        "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+
+<head>
+    <meta http-equiv="Content-Type" content="Multipart/Alternative; charset=UTF-8"/>
+    <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
+</head>
+
+<style>
+    html {
+        font-size: 10px;
+    }
+
+    * {
+        box-sizing: border-box;
+    }
+
+    a:hover,
+    a:focus {
+        color: #23527c;
+        text-decoration: underline;
+    }
+
+    a:focus {
+        outline: 5px auto -webkit-focus-ring-color;
+        outline-offset: -2px;
+    }
+</style>
+
+<body>
+<div style="margin-left:5%;margin-right:5%;font-family: 'Trebuchet MS ', Arial, Helvetica, sans-serif;">
+<span style="
+    line-height: 1.1;font-size: 18px;">
+    <p style="text-align:left;">Dear Kylin5 user,</p>
+    <p>This job of building model <strong>failed</strong>.</p>
+    <p>Please check the error log and try again, or you can contact the kylin team.</p>
+    <p>Thank you for using Kylin and we apologize for the inconvenience.</p>
+</span>
+    <hr style="margin-top: 20px;
+    margin-bottom: 20px;
+    border: 0;
+    border-top: 1px solid #eee;">
+    <h1>
+    <span style="display: inline;
+            background-color: #d9534f;
+            color: #fff;
+            line-height: 1;
+            font-weight: 700;
+            font-size:36px;
+            text-align: center;">&nbsp;Error&nbsp;</span>
+    </h1>
+    <hr style="margin-top: 20px;
+            margin-bottom: 20px;
+            border: 0;
+            border-top: 1px solid #eee;">
+
+    <table cellpadding="0" cellspacing="0" width="100%" style="border-collapse: collapse;border:1px solid #ebccd1;">
+
+        <tr>
+
+            <td style="padding: 10px 15px;
+                    background-color: #f2dede;
+                    border:1px solid #ebccd1;">
+                <h4 style="margin-top: 0;
+                        margin-bottom: 0;
+                        font-size: 16px;
+                        color: inherit;
+                        color: #a94442;
+                        font-family: 'Trebuchet MS ', Arial, Helvetica, sans-serif;">
+                ${job_name}
+                </h4>
+            </td>
+        </tr>
+        <tr>
+
+            <td style="padding: 10px 15px;
+                    background-color: #f2dede;
+                    border:1px solid #ebccd1;">
+                <h4 style="margin-top: 0;
+                        margin-bottom: 0;
+                        font-size: 16px;
+                        color: inherit;
+                        color: #a94442;
+                        font-family: 'Trebuchet MS ', Arial, Helvetica, sans-serif;">
+                ${env_name}
+                </h4>
+            </td>
+        </tr>
+        <tr>
+
+            <td style="padding: 15px;">
+                <table cellpadding="0" cellspacing="0" width="100%"
+                       style="margin-bottom: 20px;border:1 solid #ddd;border-collapse: collapse;font-family: 'Trebuchet MS ', Arial, Helvetica, sans-serif;">
+                    <tr>
+                        <th width="30%" style="padding: 8px;
+                                            line-height: 1.42857143;
+                                            vertical-align: top;
+                                            border: 1px solid #ddd;
+                                            text-align: left;
+                                            font-size: medium;
+                                            font-style: normal;">Submitter
+                        </th>
+                        <td style="padding: 8px;
+                                line-height: 1.42857143;
+                                vertical-align: top;
+                                border: 1px solid #ddd;
+                                font-size: medium;
+                                font-style: normal;">
+                        ${submitter}
+                        </td>
+                    </tr>
+                    <tr>
+                        <th width="30%" style="padding: 8px;
+                                            line-height: 1.42857143;
+                                            vertical-align: top;
+                                            border: 1px solid #ddd;
+                                            text-align: left;
+                                            font-size: medium;
+                                            font-style: normal;">Job Engine
+                        </th>
+                        <td style="padding: 8px;
+                                line-height: 1.42857143;
+                                vertical-align: top;
+                                border: 1px solid #ddd;
+                                font-size: medium;
+                                font-style: normal;">
+                        ${job_engine}
+                        </td>
+                    </tr>
+                    <tr>
+                        <th width="30%" style="padding: 8px;
+                                            line-height: 1.42857143;
+                                            vertical-align: top;
+                                            border: 1px solid #ddd;
+                                            text-align: left;
+                                            font-size: medium;
+                                            font-style: normal;">Project
+                        </th>
+                        <td style="padding: 8px;
+                                line-height: 1.42857143;
+                                vertical-align: top;
+                                border: 1px solid #ddd;
+                                font-size: medium;
+                                font-style: normal;">
+                        ${project_name}
+                        </td>
+                    </tr>
+                    <tr>
+                        <th width="30%" style="padding: 8px;
+                                            line-height: 1.42857143;
+                                            vertical-align: top;
+                                            border: 1px solid #ddd;
+                                            text-align: left;
+                                            font-size: medium;
+                                            font-style: normal;">Model Name
+                        </th>
+                        <td style="padding: 8px;
+                                line-height: 1.42857143;
+                                vertical-align: top;
+                                border: 1px solid #ddd;
+                                font-size: medium;
+                                font-style: normal;">
+                        ${model_name}
+                        </td>
+                    </tr>
+                    <tr>
+                        <th width="30%" style="padding: 8px;
+                                            line-height: 1.42857143;
+                                            vertical-align: top;
+                                            border: 1px solid #ddd;
+                                            text-align: left;
+                                            font-size: medium;
+                                            font-style: normal;">
+                            Source Byte Size
+                        </th>
+                        <td style="padding: 8px;
+                                line-height: 1.42857143;
+                                vertical-align: top;
+                                border: 1px solid #ddd;
+                                font-size: medium;
+                                font-style: normal;">
+                        ${source_byte_size}
+                        </td>
+                    </tr>
+                    <tr>
+                        <th width="30%" style="padding: 8px;
+                                            line-height: 1.42857143;
+                                            vertical-align: top;
+                                            border: 1px solid #ddd;
+                                            text-align: left;
+                                            font-size: medium;
+                                            font-style: normal;">Start Time
+                        </th>
+                        <td style="padding: 8px;
+                                line-height: 1.42857143;
+                                vertical-align: top;
+                                border: 1px solid #ddd;
+                                font-size: medium;
+                                font-style: normal;">
+                        ${start_time}
+                        </td>
+                    </tr>
+                    <tr>
+                        <th width="30%" style="padding: 8px;
+                                            line-height: 1.42857143;
+                                            vertical-align: top;
+                                            border: 1px solid #ddd;
+                                            text-align: left;
+                                            font-size: medium;
+                                            font-style: normal;">
+                            Duration
+                        </th>
+                        <td style="padding: 8px;
+                                line-height: 1.42857143;
+                                vertical-align: top;
+                                border: 1px solid #ddd;
+                                font-size: medium;
+                                font-style: normal;">
+                        ${duration}
+                        </td>
+                    </tr>
+                    <tr>
+                        <th width="30%" style="padding: 8px;
+                                            line-height: 1.42857143;
+                                            vertical-align: top;
+                                            border: 1px solid #ddd;
+                                            text-align: left;
+                                            font-size: medium;
+                                            font-style: normal;">MR Waiting Time
+                        </th>
+                        <td style="padding: 8px;
+                                line-height: 1.42857143;
+                                vertical-align: top;
+                                border: 1px solid #ddd;
+                                font-size: medium;
+                                font-style: normal;">
+                        NA
+                        </td>
+                    </tr>
+                    <tr>
+                        <th width="30%" style="padding: 8px;
+                                            line-height: 1.42857143;
+                                            vertical-align: top;
+                                            border: 1px solid #ddd;
+                                            text-align: left;
+                                            font-size: medium;
+                                            font-style: normal;">
+                            Last Update Time
+                        </th>
+                        <td style="padding: 8px;
+                                line-height: 1.42857143;
+                                vertical-align: top;
+                                border: 1px solid #ddd;
+                                font-size: medium;
+                                font-style: normal;">
+                        ${last_update_time}
+                        </td>
+                    </tr>
+                </table>
+            </td>
+        </tr>
+
+        <tr>
+
+            <td style="padding: 10px 15px;
+                    background-color: #f2dede;
+                    border:1px solid #ebccd1;">
+                <h4 style="margin-top: 0;
+                        margin-bottom: 0;
+                        font-size: 16px;
+                        color: inherit;
+                        color: #a94442;
+                        font-family: 'Trebuchet MS ', Arial, Helvetica, sans-serif;">
+                    Job Error Details
+                </h4>
+            </td>
+        </tr>
+        <tr>
+
+            <td style="padding: 15px;">
+                <table cellpadding="0" cellspacing="0" width="100%"
+                       style="margin-bottom: 20px;border:1 solid #ddd;border-collapse: collapse;font-family: 'Trebuchet MS ', Arial, Helvetica, sans-serif;">
+                    <tr>
+                        <th width="30%" style="padding: 8px;
+                                            line-height: 1.42857143;
+                                            vertical-align: top;
+                                            border: 1px solid #ddd;
+                                            text-align: left;
+                                            font-size: medium;
+                                            font-style: normal;">
+                            Error Step
+                        </th>
+                        <td style="padding: 8px;
+                                line-height: 1.42857143;
+                                vertical-align: top;
+                                border: 1px solid #ddd;
+                                font-size: medium;
+                                font-style: normal;">
+                        ${error_step}
+                        </td>
+                    </tr>
+                    <tr>
+                        <th width="30%" style="padding: 8px;
+                                            line-height: 1.42857143;
+                                            vertical-align: top;
+                                            border: 1px solid #ddd;
+                                            text-align: left;
+                                            font-size: medium;
+                                            font-style: normal;">
+                            MR Job
+                        </th>
+                        <td style="padding: 8px;
+                                line-height: 1.42857143;
+                                vertical-align: top;
+                                border: 1px solid #ddd;
+                                font-size: medium;
+                                font-style: normal;">
+                        ${mr_job_id}
+                        </td>
+                    </tr>
+                </table>
+            </td>
+        </tr>
+
+        <tr>
+
+            <td style="padding: 10px 15px;
+                    background-color: #f2dede;
+                    border:1px solid #ebccd1;">
+                <h4 style="margin-top: 0;
+                        margin-bottom: 0;
+                        font-size: 16px;
+                        color: inherit;
+                        color: #a94442;
+                        font-family: 'Trebuchet MS ', Arial, Helvetica, sans-serif;">
+                    Logs
+                </h4>
+            </td>
+        </tr>
+        <tr>
+
+            <td style="padding: 15px;">
+                <table cellpadding="0" cellspacing="0" width="100%"
+                       style="margin-bottom: 20px;border:1 solid #ddd;border-collapse: collapse;font-family: 'Trebuchet MS ', Arial, Helvetica, sans-serif;">
+                    <tr>
+                        <td style="padding: 8px;
+                                line-height: 1.42857143;
+                                vertical-align: top;
+                                border: 1px solid #ddd;
+                                font-size: medium;
+                                font-style: normal;">
+
+                            <pre style="white-space: pre-wrap;">${error_log}</pre>
+                        </td>
+                    </tr>
+                </table>
+            </td>
+        </tr>
+    </table>
+    <hr style="margin-top: 20px;
+    margin-bottom: 20px;
+    border: 0;
+    border-top: 1px solid #eee;">
+    <h4 style="font-weight: 500;
+    line-height: 1.1;font-size:18px;">
+        <p>Best Wishes!</p>
+        <p style="margin: 0 0 10px;"><b>Kylin Team</b></p>
+    </h4>
+</div>
+</body>
+
+</html>
\ No newline at end of file
diff --git a/src/core-job/src/main/resources/mail_templates/JOB_SUCCEED.ftl b/src/core-job/src/main/resources/mail_templates/JOB_SUCCEED.ftl
new file mode 100644
index 0000000000..f793c3e803
--- /dev/null
+++ b/src/core-job/src/main/resources/mail_templates/JOB_SUCCEED.ftl
@@ -0,0 +1,273 @@
+<!--
+* 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.
+-->
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+        "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+
+<head>
+    <meta http-equiv="Content-Type" content="Multipart/Alternative; charset=UTF-8"/>
+    <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
+</head>
+
+<style>
+    html {
+        font-size: 10px;
+    }
+
+    * {
+        box-sizing: border-box;
+    }
+
+    a:hover,
+    a:focus {
+        color: #23527c;
+        text-decoration: underline;
+    }
+
+    a:focus {
+        outline: 5px auto -webkit-focus-ring-color;
+        outline-offset: -2px;
+    }
+</style>
+
+<body>
+<div style="margin-left:5%;margin-right:5%;font-family: 'Trebuchet MS ', Arial, Helvetica, sans-serif;">
+<span style="line-height: 1.1;font-size: 18px;">
+    <p style="text-align:left;">Dear Kylin5 user,</p>
+    <p>Congratulations! Please feel free to query based on kylin model. Thank you for using Kylin.</p>
+</span>
+    <hr style="margin-top: 20px;
+            margin-bottom: 20px;
+            border: 0;
+            border-top: 1px solid #eee;">
+    <h1>
+    <span style="display: inline;
+            background-color: #5cb85c;
+            color: #fff;
+            line-height: 1;
+            font-weight: 700;
+            font-size:36px;
+            text-align: center;">&nbsp;Succeed&nbsp;</span>
+    </h1>
+    <hr style="margin-top: 20px;
+            margin-bottom: 20px;
+            border: 0;
+            border-top: 1px solid #eee;">
+    <table cellpadding="0" cellspacing="0" width="100%" style="border-collapse: collapse;border:1px solid #d6e9c6;">
+
+        <tr>
+
+            <td style="padding: 10px 15px;
+                    background-color: #dff0d8;
+                    border:1px solid #d6e9c6;">
+                <h4 style="margin-top: 0;
+                        margin-bottom: 0;
+                        font-size: 16px;
+                        color: inherit;
+                        color: #3c763d;
+                        font-family: 'Trebuchet MS ', Arial, Helvetica, sans-serif;">
+                ${job_name}
+                </h4>
+            </td>
+        </tr>
+
+        <tr>
+
+            <td style="padding: 10px 15px;
+                    background-color: #dff0d8;
+                    border:1px solid #d6e9c6;">
+                <h4 style="margin-top: 0;
+                        margin-bottom: 0;
+                        font-size: 16px;
+                        color: inherit;
+                        color: #3c763d;
+                        font-family: 'Trebuchet MS ', Arial, Helvetica, sans-serif;">
+                ${env_name}
+                </h4>
+            </td>
+        </tr>
+
+        <tr>
+
+            <td style="padding: 15px;">
+                <table cellpadding="0" cellspacing="0" width="100%"
+                       style="margin-bottom: 20px;border:1 solid #ddd;border-collapse: collapse;font-family: 'Trebuchet MS ', Arial, Helvetica, sans-serif;">
+                    <tr>
+                        <th width="30%" style="padding: 8px;
+                                            line-height: 1.42857143;
+                                            vertical-align: top;
+                                            border: 1px solid #ddd;
+                                            text-align: left;
+                                            font-size: medium;
+                                            font-style: normal;">Submitter
+                        </th>
+                        <td style="padding: 8px;
+                                line-height: 1.42857143;
+                                vertical-align: top;
+                                border: 1px solid #ddd;
+                                font-size: medium;
+                                font-style: normal;">
+                        ${submitter}
+                        </td>
+                    </tr>
+                    <tr>
+                        <th width="30%" style="padding: 8px;
+                                            line-height: 1.42857143;
+                                            vertical-align: top;
+                                            border: 1px solid #ddd;
+                                            text-align: left;
+                                            font-size: medium;
+                                            font-style: normal;">Job Engine
+                        </th>
+                        <td style="padding: 8px;
+                                line-height: 1.42857143;
+                                vertical-align: top;
+                                border: 1px solid #ddd;
+                                font-size: medium;
+                                font-style: normal;">
+                        ${job_engine}
+                        </td>
+                    </tr>
+                    <tr>
+                        <th width="30%" style="padding: 8px;
+                                            line-height: 1.42857143;
+                                            vertical-align: top;
+                                            border: 1px solid #ddd;
+                                            text-align: left;
+                                            font-size: medium;
+                                            font-style: normal;">Project
+                        </th>
+                        <td style="padding: 8px;
+                                line-height: 1.42857143;
+                                vertical-align: top;
+                                border: 1px solid #ddd;
+                                font-size: medium;
+                                font-style: normal;">
+                        ${project_name}
+                        </td>
+                    </tr>
+                    <tr>
+                        <th width="30%" style="padding: 8px;
+                                            line-height: 1.42857143;
+                                            vertical-align: top;
+                                            border: 1px solid #ddd;
+                                            text-align: left;
+                                            font-size: medium;
+                                            font-style: normal;">Model Name
+                        </th>
+                        <td style="padding: 8px;
+                                line-height: 1.42857143;
+                                vertical-align: top;
+                                border: 1px solid #ddd;
+                                font-size: medium;
+                                font-style: normal;">
+                        ${model_name}
+                        </td>
+                    </tr>
+                    <tr>
+                        <th width="30%" style="padding: 8px;
+                                            line-height: 1.42857143;
+                                            vertical-align: top;
+                                            border: 1px solid #ddd;
+                                            text-align: left;
+                                            font-size: medium;
+                                            font-style: normal;">
+                            Source Byte Size
+                        </th>
+                        <td style="padding: 8px;
+                                line-height: 1.42857143;
+                                vertical-align: top;
+                                border: 1px solid #ddd;
+                                font-size: medium;
+                                font-style: normal;">
+                        ${source_byte_size}
+                        </td>
+                    </tr>
+                    <tr>
+                        <th width="30%" style="padding: 8px;
+                                            line-height: 1.42857143;
+                                            vertical-align: top;
+                                            border: 1px solid #ddd;
+                                            text-align: left;
+                                            font-size: medium;
+                                            font-style: normal;">Start Time
+                        </th>
+                        <td style="padding: 8px;
+                                line-height: 1.42857143;
+                                vertical-align: top;
+                                border: 1px solid #ddd;
+                                font-size: medium;
+                                font-style: normal;">
+                        ${start_time}
+                        </td>
+                    </tr>
+                    <tr>
+                        <th width="30%" style="padding: 8px;
+                                            line-height: 1.42857143;
+                                            vertical-align: top;
+                                            border: 1px solid #ddd;
+                                            text-align: left;
+                                            font-size: medium;
+                                            font-style: normal;">
+                            Duration
+                        </th>
+                        <td style="padding: 8px;
+                                line-height: 1.42857143;
+                                vertical-align: top;
+                                border: 1px solid #ddd;
+                                font-size: medium;
+                                font-style: normal;">
+                        ${duration}
+                        </td>
+                    </tr>
+                    <tr>
+                        <th width="30%" style="padding: 8px;
+                                            line-height: 1.42857143;
+                                            vertical-align: top;
+                                            border: 1px solid #ddd;
+                                            text-align: left;
+                                            font-size: medium;
+                                            font-style: normal;">
+                            Last Update Time
+                        </th>
+                        <td style="padding: 8px;
+                                line-height: 1.42857143;
+                                vertical-align: top;
+                                border: 1px solid #ddd;
+                                font-size: medium;
+                                font-style: normal;">
+                        ${last_update_time}
+                        </td>
+                    </tr>
+                </table>
+            </td>
+        </tr>
+    </table>
+    <hr style="margin-top: 20px;
+            margin-bottom: 20px;
+            border: 0;
+            border-top: 1px solid #eee;">
+    <h4 style="font-weight: 500;
+            line-height: 1.1;font-size:18px;">
+        <p>Best Wishes!</p>
+        <p style="margin: 0 0 10px;"><b>Kylin Team</b></p>
+    </h4>
+</div>
+</body>
+
+</html>
\ No newline at end of file
diff --git a/src/core-job/src/main/resources/mail_templates/LOAD_EMPTY_DATA.ftl b/src/core-job/src/main/resources/mail_templates/LOAD_EMPTY_DATA.ftl
new file mode 100644
index 0000000000..41400ee2de
--- /dev/null
+++ b/src/core-job/src/main/resources/mail_templates/LOAD_EMPTY_DATA.ftl
@@ -0,0 +1,200 @@
+<!--
+* 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.
+-->
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+        "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+
+<head>
+    <meta http-equiv="Content-Type" content="Multipart/Alternative; charset=UTF-8"/>
+    <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
+</head>
+
+<style>
+    html {
+        font-size: 10px;
+    }
+
+    * {
+        box-sizing: border-box;
+    }
+
+    a:hover,
+    a:focus {
+        color: #23527c;
+        text-decoration: underline;
+    }
+
+    a:focus {
+        outline: 5px auto -webkit-focus-ring-color;
+        outline-offset: -2px;
+    }
+</style>
+
+<body>
+<div style="margin-left:5%;margin-right:5%;font-family: 'Trebuchet MS ', Arial, Helvetica, sans-serif;">
+<span style="
+    line-height: 1.1;font-size: 18px;">
+    <p style="text-align:left;">Dear Kylin5 user,</p>
+    <p>We found a job has loaded empty data in your Kylin system as below.</p>
+    <p>It won't affect your system stability, and you may reload data by following instructions.</p>
+</span>
+    <hr style="margin-top: 20px;
+    margin-bottom: 20px;
+    border: 0;
+    border-top: 1px solid #eee;">
+    <h1>
+    <span style="display: inline;
+            background-color: #607D8B;
+            color: #fff;
+            line-height: 1;
+            font-weight: 700;
+            font-size:36px;
+            text-align: center;">&nbsp;LOADED EMPTY DATA&nbsp;</span>
+    </h1>
+    <hr style="margin-top: 20px;
+            margin-bottom: 20px;
+            border: 0;
+            border-top: 1px solid #eee;">
+    <table cellpadding="0" cellspacing="0" width="100%" style="border-collapse: collapse;border:1px solid #f8f8f8">
+
+        <tr>
+
+            <td style="padding: 10px 15px;
+                    background-color: #eeeeee;
+                    border:1px solid #f8f8f8;">
+                <h4 style="margin-top: 0;
+                        margin-bottom: 0;
+                        font-size: 16px;
+                        color: inherit;
+                        color: #404040;
+                        font-family: 'Trebuchet MS ', Arial, Helvetica, sans-serif;">
+                ${job_name}
+                </h4>
+            </td>
+        </tr>
+
+        <tr>
+
+            <td style="padding: 10px 15px;
+                    background-color: #eeeeee;
+                    border:1px solid #f8f8f8;">
+                <h4 style="margin-top: 0;
+                        margin-bottom: 0;
+                        font-size: 16px;
+                        color: inherit;
+                        color: #404040;
+                        font-family: 'Trebuchet MS ', Arial, Helvetica, sans-serif;">
+                ${env_name}
+                </h4>
+            </td>
+        </tr>
+
+        <tr>
+
+            <td style="padding: 15px;">
+                <table cellpadding="0" cellspacing="0" width="100%"
+                       style="margin-bottom: 20px;border:1 solid #ddd;border-collapse: collapse;font-family: 'Trebuchet MS ', Arial, Helvetica, sans-serif;">
+                    <tr>
+                        <th width="30%" style="padding: 8px;
+                                            line-height: 1.42857143;
+                                            vertical-align: top;
+                                            border: 1px solid #ddd;
+                                            text-align: left;
+                                            font-size: medium;
+                                            font-style: normal;">Submitter
+                        </th>
+                        <td style="padding: 8px;
+                                line-height: 1.42857143;
+                                vertical-align: top;
+                                border: 1px solid #ddd;
+                                font-size: medium;
+                                font-style: normal;">
+                        ${submitter}
+                        </td>
+                    </tr>
+                    <tr>
+                        <th width="30%" style="padding: 8px;
+                                            line-height: 1.42857143;
+                                            vertical-align: top;
+                                            border: 1px solid #ddd;
+                                            text-align: left;
+                                            font-size: medium;
+                                            font-style: normal;">Job Engine
+                        </th>
+                        <td style="padding: 8px;
+                                line-height: 1.42857143;
+                                vertical-align: top;
+                                border: 1px solid #ddd;
+                                font-size: medium;
+                                font-style: normal;">
+                        ${job_engine}
+                        </td>
+                    </tr>
+                    <tr>
+                        <th width="30%" style="padding: 8px;
+                                            line-height: 1.42857143;
+                                            vertical-align: top;
+                                            border: 1px solid #ddd;
+                                            text-align: left;
+                                            font-size: medium;
+                                            font-style: normal;">Project
+                        </th>
+                        <td style="padding: 8px;
+                                line-height: 1.42857143;
+                                vertical-align: top;
+                                border: 1px solid #ddd;
+                                font-size: medium;
+                                font-style: normal;">
+                        ${project_name}
+                        </td>
+                    </tr>
+                    <tr>
+                        <th width="30%" style="padding: 8px;
+                                            line-height: 1.42857143;
+                                            vertical-align: top;
+                                            border: 1px solid #ddd;
+                                            text-align: left;
+                                            font-size: medium;
+                                            font-style: normal;">Model Name
+                        </th>
+                        <td style="padding: 8px;
+                                line-height: 1.42857143;
+                                vertical-align: top;
+                                border: 1px solid #ddd;
+                                font-size: medium;
+                                font-style: normal;">
+                        ${model_name}
+                        </td>
+                    </tr>
+                </table>
+            </td>
+        </tr>
+    </table>
+    <hr style="margin-top: 20px;
+    margin-bottom: 20px;
+    border: 0;
+    border-top: 1px solid #eee;">
+    <h4 style="font-weight: 500;
+    line-height: 1.1;font-size:18px;">
+        <p>Best Wishes!</p>
+        <p style="margin: 0 0 10px;"><b>Kylin Team</b></p>
+    </h4>
+</div>
+</body>
+
+</html>
\ No newline at end of file
diff --git a/src/core-job/src/main/resources/mail_templates/METADATA_PERSIST_FAIL.ftl b/src/core-job/src/main/resources/mail_templates/METADATA_PERSIST_FAIL.ftl
new file mode 100644
index 0000000000..b18e34e273
--- /dev/null
+++ b/src/core-job/src/main/resources/mail_templates/METADATA_PERSIST_FAIL.ftl
@@ -0,0 +1,232 @@
+<!--
+* 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.
+-->
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+        "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+
+<head>
+    <meta http-equiv="Content-Type" content="Multipart/Alternative; charset=UTF-8"/>
+    <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
+</head>
+
+<style>
+    html {
+        font-size: 10px;
+    }
+
+    * {
+        box-sizing: border-box;
+    }
+
+    a:hover,
+    a:focus {
+        color: #23527c;
+        text-decoration: underline;
+    }
+
+    a:focus {
+        outline: 5px auto -webkit-focus-ring-color;
+        outline-offset: -2px;
+    }
+</style>
+
+<body>
+<div style="margin-left:5%;margin-right:5%;font-family: 'Trebuchet MS ', Arial, Helvetica, sans-serif;">
+        <span style="line-height: 1.1;font-size: 18px;">
+    <p style="text-align:left;">Dear Kylin5 user,</p>
+    <p>Kylin fails to update the job output due to some hdfs issue. Need to ask Hadoop Service Team for help as soon as possible.</p>
+</span>
+    <hr style="margin-top: 20px;
+            margin-bottom: 20px;
+            border: 0;
+            border-top: 1px solid #eee;">
+    <h1>
+            <span style="display: inline;
+                    background-color: #d9534f;
+                    color: #fff;
+                    line-height: 1;
+                    font-weight: 700;
+                    font-size:36px;
+                    text-align: center;">&nbsp;Metadata Persist Error&nbsp;</span>
+    </h1>
+    <hr style="margin-top: 20px;
+            margin-bottom: 20px;
+            border: 0;
+            border-top: 1px solid #eee;">
+    <table cellpadding="0" cellspacing="0" width="100%" style="border-collapse: collapse;border:1px solid #ebccd1;">
+
+        <tr>
+
+            <td style="padding: 10px 15px;
+                    background-color: #f2dede;
+                    border:1px solid #ebccd1;">
+                <h4 style="margin-top: 0;
+                        margin-bottom: 0;
+                        font-size: 16px;
+                        color: inherit;
+                        color: #a94442;
+                        font-family: 'Trebuchet MS ', Arial, Helvetica, sans-serif;">
+                ${job_name}
+                </h4>
+            </td>
+        </tr>
+
+        <tr>
+
+            <td style="padding: 10px 15px;
+                    background-color: #f2dede;
+                    border:1px solid #ebccd1;">
+                <h4 style="margin-top: 0;
+                        margin-bottom: 0;
+                        font-size: 16px;
+                        color: inherit;
+                        color: #a94442;
+                        font-family: 'Trebuchet MS ', Arial, Helvetica, sans-serif;">
+                ${env_name}
+                </h4>
+            </td>
+        </tr>
+
+        <tr>
+
+            <td style="padding: 15px;">
+                <table cellpadding="0" cellspacing="0" width="100%"
+                       style="margin-bottom: 20px;border:1 solid #ddd;border-collapse: collapse;font-family: 'Trebuchet MS ', Arial, Helvetica, sans-serif;">
+                    <tr>
+                        <th width="30%" style="padding: 8px;
+                                            line-height: 1.42857143;
+                                            vertical-align: top;
+                                            border: 1px solid #ddd;
+                                            text-align: left;
+                                            font-size: medium;
+                                            font-style: normal;">Submitter
+                        </th>
+                        <td style="padding: 8px;
+                                line-height: 1.42857143;
+                                vertical-align: top;
+                                border: 1px solid #ddd;
+                                font-size: medium;
+                                font-style: normal;">
+                        ${submitter}
+                        </td>
+                    </tr>
+                    <tr>
+                        <th width="30%" style="padding: 8px;
+                                            line-height: 1.42857143;
+                                            vertical-align: top;
+                                            border: 1px solid #ddd;
+                                            text-align: left;
+                                            font-size: medium;
+                                            font-style: normal;">Job Engine
+                        </th>
+                        <td style="padding: 8px;
+                                line-height: 1.42857143;
+                                vertical-align: top;
+                                border: 1px solid #ddd;
+                                font-size: medium;
+                                font-style: normal;">
+                        ${job_engine}
+                        </td>
+                    </tr>
+                    <tr>
+                        <th width="30%" style="padding: 8px;
+                                            line-height: 1.42857143;
+                                            vertical-align: top;
+                                            border: 1px solid #ddd;
+                                            text-align: left;
+                                            font-size: medium;
+                                            font-style: normal;">Project
+                        </th>
+                        <td style="padding: 8px;
+                                line-height: 1.42857143;
+                                vertical-align: top;
+                                border: 1px solid #ddd;
+                                font-size: medium;
+                                font-style: normal;">
+                            ${project_name}
+                        </td>
+                    </tr>
+                    <tr>
+                        <th width="30%" style="padding: 8px;
+                                            line-height: 1.42857143;
+                                            vertical-align: top;
+                                            border: 1px solid #ddd;
+                                            text-align: left;
+                                            font-size: medium;
+                                            font-style: normal;">Model Name
+                        </th>
+                        <td style="padding: 8px;
+                                line-height: 1.42857143;
+                                vertical-align: top;
+                                border: 1px solid #ddd;
+                                font-size: medium;
+                                font-style: normal;">
+                            ${model_name}
+                        </td>
+                    </tr>
+                </table>
+            </td>
+        </tr>
+
+        <tr>
+
+            <td style="padding: 10px 15px;
+                    background-color: #f2dede;
+                    border:1px solid #ebccd1;">
+                <h4 style="margin-top: 0;
+                        margin-bottom: 0;
+                        font-size: 16px;
+                        color: inherit;
+                        color: #a94442;
+                        font-family: 'Trebuchet MS ', Arial, Helvetica, sans-serif;">
+                    Logs
+                </h4>
+            </td>
+        </tr>
+        <tr>
+
+            <td style="padding: 15px;">
+                <table cellpadding="0" cellspacing="0" width="100%"
+                       style="margin-bottom: 20px;border:1 solid #ddd;border-collapse: collapse;font-family: 'Trebuchet MS ', Arial, Helvetica, sans-serif;">
+                    <tr>
+                        <td style="padding: 8px;
+                                line-height: 1.42857143;
+                                vertical-align: top;
+                                border: 1px solid #ddd;
+                                font-size: medium;
+                                font-style: normal;">
+                            <pre style="white-space: pre-wrap;">${error_log}</pre>
+                        </td>
+                    </tr>
+                </table>
+            </td>
+        </tr>
+    </table>
+    <hr style="margin-top: 20px;
+    margin-bottom: 20px;
+    border: 0;
+    border-top: 1px solid #eee;">
+    <h4 style="font-weight: 500;
+            line-height: 1.1;font-size:18px;">
+        <p>Best Wishes!</p>
+        <p style="margin: 0 0 10px;"><b>Kylin Team</b></p>
+    </h4>
+</div>
+</body>
+
+</html>
\ No newline at end of file
diff --git a/src/core-job/src/main/resources/mail_templates/OVER_CAPACITY_THRESHOLD.ftl b/src/core-job/src/main/resources/mail_templates/OVER_CAPACITY_THRESHOLD.ftl
new file mode 100644
index 0000000000..5114f1f3e8
--- /dev/null
+++ b/src/core-job/src/main/resources/mail_templates/OVER_CAPACITY_THRESHOLD.ftl
@@ -0,0 +1,200 @@
+<!--
+* 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.
+-->
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+        "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+
+<head>
+    <meta http-equiv="Content-Type" content="Multipart/Alternative; charset=UTF-8"/>
+    <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
+</head>
+
+<style>
+    html {
+        font-size: 10px;
+    }
+
+    * {
+        box-sizing: border-box;
+    }
+
+    a:hover,
+    a:focus {
+        color: #23527c;
+        text-decoration: underline;
+    }
+
+    a:focus {
+        outline: 5px auto -webkit-focus-ring-color;
+        outline-offset: -2px;
+    }
+</style>
+
+<body>
+<div style="margin-left:5%;margin-right:5%;font-family: 'Trebuchet MS ', Arial, Helvetica, sans-serif;">
+<span style="
+    line-height: 1.1;font-size: 18px;">
+    <p style="text-align:left;">Dear Kylin5 user,</p>
+    <p>The amount of data volume used has reached threshold of the license’s limit</p>
+    <p>To ensure the availability of your service, please contact Kylin team to get a new license, or try deleting some segments.</p>
+</span>
+    <hr style="margin-top: 20px;
+    margin-bottom: 20px;
+    border: 0;
+    border-top: 1px solid #eee;">
+    <h1>
+    <span style="display: inline;
+            background-color: #607D8B;
+            color: #fff;
+            line-height: 1;
+            font-weight: 700;
+            font-size:36px;
+            text-align: center;">&nbsp;Over Capacity Threshold&nbsp;</span>
+    </h1>
+    <hr style="margin-top: 20px;
+            margin-bottom: 20px;
+            border: 0;
+            border-top: 1px solid #eee;">
+    <table cellpadding="0" cellspacing="0" width="100%" style="border-collapse: collapse;border:1px solid #f8f8f8">
+
+        <tr>
+
+            <td style="padding: 10px 15px;
+                    background-color: #eeeeee;
+                    border:1px solid #f8f8f8;">
+                <h4 style="margin-top: 0;
+                        margin-bottom: 0;
+                        font-size: 16px;
+                        color: inherit;
+                        color: #404040;
+                        font-family: 'Trebuchet MS ', Arial, Helvetica, sans-serif;">
+                    CHECK USAGE
+                </h4>
+            </td>
+        </tr>
+
+        <tr>
+
+            <td style="padding: 10px 15px;
+                    background-color: #eeeeee;
+                    border:1px solid #f8f8f8;">
+                <h4 style="margin-top: 0;
+                        margin-bottom: 0;
+                        font-size: 16px;
+                        color: inherit;
+                        color: #404040;
+                        font-family: 'Trebuchet MS ', Arial, Helvetica, sans-serif;">
+                    ${env_name}
+                </h4>
+            </td>
+        </tr>
+
+        <tr>
+
+            <td style="padding: 15px;">
+                <table cellpadding="0" cellspacing="0" width="100%"
+                       style="margin-bottom: 20px;border:1 solid #ddd;border-collapse: collapse;font-family: 'Trebuchet MS ', Arial, Helvetica, sans-serif;">
+                    <tr>
+                        <th width="30%" style="padding: 8px;
+                                            line-height: 1.42857143;
+                                            vertical-align: top;
+                                            border: 1px solid #ddd;
+                                            text-align: left;
+                                            font-size: medium;
+                                            font-style: normal;">Resource Name
+                        </th>
+                        <td style="padding: 8px;
+                                line-height: 1.42857143;
+                                vertical-align: top;
+                                border: 1px solid #ddd;
+                                font-size: medium;
+                                font-style: normal;">
+                            ${resource_name}
+                        </td>
+                    </tr>
+                    <tr>
+                        <th width="30%" style="padding: 8px;
+                                            line-height: 1.42857143;
+                                            vertical-align: top;
+                                            border: 1px solid #ddd;
+                                            text-align: left;
+                                            font-size: medium;
+                                            font-style: normal;">Capacity Threshold
+                        </th>
+                        <td style="padding: 8px;
+                                line-height: 1.42857143;
+                                vertical-align: top;
+                                border: 1px solid #ddd;
+                                font-size: medium;
+                                font-style: normal;">
+                        ${capacity_threshold}%
+                        </td>
+                    </tr>
+                    <tr>
+                        <th width="30%" style="padding: 8px;
+                                            line-height: 1.42857143;
+                                            vertical-align: top;
+                                            border: 1px solid #ddd;
+                                            text-align: left;
+                                            font-size: medium;
+                                            font-style: normal;">Volume Total
+                        </th>
+                        <td style="padding: 8px;
+                                line-height: 1.42857143;
+                                vertical-align: top;
+                                border: 1px solid #ddd;
+                                font-size: medium;
+                                font-style: normal;">
+                        ${volume_total}
+                        </td>
+                    </tr>
+                    <tr>
+                        <th width="30%" style="padding: 8px;
+                                            line-height: 1.42857143;
+                                            vertical-align: top;
+                                            border: 1px solid #ddd;
+                                            text-align: left;
+                                            font-size: medium;
+                                            font-style: normal;">Volume Used
+                        </th>
+                        <td style="padding: 8px;
+                                line-height: 1.42857143;
+                                vertical-align: top;
+                                border: 1px solid #ddd;
+                                font-size: medium;
+                                font-style: normal;">
+                        ${volume_used}
+                        </td>
+                    </tr>
+                </table>
+            </td>
+        </tr>
+    </table>
+    <hr style="margin-top: 20px;
+    margin-bottom: 20px;
+    border: 0;
+    border-top: 1px solid #eee;">
+    <h4 style="font-weight: 500;
+    line-height: 1.1;font-size:18px;">
+        <p>Best Wishes!</p>
+        <p style="margin: 0 0 10px;"><b>Kylin Team</b></p>
+    </h4>
+</div>
+</body>
+
+</html>
\ No newline at end of file
diff --git a/src/core-job/src/main/resources/mail_templates/SOURCE_RECORDS_CHANGE.ftl b/src/core-job/src/main/resources/mail_templates/SOURCE_RECORDS_CHANGE.ftl
new file mode 100644
index 0000000000..405fb478f2
--- /dev/null
+++ b/src/core-job/src/main/resources/mail_templates/SOURCE_RECORDS_CHANGE.ftl
@@ -0,0 +1,205 @@
+<!--
+* 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.
+-->
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+        "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+
+<head>
+    <meta http-equiv="Content-Type" content="Multipart/Alternative; charset=UTF-8"/>
+    <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
+</head>
+
+<style>
+    html {
+        font-size: 10px;
+    }
+
+    * {
+        box-sizing: border-box;
+    }
+
+    a:hover,
+    a:focus {
+        color: #23527c;
+        text-decoration: underline;
+    }
+
+    a:focus {
+        outline: 5px auto -webkit-focus-ring-color;
+        outline-offset: -2px;
+    }
+</style>
+
+<body>
+<div style="margin-left:5%;margin-right:5%;font-family: 'Trebuchet MS ', Arial, Helvetica, sans-serif;">
+<span style="
+    line-height: 1.1;font-size: 18px;">
+    <p style="text-align:left;">Dear Kylin5 user,</p>
+    <p>We found some source records updated in your Kylin system. You can reload updated records by following instructions.</p>
+    <p>Ignore this issue may cause query result inconsistency over different indexes.</p>
+    <#if key = ${start_time} == 0>
+        <p>You may refresh the full build segment to apply source records change.</p>
+    <#else>
+        <p>You may refresh the segment from ${start_time} to ${end_time} to apply source records change.</p>
+    </#if>
+</span>
+    <hr style="margin-top: 20px;
+    margin-bottom: 20px;
+    border: 0;
+    border-top: 1px solid #eee;">
+    <h1>
+    <span style="display: inline;
+            background-color: #607D8B;
+            color: #fff;
+            line-height: 1;
+            font-weight: 700;
+            font-size:36px;
+            text-align: center;">&nbsp;SOURCE RECORDS CHANGED&nbsp;</span>
+    </h1>
+    <hr style="margin-top: 20px;
+            margin-bottom: 20px;
+            border: 0;
+            border-top: 1px solid #eee;">
+    <table cellpadding="0" cellspacing="0" width="100%" style="border-collapse: collapse;border:1px solid #f8f8f8">
+
+        <tr>
+
+            <td style="padding: 10px 15px;
+                    background-color: #eeeeee;
+                    border:1px solid #f8f8f8;">
+                <h4 style="margin-top: 0;
+                        margin-bottom: 0;
+                        font-size: 16px;
+                        color: inherit;
+                        color: #404040;
+                        font-family: 'Trebuchet MS ', Arial, Helvetica, sans-serif;">
+                ${job_name}
+                </h4>
+            </td>
+        </tr>
+
+        <tr>
+
+            <td style="padding: 10px 15px;
+                    background-color: #eeeeee;
+                    border:1px solid #f8f8f8;">
+                <h4 style="margin-top: 0;
+                        margin-bottom: 0;
+                        font-size: 16px;
+                        color: inherit;
+                        color: #404040;
+                        font-family: 'Trebuchet MS ', Arial, Helvetica, sans-serif;">
+                ${env_name}
+                </h4>
+            </td>
+        </tr>
+
+        <tr>
+
+            <td style="padding: 15px;">
+                <table cellpadding="0" cellspacing="0" width="100%"
+                       style="margin-bottom: 20px;border:1 solid #ddd;border-collapse: collapse;font-family: 'Trebuchet MS ', Arial, Helvetica, sans-serif;">
+                    <tr>
+                        <th width="30%" style="padding: 8px;
+                                            line-height: 1.42857143;
+                                            vertical-align: top;
+                                            border: 1px solid #ddd;
+                                            text-align: left;
+                                            font-size: medium;
+                                            font-style: normal;">Submitter
+                        </th>
+                        <td style="padding: 8px;
+                                line-height: 1.42857143;
+                                vertical-align: top;
+                                border: 1px solid #ddd;
+                                font-size: medium;
+                                font-style: normal;">
+                        ${submitter}
+                        </td>
+                    </tr>
+                    <tr>
+                        <th width="30%" style="padding: 8px;
+                                            line-height: 1.42857143;
+                                            vertical-align: top;
+                                            border: 1px solid #ddd;
+                                            text-align: left;
+                                            font-size: medium;
+                                            font-style: normal;">Job Engine
+                        </th>
+                        <td style="padding: 8px;
+                                line-height: 1.42857143;
+                                vertical-align: top;
+                                border: 1px solid #ddd;
+                                font-size: medium;
+                                font-style: normal;">
+                        ${job_engine}
+                        </td>
+                    </tr>
+                    <tr>
+                        <th width="30%" style="padding: 8px;
+                                            line-height: 1.42857143;
+                                            vertical-align: top;
+                                            border: 1px solid #ddd;
+                                            text-align: left;
+                                            font-size: medium;
+                                            font-style: normal;">Project
+                        </th>
+                        <td style="padding: 8px;
+                                line-height: 1.42857143;
+                                vertical-align: top;
+                                border: 1px solid #ddd;
+                                font-size: medium;
+                                font-style: normal;">
+                        ${project_name}
+                        </td>
+                    </tr>
+                    <tr>
+                        <th width="30%" style="padding: 8px;
+                                            line-height: 1.42857143;
+                                            vertical-align: top;
+                                            border: 1px solid #ddd;
+                                            text-align: left;
+                                            font-size: medium;
+                                            font-style: normal;">Model Name
+                        </th>
+                        <td style="padding: 8px;
+                                line-height: 1.42857143;
+                                vertical-align: top;
+                                border: 1px solid #ddd;
+                                font-size: medium;
+                                font-style: normal;">
+                        ${model_name}
+                        </td>
+                    </tr>
+                </table>
+            </td>
+        </tr>
+    </table>
+    <hr style="margin-top: 20px;
+    margin-bottom: 20px;
+    border: 0;
+    border-top: 1px solid #eee;">
+    <h4 style="font-weight: 500;
+    line-height: 1.1;font-size:18px;">
+        <p>Best Wishes!</p>
+        <p style="margin: 0 0 10px;"><b>Kylin Team</b></p>
+    </h4>
+</div>
+</body>
+
+</html>
\ No newline at end of file
diff --git a/src/core-job/src/test/java/org/apache/kylin/job/execution/ErrorTestExecutable.java b/src/core-job/src/test/java/org/apache/kylin/job/execution/ErrorTestExecutable.java
index b0544d5974..0305fa5c2a 100644
--- a/src/core-job/src/test/java/org/apache/kylin/job/execution/ErrorTestExecutable.java
+++ b/src/core-job/src/test/java/org/apache/kylin/job/execution/ErrorTestExecutable.java
@@ -22,6 +22,7 @@ import java.util.HashMap;
 import java.util.Map;
 
 import org.apache.kylin.job.exception.ExecuteException;
+import org.apache.kylin.job.exception.PersistentException;
 
 /**
  */
@@ -36,7 +37,7 @@ public class ErrorTestExecutable extends BaseTestExecutable {
     }
 
     @Override
-    public ExecuteResult doWork(ExecutableContext context) throws ExecuteException {
+    protected ExecuteResult doWork(ExecutableContext context) throws ExecuteException, PersistentException {
         Map<String, String> info = new HashMap<String, String>() {
             {
                 put("runningStatus", "inRunning");
diff --git a/src/core-job/src/test/java/org/apache/kylin/job/execution/NExecutableManagerTest.java b/src/core-job/src/test/java/org/apache/kylin/job/execution/NExecutableManagerTest.java
index 519339262a..8da46311dc 100644
--- a/src/core-job/src/test/java/org/apache/kylin/job/execution/NExecutableManagerTest.java
+++ b/src/core-job/src/test/java/org/apache/kylin/job/execution/NExecutableManagerTest.java
@@ -49,6 +49,8 @@ import org.apache.hadoop.fs.Path;
 import org.apache.kylin.common.KylinConfig;
 import org.apache.kylin.common.exception.KylinException;
 import org.apache.kylin.common.util.HadoopUtil;
+import org.apache.kylin.common.util.MailHelper;
+import org.apache.kylin.common.util.Pair;
 import org.apache.kylin.job.constant.ExecutableConstants;
 import org.apache.kylin.job.constant.JobIssueEnum;
 import org.apache.kylin.job.dao.NExecutableDao;
@@ -599,18 +601,31 @@ public class NExecutableManagerTest extends NLocalFileMetadataTestCase {
         job.setParam(NBatchConstants.P_DATA_RANGE_START, SegmentRange.dateToLong(start) + "");
         job.setParam(NBatchConstants.P_DATA_RANGE_END, SegmentRange.dateToLong(end) + "");
         job.setTargetSubject("89af4ee2-2cdb-4b07-b39e-4c29856309aa");
-        EmailNotificationContent content = EmailNotificationContent.createContent(JobIssueEnum.JOB_ERROR, job);
-        Assert.assertTrue(content.getEmailTitle().contains(JobIssueEnum.JOB_ERROR.getDisplayName()));
-        Assert.assertTrue(!content.getEmailBody().contains("$"));
-        Assert.assertTrue(content.getEmailBody().contains(project));
-        Assert.assertTrue(content.getEmailBody().contains(job.getName()));
-
-        content = EmailNotificationContent.createContent(JobIssueEnum.LOAD_EMPTY_DATA, job);
-        Assert.assertTrue(content.getEmailBody().contains(job.getTargetModelAlias()));
+        Pair<String, String> mail = EmailNotificationContent.createContent(ExecutableState.ERROR, job, job.getTasks());
+        assert mail != null;
+        Assert.assertTrue(mail.getFirst().contains(ExecutableState.ERROR.toString()));
+        Assert.assertTrue(mail.getSecond().contains("Job Error Details"));
+        Assert.assertTrue(mail.getSecond().contains(project));
+        Assert.assertTrue(mail.getSecond().contains(job.getName()));
+
+        mail = EmailNotificationContent.createContent(JobIssueEnum.LOAD_EMPTY_DATA, job);
+        assert mail != null;
+        Assert.assertTrue(mail.getSecond().contains(job.getTargetModelAlias()));
         Assert.assertEquals("89af4ee2-2cdb-4b07-b39e-4c29856309aa", job.getTargetModelId());
-        content = EmailNotificationContent.createContent(JobIssueEnum.SOURCE_RECORDS_CHANGE, job);
-        Assert.assertTrue(content.getEmailBody().contains(start));
-        Assert.assertTrue(content.getEmailBody().contains(end));
+
+        mail = EmailNotificationContent.createContent(JobIssueEnum.SOURCE_RECORDS_CHANGE, job);
+        assert mail != null;
+        Assert.assertTrue(mail.getSecond().contains(start));
+        Assert.assertTrue(mail.getSecond().contains(end));
+
+        Throwable exception = new Throwable("metadata persist failed!");
+        mail = EmailNotificationContent.createMetadataPersistExceptionContent(exception, job);
+        Assert.assertTrue(mail.getSecond().contains("Hadoop Service"));
+        Assert.assertTrue(mail.getSecond().contains(job.getName()));
+
+        mail = MailHelper.creatContentForCapacityUsage(1000000L, 10000L, project);
+        Assert.assertTrue(mail.getSecond().contains("capacity_threshold"));
+        Assert.assertTrue(mail.getSecond().contains("deleting some segments"));
 
     }
 
diff --git a/src/core-job/src/test/java/org/apache/kylin/job/execution/SucceedTestExecutable.java b/src/core-job/src/test/java/org/apache/kylin/job/execution/SucceedTestExecutable.java
index 5d49a87de7..b7b7a9d45d 100644
--- a/src/core-job/src/test/java/org/apache/kylin/job/execution/SucceedTestExecutable.java
+++ b/src/core-job/src/test/java/org/apache/kylin/job/execution/SucceedTestExecutable.java
@@ -18,6 +18,9 @@
 
 package org.apache.kylin.job.execution;
 
+import org.apache.kylin.job.exception.ExecuteException;
+import org.apache.kylin.job.exception.PersistentException;
+
 import java.util.HashMap;
 import java.util.Map;
 
@@ -34,7 +37,7 @@ public class SucceedTestExecutable extends BaseTestExecutable {
     }
 
     @Override
-    public ExecuteResult doWork(ExecutableContext context) {
+    protected ExecuteResult doWork(ExecutableContext context) throws PersistentException, ExecuteException {
         Map<String, String> info = new HashMap<String, String>() {
             {
                 put("runningStatus", "inRunning");
diff --git a/src/core-job/src/test/java/org/apache/kylin/job/impl/threadpool/NDefaultSchedulerTest.java b/src/core-job/src/test/java/org/apache/kylin/job/impl/threadpool/NDefaultSchedulerTest.java
index 77990e29ca..353c9c6de2 100644
--- a/src/core-job/src/test/java/org/apache/kylin/job/impl/threadpool/NDefaultSchedulerTest.java
+++ b/src/core-job/src/test/java/org/apache/kylin/job/impl/threadpool/NDefaultSchedulerTest.java
@@ -49,6 +49,7 @@ import org.apache.kylin.job.dao.ExecutablePO;
 import org.apache.kylin.job.engine.JobEngineConfig;
 import org.apache.kylin.job.exception.JobStoppedException;
 import org.apache.kylin.job.exception.JobStoppedNonVoluntarilyException;
+import org.apache.kylin.job.exception.PersistentException;
 import org.apache.kylin.job.execution.AbstractExecutable;
 import org.apache.kylin.job.execution.BaseTestExecutable;
 import org.apache.kylin.job.execution.DefaultExecutable;
@@ -156,7 +157,7 @@ public class NDefaultSchedulerTest extends BaseSchedulerTest {
     }
 
     @Test
-    public void testGetOutputFromHDFSByJobId() throws IOException {
+    public void testGetOutputFromHDFSByJobId() throws IOException, PersistentException {
         File file = temporaryFolder.newFile("execute_output.json." + System.currentTimeMillis() + ".log");
         for (int i = 0; i < 200; i++) {
             Files.write(file.toPath(), String.format(Locale.ROOT, "lines: %s\n", i).getBytes(Charset.defaultCharset()),
diff --git a/src/core-metadata/src/main/java/org/apache/kylin/metadata/project/ProjectInstance.java b/src/core-metadata/src/main/java/org/apache/kylin/metadata/project/ProjectInstance.java
index cf4136d6d5..ed9f738cd3 100644
--- a/src/core-metadata/src/main/java/org/apache/kylin/metadata/project/ProjectInstance.java
+++ b/src/core-metadata/src/main/java/org/apache/kylin/metadata/project/ProjectInstance.java
@@ -19,6 +19,7 @@
 package org.apache.kylin.metadata.project;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
@@ -28,6 +29,7 @@ import java.util.TreeSet;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.kylin.common.KylinConfig;
 import org.apache.kylin.common.KylinConfigExt;
+import org.apache.kylin.common.constant.NonCustomProjectLevelConfig;
 import org.apache.kylin.common.persistence.ResourceStore;
 import org.apache.kylin.common.persistence.RootPersistentEntity;
 import org.apache.kylin.common.util.StringUtil;
@@ -364,4 +366,12 @@ public class ProjectInstance extends RootPersistentEntity implements ISourceAwar
         return ResourceStore.getKylinMetaStore(this.config);
     }
 
+    public List<String> getEmailUsers() {
+        String users = this.getOverrideKylinProps().get(NonCustomProjectLevelConfig.NOTIFICATION_USER_EMAILS.getValue());
+        if(users != null) {
+            return Arrays.asList(StringUtil.split(users, ","));
+        }
+        return new ArrayList<>();
+    }
+
 }
diff --git a/src/core-metadata/src/main/java/org/apache/kylin/metadata/sourceusage/SourceUsageManager.java b/src/core-metadata/src/main/java/org/apache/kylin/metadata/sourceusage/SourceUsageManager.java
index c9c3be30fb..882a085f6d 100644
--- a/src/core-metadata/src/main/java/org/apache/kylin/metadata/sourceusage/SourceUsageManager.java
+++ b/src/core-metadata/src/main/java/org/apache/kylin/metadata/sourceusage/SourceUsageManager.java
@@ -45,7 +45,7 @@ import org.apache.kylin.common.msg.MsgPicker;
 import org.apache.kylin.common.persistence.ResourceStore;
 import org.apache.kylin.common.util.DateFormat;
 import org.apache.kylin.common.util.JsonUtil;
-import org.apache.kylin.common.util.MailHelper;
+import org.apache.kylin.common.util.MailTemplateProvider;
 import org.apache.kylin.metadata.MetadataConstants;
 import org.apache.kylin.metadata.cachesync.CachedCrudAssist;
 import org.apache.kylin.metadata.model.SegmentStatusEnum;
@@ -233,8 +233,8 @@ public class SourceUsageManager {
                         logger.info("Capacity usage is less than threshold, enable notification");
                     } else if (copyForWrite.isCapacityNotification() && config.isOverCapacityNotificationEnabled()
                             && isOverCapacityThreshold(copyForWrite)) {
-                        if (MailHelper.notifyUserForOverCapacity(copyForWrite.getLicenseCapacity(),
-                                copyForWrite.getCurrentCapacity())) {
+                        if (MailTemplateProvider.notifyUserForOverCapacity(copyForWrite.getLicenseCapacity(),
+                                copyForWrite.getCurrentCapacity(), usageRecord.resourceName())) {
                             copyForWrite.setCapacityNotification(false);
                             logger.info("Capacity usage is more than threshold, disable notification");
                         } else {
diff --git a/src/examples/test_case_data/localmeta/kylin.properties b/src/examples/test_case_data/localmeta/kylin.properties
index a8e324a447..239cfe4b0c 100755
--- a/src/examples/test_case_data/localmeta/kylin.properties
+++ b/src/examples/test_case_data/localmeta/kylin.properties
@@ -107,14 +107,25 @@ kylin.security.saml.context-server-port=443
 kylin.security.saml.context-path=/kylin
 
 ### MAIL ###
-# If true, will send email notification;
-#kylin.job.notification-enabled=true
+# If true, will send email notification: succeed, error, discard;
+kylin.job.notification-enabled=false
 #kylin.job.notification-mail-enable-starttls=true
-#kylin.job.notification-mail-host=smtp.office365.com
+kylin.job.notification-mail-host=smtp.office365.com
 #kylin.job.notification-mail-port=587
-#kylin.job.notification-mail-username=kylin@example.com
-#kylin.job.notification-mail-password=mypassword
-#kylin.job.notification-mail-sender=kylin@example.com
+kylin.job.notification-mail-username=kylin@example.com
+kylin.job.notification-mail-password=mypassword
+kylin.job.notification-mail-sender=kylin@example.com
+kylin.job.notification-admin-emails=kylin@example.com
+kylin.job.notification-enable-states=Succeed,Error,Discard
+#kylin.capacity.notification-emails=
+#notify empty data
+#kylin.job.notification-on-empty-data-load=false
+#notify source records change
+#kylin.job.notification-on-source-records-change=false
+#notify metadata persist
+#kylin.job.notification-on-metadata-persist=false
+#capacity threshold only invoked in test case
+#kylin.capacity.notification-enabled=false
 
 
 ### OTHER ###
diff --git a/src/job-service/src/test/java/org/apache/kylin/rest/service/JobServiceTest.java b/src/job-service/src/test/java/org/apache/kylin/rest/service/JobServiceTest.java
index 71266afd4c..0824ecb996 100644
--- a/src/job-service/src/test/java/org/apache/kylin/rest/service/JobServiceTest.java
+++ b/src/job-service/src/test/java/org/apache/kylin/rest/service/JobServiceTest.java
@@ -79,6 +79,7 @@ import org.apache.kylin.job.constant.JobStatusEnum;
 import org.apache.kylin.job.dao.ExecutableOutputPO;
 import org.apache.kylin.job.dao.ExecutablePO;
 import org.apache.kylin.job.dao.NExecutableDao;
+import org.apache.kylin.job.exception.PersistentException;
 import org.apache.kylin.job.execution.AbstractExecutable;
 import org.apache.kylin.job.execution.BaseTestExecutable;
 import org.apache.kylin.job.execution.ChainedExecutable;
@@ -1332,7 +1333,7 @@ public class JobServiceTest extends NLocalFileMetadataTestCase {
     }
 
     @Test
-    public void testGetJobOutput() {
+    public void testGetJobOutput() throws PersistentException {
         NExecutableManager manager = NExecutableManager.getInstance(jobService.getConfig(), "default");
         ExecutableOutputPO executableOutputPO = new ExecutableOutputPO();
         executableOutputPO.setStatus("SUCCEED");
@@ -1345,7 +1346,7 @@ public class JobServiceTest extends NLocalFileMetadataTestCase {
     }
 
     @Test
-    public void testGetAllJobOutput() throws IOException {
+    public void testGetAllJobOutput() throws IOException, PersistentException {
         File file = temporaryFolder.newFile("execute_output.json." + System.currentTimeMillis() + ".log");
         for (int i = 0; i < 200; i++) {
             Files.write(file.toPath(), String.format(Locale.ROOT, "lines: %s\n", i).getBytes(Charset.defaultCharset()),
@@ -1839,7 +1840,7 @@ public class JobServiceTest extends NLocalFileMetadataTestCase {
     }
 
     @Test
-    public void testGetStepOutput() {
+    public void testGetStepOutput() throws PersistentException {
         String jobId = "e1ad7bb0-522e-456a-859d-2eab1df448de";
         NExecutableManager manager = NExecutableManager.getInstance(jobService.getConfig(), "default");
         ExecutableOutputPO executableOutputPO = new ExecutableOutputPO();
diff --git a/src/metadata-server/src/main/java/org/apache/kylin/rest/controller/NProjectController.java b/src/metadata-server/src/main/java/org/apache/kylin/rest/controller/NProjectController.java
index abe3dd18cc..5827b5d6fe 100644
--- a/src/metadata-server/src/main/java/org/apache/kylin/rest/controller/NProjectController.java
+++ b/src/metadata-server/src/main/java/org/apache/kylin/rest/controller/NProjectController.java
@@ -285,9 +285,12 @@ public class NProjectController extends NBasicController {
             @RequestBody JobNotificationConfigRequest jobNotificationConfigRequest) {
         checkRequiredArg("data_load_empty_notification_enabled",
                 jobNotificationConfigRequest.getDataLoadEmptyNotificationEnabled());
-        checkRequiredArg("job_error_notification_enabled",
-                jobNotificationConfigRequest.getJobErrorNotificationEnabled());
-        checkRequiredArg("job_notification_emails", jobNotificationConfigRequest.getJobNotificationEmails());
+        checkRequiredArg("job_notification_states",
+                jobNotificationConfigRequest.getJobNotificationStates());
+        checkRequiredArg("job_notification_emails",
+                jobNotificationConfigRequest.getJobNotificationEmails());
+        checkRequiredArg("metadata_persist_notification_enabled",
+                jobNotificationConfigRequest.getMetadataPersistNotificationEnabled());
         projectService.updateJobNotificationConfig(project, jobNotificationConfigRequest);
         return new EnvelopeResponse<>(KylinException.CODE_SUCCESS, "", "");
     }
diff --git a/src/metadata-server/src/test/java/org/apache/kylin/rest/controller/NProjectControllerTest.java b/src/metadata-server/src/test/java/org/apache/kylin/rest/controller/NProjectControllerTest.java
index 23aff14ac5..442ef65140 100644
--- a/src/metadata-server/src/test/java/org/apache/kylin/rest/controller/NProjectControllerTest.java
+++ b/src/metadata-server/src/test/java/org/apache/kylin/rest/controller/NProjectControllerTest.java
@@ -243,7 +243,7 @@ public class NProjectControllerTest extends NLocalFileMetadataTestCase {
     public void testUpdateJobNotificationConfig() throws Exception {
         val request = new JobNotificationConfigRequest();
 
-        request.setJobErrorNotificationEnabled(true);
+        request.setJobNotificationStates(Arrays.asList("Succeed", "Error", "Discard"));
         request.setDataLoadEmptyNotificationEnabled(true);
         request.setJobNotificationEmails(Arrays.asList("fff@g.com"));
 
diff --git a/src/modeling-service/src/test/java/org/apache/kylin/rest/service/ProjectServiceTest.java b/src/modeling-service/src/test/java/org/apache/kylin/rest/service/ProjectServiceTest.java
index 61720f727c..549384fa04 100644
--- a/src/modeling-service/src/test/java/org/apache/kylin/rest/service/ProjectServiceTest.java
+++ b/src/modeling-service/src/test/java/org/apache/kylin/rest/service/ProjectServiceTest.java
@@ -367,13 +367,14 @@ public class ProjectServiceTest extends NLocalFileMetadataTestCase {
         var response = projectService.getProjectConfig(project);
         val jobNotificationConfigRequest = new JobNotificationConfigRequest();
         jobNotificationConfigRequest.setDataLoadEmptyNotificationEnabled(false);
-        jobNotificationConfigRequest.setJobErrorNotificationEnabled(false);
+        jobNotificationConfigRequest.setJobNotificationEmails(
+                Lists.newArrayList("Succeed", "Error", "Discard"));
         jobNotificationConfigRequest.setJobNotificationEmails(
                 Lists.newArrayList("user1@kyligence.io", "user2@kyligence.io", "user2@kyligence.io"));
         projectService.updateJobNotificationConfig(project, jobNotificationConfigRequest);
         response = projectService.getProjectConfig(project);
         Assert.assertEquals(2, response.getJobNotificationEmails().size());
-        Assert.assertFalse(response.isJobErrorNotificationEnabled());
+        Assert.assertEquals(3, response.getJobNotificationStates().size());
         Assert.assertFalse(response.isDataLoadEmptyNotificationEnabled());
 
         jobNotificationConfigRequest
@@ -770,7 +771,8 @@ public class ProjectServiceTest extends NLocalFileMetadataTestCase {
 
         val jobNotificationConfigRequest = new JobNotificationConfigRequest();
         jobNotificationConfigRequest.setDataLoadEmptyNotificationEnabled(true);
-        jobNotificationConfigRequest.setJobErrorNotificationEnabled(true);
+        jobNotificationConfigRequest.setJobNotificationEmails(
+                Lists.newArrayList("Succeed", "Error", "Discard"));
         jobNotificationConfigRequest.setJobNotificationEmails(
                 Lists.newArrayList("user1@kyligence.io", "user2@kyligence.io", "user2@kyligence.io"));
         projectService.updateJobNotificationConfig(PROJECT, jobNotificationConfigRequest);
@@ -788,12 +790,12 @@ public class ProjectServiceTest extends NLocalFileMetadataTestCase {
         updateProject();
         var response = projectService.getProjectConfig(PROJECT);
         Assert.assertEquals(2, response.getJobNotificationEmails().size());
-        Assert.assertTrue(response.isJobErrorNotificationEnabled());
+        Assert.assertEquals(3, response.getJobNotificationStates().size());
         Assert.assertTrue(response.isDataLoadEmptyNotificationEnabled());
 
         response = projectService.resetProjectConfig(PROJECT, "job_notification_config");
         Assert.assertEquals(0, response.getJobNotificationEmails().size());
-        Assert.assertFalse(response.isJobErrorNotificationEnabled());
+        Assert.assertEquals(0, response.getJobNotificationStates().size());
         Assert.assertFalse(response.isDataLoadEmptyNotificationEnabled());
 
         Assert.assertFalse(response.isFavoriteQueryTipsEnabled());


[kylin] 03/03: Update version from 5.0.0 to 5.0.0-alpha

Posted by xx...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

xxyu pushed a commit to branch kylin5.0.0-alpha-release
in repository https://gitbox.apache.org/repos/asf/kylin.git

commit e27552561e54e2ef2b34fedcc8148bc36c46cc46
Author: XiaoxiangYu <xx...@apache.org>
AuthorDate: Thu Jan 5 11:57:48 2023 +0800

    Update version from 5.0.0 to 5.0.0-alpha
---
 pom.xml                                             | 2 +-
 src/assembly/pom.xml                                | 2 +-
 src/common-booter/pom.xml                           | 2 +-
 src/common-server/pom.xml                           | 2 +-
 src/common-service/pom.xml                          | 2 +-
 src/core-common/pom.xml                             | 2 +-
 src/core-job/pom.xml                                | 2 +-
 src/core-metadata/pom.xml                           | 2 +-
 src/core-storage/pom.xml                            | 2 +-
 src/data-loading-booter/pom.xml                     | 2 +-
 src/data-loading-server/pom.xml                     | 2 +-
 src/data-loading-service/pom.xml                    | 2 +-
 src/datasource-sdk/pom.xml                          | 2 +-
 src/datasource-service/pom.xml                      | 2 +-
 src/integration-service/pom.xml                     | 2 +-
 src/jdbc/pom.xml                                    | 2 +-
 src/job-service/pom.xml                             | 2 +-
 src/kylin-it/pom.xml                                | 2 +-
 src/metadata-server/pom.xml                         | 2 +-
 src/modeling-service/pom.xml                        | 2 +-
 src/query-booter/pom.xml                            | 2 +-
 src/query-common/pom.xml                            | 2 +-
 src/query-server/pom.xml                            | 2 +-
 src/query-service/pom.xml                           | 2 +-
 src/query/pom.xml                                   | 2 +-
 src/second-storage/clickhouse-it/pom.xml            | 2 +-
 src/second-storage/clickhouse/pom.xml               | 2 +-
 src/second-storage/core-ui/pom.xml                  | 2 +-
 src/second-storage/core/pom.xml                     | 2 +-
 src/server/pom.xml                                  | 2 +-
 src/source-hive/pom.xml                             | 2 +-
 src/spark-project/engine-build-sdk/pom.xml          | 2 +-
 src/spark-project/engine-spark/pom.xml              | 2 +-
 src/spark-project/kylin-soft-affinity-cache/pom.xml | 2 +-
 src/spark-project/source-jdbc/pom.xml               | 2 +-
 src/spark-project/sparder/pom.xml                   | 2 +-
 src/spark-project/spark-common/pom.xml              | 2 +-
 src/spark-project/spark-ddl-plugin/pom.xml          | 2 +-
 src/spark-project/spark-it/pom.xml                  | 2 +-
 src/streaming-service/pom.xml                       | 2 +-
 src/streaming/pom.xml                               | 2 +-
 src/systools/pom.xml                                | 2 +-
 src/tool/pom.xml                                    | 2 +-
 43 files changed, 43 insertions(+), 43 deletions(-)

diff --git a/pom.xml b/pom.xml
index dfb1f7c464..2f0e486881 100644
--- a/pom.xml
+++ b/pom.xml
@@ -22,7 +22,7 @@
     <groupId>org.apache.kylin</groupId>
     <artifactId>kylin</artifactId>
     <packaging>pom</packaging>
-    <version>5.0.0-SNAPSHOT</version>
+    <version>5.0.0-alpha-SNAPSHOT</version>
 
     <name>Apache Kylin 5</name>
     <url>http://kylin.apache.org</url>
diff --git a/src/assembly/pom.xml b/src/assembly/pom.xml
index 2a56110bfc..af01697fd1 100644
--- a/src/assembly/pom.xml
+++ b/src/assembly/pom.xml
@@ -21,7 +21,7 @@
     <parent>
         <artifactId>kylin</artifactId>
         <groupId>org.apache.kylin</groupId>
-        <version>5.0.0-SNAPSHOT</version>
+        <version>5.0.0-alpha-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <modelVersion>4.0.0</modelVersion>
diff --git a/src/common-booter/pom.xml b/src/common-booter/pom.xml
index f02c9479bc..3f6374309f 100644
--- a/src/common-booter/pom.xml
+++ b/src/common-booter/pom.xml
@@ -5,7 +5,7 @@
     <parent>
         <artifactId>kylin</artifactId>
         <groupId>org.apache.kylin</groupId>
-        <version>5.0.0-SNAPSHOT</version>
+        <version>5.0.0-alpha-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <modelVersion>4.0.0</modelVersion>
diff --git a/src/common-server/pom.xml b/src/common-server/pom.xml
index 09d3edf4a9..57b1128fe4 100644
--- a/src/common-server/pom.xml
+++ b/src/common-server/pom.xml
@@ -5,7 +5,7 @@
     <parent>
         <artifactId>kylin</artifactId>
         <groupId>org.apache.kylin</groupId>
-        <version>5.0.0-SNAPSHOT</version>
+        <version>5.0.0-alpha-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <modelVersion>4.0.0</modelVersion>
diff --git a/src/common-service/pom.xml b/src/common-service/pom.xml
index 40c94752dc..ab885eba19 100644
--- a/src/common-service/pom.xml
+++ b/src/common-service/pom.xml
@@ -21,7 +21,7 @@
     <parent>
         <groupId>org.apache.kylin</groupId>
         <artifactId>kylin</artifactId>
-        <version>5.0.0-SNAPSHOT</version>
+        <version>5.0.0-alpha-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <modelVersion>4.0.0</modelVersion>
diff --git a/src/core-common/pom.xml b/src/core-common/pom.xml
index d9877d8606..17abbb6c7a 100644
--- a/src/core-common/pom.xml
+++ b/src/core-common/pom.xml
@@ -26,7 +26,7 @@
     <parent>
         <groupId>org.apache.kylin</groupId>
         <artifactId>kylin</artifactId>
-        <version>5.0.0-SNAPSHOT</version>
+        <version>5.0.0-alpha-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
 
diff --git a/src/core-job/pom.xml b/src/core-job/pom.xml
index bbdbb0cf2f..f01cf36f2f 100644
--- a/src/core-job/pom.xml
+++ b/src/core-job/pom.xml
@@ -27,7 +27,7 @@
     <parent>
         <groupId>org.apache.kylin</groupId>
         <artifactId>kylin</artifactId>
-        <version>5.0.0-SNAPSHOT</version>
+        <version>5.0.0-alpha-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
 
diff --git a/src/core-metadata/pom.xml b/src/core-metadata/pom.xml
index 96760a701c..e33bcc5d87 100644
--- a/src/core-metadata/pom.xml
+++ b/src/core-metadata/pom.xml
@@ -27,7 +27,7 @@
     <parent>
         <groupId>org.apache.kylin</groupId>
         <artifactId>kylin</artifactId>
-        <version>5.0.0-SNAPSHOT</version>
+        <version>5.0.0-alpha-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
 
diff --git a/src/core-storage/pom.xml b/src/core-storage/pom.xml
index 51f36b73fb..4de9e7315d 100644
--- a/src/core-storage/pom.xml
+++ b/src/core-storage/pom.xml
@@ -28,7 +28,7 @@
     <parent>
         <groupId>org.apache.kylin</groupId>
         <artifactId>kylin</artifactId>
-        <version>5.0.0-SNAPSHOT</version>
+        <version>5.0.0-alpha-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
 
diff --git a/src/data-loading-booter/pom.xml b/src/data-loading-booter/pom.xml
index 86f3295b3b..2d4d086689 100644
--- a/src/data-loading-booter/pom.xml
+++ b/src/data-loading-booter/pom.xml
@@ -5,7 +5,7 @@
     <parent>
         <artifactId>kylin</artifactId>
         <groupId>org.apache.kylin</groupId>
-        <version>5.0.0-SNAPSHOT</version>
+        <version>5.0.0-alpha-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <modelVersion>4.0.0</modelVersion>
diff --git a/src/data-loading-server/pom.xml b/src/data-loading-server/pom.xml
index e3bbc8e601..1d51f26301 100644
--- a/src/data-loading-server/pom.xml
+++ b/src/data-loading-server/pom.xml
@@ -5,7 +5,7 @@
     <parent>
         <artifactId>kylin</artifactId>
         <groupId>org.apache.kylin</groupId>
-        <version>5.0.0-SNAPSHOT</version>
+        <version>5.0.0-alpha-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <modelVersion>4.0.0</modelVersion>
diff --git a/src/data-loading-service/pom.xml b/src/data-loading-service/pom.xml
index 59dacc872f..dd608e3d3c 100644
--- a/src/data-loading-service/pom.xml
+++ b/src/data-loading-service/pom.xml
@@ -21,7 +21,7 @@
     <parent>
         <artifactId>kylin</artifactId>
         <groupId>org.apache.kylin</groupId>
-        <version>5.0.0-SNAPSHOT</version>
+        <version>5.0.0-alpha-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <modelVersion>4.0.0</modelVersion>
diff --git a/src/datasource-sdk/pom.xml b/src/datasource-sdk/pom.xml
index 27bcc81db4..78538b9a87 100644
--- a/src/datasource-sdk/pom.xml
+++ b/src/datasource-sdk/pom.xml
@@ -5,7 +5,7 @@
     <parent>
         <artifactId>kylin</artifactId>
         <groupId>org.apache.kylin</groupId>
-        <version>5.0.0-SNAPSHOT</version>
+        <version>5.0.0-alpha-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <modelVersion>4.0.0</modelVersion>
diff --git a/src/datasource-service/pom.xml b/src/datasource-service/pom.xml
index 69e2d8427b..7b48590572 100644
--- a/src/datasource-service/pom.xml
+++ b/src/datasource-service/pom.xml
@@ -21,7 +21,7 @@
     <parent>
         <artifactId>kylin</artifactId>
         <groupId>org.apache.kylin</groupId>
-        <version>5.0.0-SNAPSHOT</version>
+        <version>5.0.0-alpha-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <modelVersion>4.0.0</modelVersion>
diff --git a/src/integration-service/pom.xml b/src/integration-service/pom.xml
index 98351988d0..96f16bef3d 100644
--- a/src/integration-service/pom.xml
+++ b/src/integration-service/pom.xml
@@ -5,7 +5,7 @@
     <parent>
         <artifactId>kylin</artifactId>
         <groupId>org.apache.kylin</groupId>
-        <version>5.0.0-SNAPSHOT</version>
+        <version>5.0.0-alpha-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <modelVersion>4.0.0</modelVersion>
diff --git a/src/jdbc/pom.xml b/src/jdbc/pom.xml
index f2a696c971..4cf878ad8b 100644
--- a/src/jdbc/pom.xml
+++ b/src/jdbc/pom.xml
@@ -5,7 +5,7 @@
     <parent>
         <artifactId>kylin</artifactId>
         <groupId>org.apache.kylin</groupId>
-        <version>5.0.0-SNAPSHOT</version>
+        <version>5.0.0-alpha-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <modelVersion>4.0.0</modelVersion>
diff --git a/src/job-service/pom.xml b/src/job-service/pom.xml
index 05a7cd4a79..a4eb3c8bce 100644
--- a/src/job-service/pom.xml
+++ b/src/job-service/pom.xml
@@ -5,7 +5,7 @@
     <parent>
         <artifactId>kylin</artifactId>
         <groupId>org.apache.kylin</groupId>
-        <version>5.0.0-SNAPSHOT</version>
+        <version>5.0.0-alpha-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <modelVersion>4.0.0</modelVersion>
diff --git a/src/kylin-it/pom.xml b/src/kylin-it/pom.xml
index 26f612b535..164c465210 100644
--- a/src/kylin-it/pom.xml
+++ b/src/kylin-it/pom.xml
@@ -5,7 +5,7 @@
     <parent>
         <artifactId>kylin</artifactId>
         <groupId>org.apache.kylin</groupId>
-        <version>5.0.0-SNAPSHOT</version>
+        <version>5.0.0-alpha-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <modelVersion>4.0.0</modelVersion>
diff --git a/src/metadata-server/pom.xml b/src/metadata-server/pom.xml
index 47f233bff3..45fc90653c 100644
--- a/src/metadata-server/pom.xml
+++ b/src/metadata-server/pom.xml
@@ -5,7 +5,7 @@
     <parent>
         <artifactId>kylin</artifactId>
         <groupId>org.apache.kylin</groupId>
-        <version>5.0.0-SNAPSHOT</version>
+        <version>5.0.0-alpha-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <modelVersion>4.0.0</modelVersion>
diff --git a/src/modeling-service/pom.xml b/src/modeling-service/pom.xml
index 355240107c..6219f08344 100644
--- a/src/modeling-service/pom.xml
+++ b/src/modeling-service/pom.xml
@@ -21,7 +21,7 @@
     <parent>
         <groupId>org.apache.kylin</groupId>
         <artifactId>kylin</artifactId>
-        <version>5.0.0-SNAPSHOT</version>
+        <version>5.0.0-alpha-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <modelVersion>4.0.0</modelVersion>
diff --git a/src/query-booter/pom.xml b/src/query-booter/pom.xml
index 4be5019318..59bba14174 100644
--- a/src/query-booter/pom.xml
+++ b/src/query-booter/pom.xml
@@ -5,7 +5,7 @@
     <parent>
         <artifactId>kylin</artifactId>
         <groupId>org.apache.kylin</groupId>
-        <version>5.0.0-SNAPSHOT</version>
+        <version>5.0.0-alpha-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <modelVersion>4.0.0</modelVersion>
diff --git a/src/query-common/pom.xml b/src/query-common/pom.xml
index 029c427b79..b64d1982d5 100644
--- a/src/query-common/pom.xml
+++ b/src/query-common/pom.xml
@@ -12,7 +12,7 @@
     <parent>
         <artifactId>kylin</artifactId>
         <groupId>org.apache.kylin</groupId>
-        <version>5.0.0-SNAPSHOT</version>
+        <version>5.0.0-alpha-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
 
diff --git a/src/query-server/pom.xml b/src/query-server/pom.xml
index c798101cab..ed330c821f 100644
--- a/src/query-server/pom.xml
+++ b/src/query-server/pom.xml
@@ -5,7 +5,7 @@
     <parent>
         <artifactId>kylin</artifactId>
         <groupId>org.apache.kylin</groupId>
-        <version>5.0.0-SNAPSHOT</version>
+        <version>5.0.0-alpha-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <modelVersion>4.0.0</modelVersion>
diff --git a/src/query-service/pom.xml b/src/query-service/pom.xml
index d278821f6f..d4ab4fe0d8 100644
--- a/src/query-service/pom.xml
+++ b/src/query-service/pom.xml
@@ -21,7 +21,7 @@
     <parent>
         <artifactId>kylin</artifactId>
         <groupId>org.apache.kylin</groupId>
-        <version>5.0.0-SNAPSHOT</version>
+        <version>5.0.0-alpha-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <modelVersion>4.0.0</modelVersion>
diff --git a/src/query/pom.xml b/src/query/pom.xml
index 1479d73d1f..e04a0da7fc 100644
--- a/src/query/pom.xml
+++ b/src/query/pom.xml
@@ -27,7 +27,7 @@
     <parent>
         <groupId>org.apache.kylin</groupId>
         <artifactId>kylin</artifactId>
-        <version>5.0.0-SNAPSHOT</version>
+        <version>5.0.0-alpha-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
 
diff --git a/src/second-storage/clickhouse-it/pom.xml b/src/second-storage/clickhouse-it/pom.xml
index e2ed6e4089..31baa6c064 100644
--- a/src/second-storage/clickhouse-it/pom.xml
+++ b/src/second-storage/clickhouse-it/pom.xml
@@ -5,7 +5,7 @@
     <parent>
         <artifactId>kylin</artifactId>
         <groupId>org.apache.kylin</groupId>
-        <version>5.0.0-SNAPSHOT</version>
+        <version>5.0.0-alpha-SNAPSHOT</version>
         <relativePath>../../../pom.xml</relativePath>
     </parent>
     <modelVersion>4.0.0</modelVersion>
diff --git a/src/second-storage/clickhouse/pom.xml b/src/second-storage/clickhouse/pom.xml
index 49fef8791b..57e102eadc 100644
--- a/src/second-storage/clickhouse/pom.xml
+++ b/src/second-storage/clickhouse/pom.xml
@@ -5,7 +5,7 @@
     <parent>
         <groupId>org.apache.kylin</groupId>
         <artifactId>kylin</artifactId>
-        <version>5.0.0-SNAPSHOT</version>
+        <version>5.0.0-alpha-SNAPSHOT</version>
         <relativePath>../../../pom.xml</relativePath>
     </parent>
     <modelVersion>4.0.0</modelVersion>
diff --git a/src/second-storage/core-ui/pom.xml b/src/second-storage/core-ui/pom.xml
index a2c24ab740..27069cb23d 100644
--- a/src/second-storage/core-ui/pom.xml
+++ b/src/second-storage/core-ui/pom.xml
@@ -5,7 +5,7 @@
     <parent>
         <artifactId>kylin</artifactId>
         <groupId>org.apache.kylin</groupId>
-        <version>5.0.0-SNAPSHOT</version>
+        <version>5.0.0-alpha-SNAPSHOT</version>
         <relativePath>../../../pom.xml</relativePath>
     </parent>
     <modelVersion>4.0.0</modelVersion>
diff --git a/src/second-storage/core/pom.xml b/src/second-storage/core/pom.xml
index 16556995c0..904e332cc0 100644
--- a/src/second-storage/core/pom.xml
+++ b/src/second-storage/core/pom.xml
@@ -6,7 +6,7 @@
     <parent>
         <artifactId>kylin</artifactId>
         <groupId>org.apache.kylin</groupId>
-        <version>5.0.0-SNAPSHOT</version>
+        <version>5.0.0-alpha-SNAPSHOT</version>
         <relativePath>../../../pom.xml</relativePath>
     </parent>
 
diff --git a/src/server/pom.xml b/src/server/pom.xml
index 92d041bbf9..578dc7cffd 100644
--- a/src/server/pom.xml
+++ b/src/server/pom.xml
@@ -29,7 +29,7 @@
     <parent>
         <artifactId>kylin</artifactId>
         <groupId>org.apache.kylin</groupId>
-        <version>5.0.0-SNAPSHOT</version>
+        <version>5.0.0-alpha-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
 
diff --git a/src/source-hive/pom.xml b/src/source-hive/pom.xml
index 00a5f4a4ad..69d1bb28d6 100644
--- a/src/source-hive/pom.xml
+++ b/src/source-hive/pom.xml
@@ -28,7 +28,7 @@
     <parent>
         <groupId>org.apache.kylin</groupId>
         <artifactId>kylin</artifactId>
-        <version>5.0.0-SNAPSHOT</version>
+        <version>5.0.0-alpha-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
 
diff --git a/src/spark-project/engine-build-sdk/pom.xml b/src/spark-project/engine-build-sdk/pom.xml
index 0e2b3d6866..6b7d0b85dd 100644
--- a/src/spark-project/engine-build-sdk/pom.xml
+++ b/src/spark-project/engine-build-sdk/pom.xml
@@ -22,7 +22,7 @@
     <parent>
         <groupId>org.apache.kylin</groupId>
         <artifactId>kylin</artifactId>
-        <version>5.0.0-SNAPSHOT</version>
+        <version>5.0.0-alpha-SNAPSHOT</version>
         <relativePath>../../../pom.xml</relativePath>
     </parent>
     <modelVersion>4.0.0</modelVersion>
diff --git a/src/spark-project/engine-spark/pom.xml b/src/spark-project/engine-spark/pom.xml
index b03cffa85b..e6946b5500 100644
--- a/src/spark-project/engine-spark/pom.xml
+++ b/src/spark-project/engine-spark/pom.xml
@@ -28,7 +28,7 @@
     <parent>
         <groupId>org.apache.kylin</groupId>
         <artifactId>kylin</artifactId>
-        <version>5.0.0-SNAPSHOT</version>
+        <version>5.0.0-alpha-SNAPSHOT</version>
         <relativePath>../../../pom.xml</relativePath>
     </parent>
 
diff --git a/src/spark-project/kylin-soft-affinity-cache/pom.xml b/src/spark-project/kylin-soft-affinity-cache/pom.xml
index ba4279965f..a2a576a8de 100644
--- a/src/spark-project/kylin-soft-affinity-cache/pom.xml
+++ b/src/spark-project/kylin-soft-affinity-cache/pom.xml
@@ -31,7 +31,7 @@
     <parent>
         <artifactId>kylin</artifactId>
         <groupId>org.apache.kylin</groupId>
-        <version>5.0.0-SNAPSHOT</version>
+        <version>5.0.0-alpha-SNAPSHOT</version>
         <relativePath>../../../pom.xml</relativePath>
     </parent>
 
diff --git a/src/spark-project/source-jdbc/pom.xml b/src/spark-project/source-jdbc/pom.xml
index ea58eb83de..53869ffb9a 100644
--- a/src/spark-project/source-jdbc/pom.xml
+++ b/src/spark-project/source-jdbc/pom.xml
@@ -21,7 +21,7 @@
     <parent>
         <artifactId>kylin</artifactId>
         <groupId>org.apache.kylin</groupId>
-        <version>5.0.0-SNAPSHOT</version>
+        <version>5.0.0-alpha-SNAPSHOT</version>
         <relativePath>../../../pom.xml</relativePath>
     </parent>
     <modelVersion>4.0.0</modelVersion>
diff --git a/src/spark-project/sparder/pom.xml b/src/spark-project/sparder/pom.xml
index 1cccda51c6..cc12d2feea 100644
--- a/src/spark-project/sparder/pom.xml
+++ b/src/spark-project/sparder/pom.xml
@@ -32,7 +32,7 @@
     <parent>
         <artifactId>kylin</artifactId>
         <groupId>org.apache.kylin</groupId>
-        <version>5.0.0-SNAPSHOT</version>
+        <version>5.0.0-alpha-SNAPSHOT</version>
         <relativePath>../../../pom.xml</relativePath>
     </parent>
 
diff --git a/src/spark-project/spark-common/pom.xml b/src/spark-project/spark-common/pom.xml
index ff80077128..93d56ee16e 100644
--- a/src/spark-project/spark-common/pom.xml
+++ b/src/spark-project/spark-common/pom.xml
@@ -29,7 +29,7 @@
     <parent>
         <artifactId>kylin</artifactId>
         <groupId>org.apache.kylin</groupId>
-        <version>5.0.0-SNAPSHOT</version>
+        <version>5.0.0-alpha-SNAPSHOT</version>
         <relativePath>../../../pom.xml</relativePath>
     </parent>
 
diff --git a/src/spark-project/spark-ddl-plugin/pom.xml b/src/spark-project/spark-ddl-plugin/pom.xml
index 0b7d00b998..ac59dcf885 100644
--- a/src/spark-project/spark-ddl-plugin/pom.xml
+++ b/src/spark-project/spark-ddl-plugin/pom.xml
@@ -5,7 +5,7 @@
     <parent>
         <artifactId>kylin</artifactId>
         <groupId>org.apache.kylin</groupId>
-        <version>5.0.0-SNAPSHOT</version>
+        <version>5.0.0-alpha-SNAPSHOT</version>
         <relativePath>../../../pom.xml</relativePath>
     </parent>
     <modelVersion>4.0.0</modelVersion>
diff --git a/src/spark-project/spark-it/pom.xml b/src/spark-project/spark-it/pom.xml
index 32ed08642f..71255f87a6 100644
--- a/src/spark-project/spark-it/pom.xml
+++ b/src/spark-project/spark-it/pom.xml
@@ -29,7 +29,7 @@
     <parent>
         <artifactId>kylin</artifactId>
         <groupId>org.apache.kylin</groupId>
-        <version>5.0.0-SNAPSHOT</version>
+        <version>5.0.0-alpha-SNAPSHOT</version>
         <relativePath>../../../pom.xml</relativePath>
     </parent>
 
diff --git a/src/streaming-service/pom.xml b/src/streaming-service/pom.xml
index 25ffa8eac2..ea483c9936 100644
--- a/src/streaming-service/pom.xml
+++ b/src/streaming-service/pom.xml
@@ -5,7 +5,7 @@
     <parent>
         <artifactId>kylin</artifactId>
         <groupId>org.apache.kylin</groupId>
-        <version>5.0.0-SNAPSHOT</version>
+        <version>5.0.0-alpha-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <modelVersion>4.0.0</modelVersion>
diff --git a/src/streaming/pom.xml b/src/streaming/pom.xml
index cf7d1c0037..54c39f3f36 100644
--- a/src/streaming/pom.xml
+++ b/src/streaming/pom.xml
@@ -27,7 +27,7 @@
     <parent>
         <artifactId>kylin</artifactId>
         <groupId>org.apache.kylin</groupId>
-        <version>5.0.0-SNAPSHOT</version>
+        <version>5.0.0-alpha-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <properties>
diff --git a/src/systools/pom.xml b/src/systools/pom.xml
index c9026a1dac..35c774de05 100644
--- a/src/systools/pom.xml
+++ b/src/systools/pom.xml
@@ -21,7 +21,7 @@
     <parent>
         <artifactId>kylin</artifactId>
         <groupId>org.apache.kylin</groupId>
-        <version>5.0.0-SNAPSHOT</version>
+        <version>5.0.0-alpha-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <modelVersion>4.0.0</modelVersion>
diff --git a/src/tool/pom.xml b/src/tool/pom.xml
index e4174d46d1..0d823a3a8a 100644
--- a/src/tool/pom.xml
+++ b/src/tool/pom.xml
@@ -29,7 +29,7 @@
     <parent>
         <artifactId>kylin</artifactId>
         <groupId>org.apache.kylin</groupId>
-        <version>5.0.0-SNAPSHOT</version>
+        <version>5.0.0-alpha-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>