Repository: apache/streampipes
Pull request #2057 (2023-10-19 13:45:33)

feat(#2056): Make email templates configurable in UI
<!--
  ~ 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.
  ~
  -->
  
  <!--
Thanks for contributing! Here are some tips you can follow to help us incorporate your contribution quickly and easily:
1. If this is your first time, please read our contributor guidelines:
    - https://streampipes.apache.org/getinvolved.html
    - https://cwiki.apache.org/confluence/display/STREAMPIPES/Getting+Started
2. Make sure the PR title is formatted like: `[#<GitHub issue id>] PR title ...`
3. If the PR is unfinished, add '[WIP]' in your PR title, e.g., `[WIP][#<GitHub issue id>] PR title ...`.
4. Please write your PR title to summarize what this PR proposes/fixes.
5. Link the PR to the corresponding GitHub issue (if present) in the `Development` section in the right menu bar. 
6. Be sure to keep the PR description updated to reflect all changes.
7. If possible, provide a concise example to reproduce the issue for a faster review.
8. Make sure tests pass via `mvn clean install`.
9. (Optional) If the contribution is large, please file an Apache ICLA
    - http://apache.org/licenses/icla.pdf
-->

### Purpose
<!--
Please clarify what changes you are proposing and describe how those changes will address the issue.
Furthermore, describe potential consequences the changes might have.
-->

See #2056 

### Remarks
<!--
Is there anything left we need to pay attention on?
Are there some references that might be important? E.g. links to Confluence, or discussions
on the mailing list or GitHub.
-->
PR introduces (a) breaking change(s): no

PR introduces (a) deprecation(s): no

bossenti: ![image](https://github.com/apache/streampipes/assets/50115603/30032ca6-2b90-4ef1-9f32-991bce5aab7e)

How is this user dialog supposed to work? I can't click or select anything. Typing in the code editor works.

Would it be possible to add syntax highlighting for HTML?
dominikriemer: All done, thanks ;-)

Patch:
diff --git a/streampipes-mail/src/main/java/org/apache/streampipes/mail/MailSender.java b/streampipes-mail/src/main/java/org/apache/streampipes/mail/MailSender.java
index c34f8f6e65..43f9d57803 100644
--- a/streampipes-mail/src/main/java/org/apache/streampipes/mail/MailSender.java
+++ b/streampipes-mail/src/main/java/org/apache/streampipes/mail/MailSender.java
@@ -18,6 +18,7 @@
 package org.apache.streampipes.mail;
 
 import org.apache.streampipes.mail.template.AccountActiviationMailTemplate;
+import org.apache.streampipes.mail.template.CustomMailTemplate;
 import org.apache.streampipes.mail.template.InitialPasswordMailTemplate;
 import org.apache.streampipes.mail.template.PasswordRecoveryMailTemplate;
 import org.apache.streampipes.mail.utils.MailUtils;
@@ -29,11 +30,14 @@ import java.io.IOException;
 
 public class MailSender extends AbstractMailer {
 
-  public void sendEmail(SpEmail mail) {
+  public void sendEmail(SpEmail mail) throws IOException {
     Email email = baseEmail()
         .withRecipients(toSimpleRecipientList(mail.getRecipients()))
         .withSubject(mail.getSubject())
-        .appendText(mail.getMessage())
+        .appendTextHTML(new CustomMailTemplate(
+            mail.getSubject(),
+            mail.getPreheader(),
+            mail.getMessage()).generateTemplate())
         .buildEmail();
 
     deliverMail(email);
diff --git a/streampipes-mail/src/main/java/org/apache/streampipes/mail/template/AbstractMailTemplate.java b/streampipes-mail/src/main/java/org/apache/streampipes/mail/template/AbstractMailTemplate.java
index ec05ada2e8..d9f58e00f7 100644
--- a/streampipes-mail/src/main/java/org/apache/streampipes/mail/template/AbstractMailTemplate.java
+++ b/streampipes-mail/src/main/java/org/apache/streampipes/mail/template/AbstractMailTemplate.java
@@ -21,7 +21,7 @@ import org.apache.streampipes.mail.template.generation.DefaultPlaceholders;
 import org.apache.streampipes.mail.template.generation.MailTemplateBuilder;
 import org.apache.streampipes.mail.template.part.BaseUrlPart;
 import org.apache.streampipes.mail.template.part.LogoPart;
-import org.apache.streampipes.mail.template.part.MailTemplatePart;
+import org.apache.streampipes.storage.management.StorageDispatcher;
 
 import java.io.IOException;
 import java.util.HashMap;
@@ -35,22 +35,27 @@ public abstract class AbstractMailTemplate {
 
   protected abstract void addPlaceholders(Map<String, String> placeholders);
 
-  protected abstract void addTemplateParts(Map<String, MailTemplatePart> templateParts);
+  protected abstract void configureTemplate(MailTemplateBuilder builder);
 
   public String generateTemplate() throws IOException {
-    Map<String, MailTemplatePart> templateParts = new HashMap<>();
     Map<String, String> placeholders = new HashMap<>();
-    addTemplateParts(templateParts);
     addPlaceholders(placeholders);
 
-    return MailTemplateBuilder.create(MailTemplatePart.MAIL_TEMPLATE_OUTER)
-        .addSubpart(DefaultPlaceholders.FOOTER, MailTemplatePart.MAIL_TEMPLATE_FOOTER)
-        .addSubparts(templateParts)
+    var template = StorageDispatcher.INSTANCE
+        .getNoSqlStore()
+        .getSpCoreConfigurationStorage()
+        .get()
+        .getEmailTemplateConfig()
+        .getTemplate();
+
+    var builder = MailTemplateBuilder.create(template)
         .withPlaceholder(DefaultPlaceholders.TITLE, getTitle())
         .withPlaceholder(DefaultPlaceholders.PREHEADER, getPreHeader())
         .withPlaceholder(DefaultPlaceholders.LOGO, new LogoPart().generate())
         .withPlaceholder(DefaultPlaceholders.BASE_URL, new BaseUrlPart().generate())
-        .withPlaceholders(placeholders)
-        .generateHtmlTemplate();
+        .withPlaceholders(placeholders);
+
+    configureTemplate(builder);
+    return builder.generateHtmlTemplate();
   }
 }
diff --git a/streampipes-mail/src/main/java/org/apache/streampipes/mail/template/AccountActiviationMailTemplate.java b/streampipes-mail/src/main/java/org/apache/streampipes/mail/template/AccountActiviationMailTemplate.java
index 7bd0a7974d..c9c204559e 100644
--- a/streampipes-mail/src/main/java/org/apache/streampipes/mail/template/AccountActiviationMailTemplate.java
+++ b/streampipes-mail/src/main/java/org/apache/streampipes/mail/template/AccountActiviationMailTemplate.java
@@ -18,6 +18,7 @@
 package org.apache.streampipes.mail.template;
 
 import org.apache.streampipes.mail.template.generation.DefaultPlaceholders;
+import org.apache.streampipes.mail.template.generation.MailTemplateBuilder;
 import org.apache.streampipes.mail.template.part.LinkPart;
 import org.apache.streampipes.mail.template.part.MailTemplatePart;
 import org.apache.streampipes.mail.utils.MailUtils;
@@ -54,8 +55,8 @@ public class AccountActiviationMailTemplate extends AbstractMailTemplate {
   }
 
   @Override
-  protected void addTemplateParts(Map<String, MailTemplatePart> templateParts) {
-    templateParts.put(DefaultPlaceholders.INNER.key(), MailTemplatePart.MAIL_TEMPLATE_INNER_BUTTON);
+  protected void configureTemplate(MailTemplateBuilder builder) {
+    builder.withInnerPart(MailTemplatePart.MAIL_TEMPLATE_INNER_BUTTON);
   }
 
   private String makeLink() {
diff --git a/streampipes-mail/src/main/java/org/apache/streampipes/mail/template/CustomMailTemplate.java b/streampipes-mail/src/main/java/org/apache/streampipes/mail/template/CustomMailTemplate.java
new file mode 100644
index 0000000000..1ea53286d8
--- /dev/null
+++ b/streampipes-mail/src/main/java/org/apache/streampipes/mail/template/CustomMailTemplate.java
@@ -0,0 +1,57 @@
+/*
+ * 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.streampipes.mail.template;
+
+import org.apache.streampipes.mail.template.generation.MailTemplateBuilder;
+
+import java.util.Map;
+
+public class CustomMailTemplate extends AbstractMailTemplate {
+
+  private final String title;
+  private final String preheader;
+  private final String content;
+
+  public CustomMailTemplate(String title,
+                            String preheader,
+                            String content) {
+    this.title = title;
+    this.preheader = preheader;
+    this.content = content;
+  }
+
+  @Override
+  protected String getTitle() {
+    return title;
+  }
+
+  @Override
+  protected String getPreHeader() {
+    return preheader;
+  }
+
+  @Override
+  protected void addPlaceholders(Map<String, String> placeholders) {
+  }
+
+  @Override
+  protected void configureTemplate(MailTemplateBuilder builder) {
+    builder.withInnerPart(content);
+  }
+}
diff --git a/streampipes-mail/src/main/java/org/apache/streampipes/mail/template/InitialPasswordMailTemplate.java b/streampipes-mail/src/main/java/org/apache/streampipes/mail/template/InitialPasswordMailTemplate.java
index 6e8a7e847c..f74f279f1f 100644
--- a/streampipes-mail/src/main/java/org/apache/streampipes/mail/template/InitialPasswordMailTemplate.java
+++ b/streampipes-mail/src/main/java/org/apache/streampipes/mail/template/InitialPasswordMailTemplate.java
@@ -18,6 +18,7 @@
 package org.apache.streampipes.mail.template;
 
 import org.apache.streampipes.mail.template.generation.DefaultPlaceholders;
+import org.apache.streampipes.mail.template.generation.MailTemplateBuilder;
 import org.apache.streampipes.mail.template.part.LinkPart;
 import org.apache.streampipes.mail.template.part.MailTemplatePart;
 import org.apache.streampipes.mail.utils.MailUtils;
@@ -54,8 +55,8 @@ public class InitialPasswordMailTemplate extends AbstractMailTemplate {
   }
 
   @Override
-  protected void addTemplateParts(Map<String, MailTemplatePart> templateParts) {
-    templateParts.put(DefaultPlaceholders.INNER.key(), MailTemplatePart.MAIL_TEMPLATE_INNER_BUTTON);
+  protected void configureTemplate(MailTemplateBuilder builder) {
+    builder.withInnerPart(MailTemplatePart.MAIL_TEMPLATE_INNER_BUTTON);
   }
 
   private String makeLink() {
diff --git a/streampipes-mail/src/main/java/org/apache/streampipes/mail/template/PasswordRecoveryMailTemplate.java b/streampipes-mail/src/main/java/org/apache/streampipes/mail/template/PasswordRecoveryMailTemplate.java
index 2cfa43baa0..da1d0aa093 100644
--- a/streampipes-mail/src/main/java/org/apache/streampipes/mail/template/PasswordRecoveryMailTemplate.java
+++ b/streampipes-mail/src/main/java/org/apache/streampipes/mail/template/PasswordRecoveryMailTemplate.java
@@ -18,6 +18,7 @@
 package org.apache.streampipes.mail.template;
 
 import org.apache.streampipes.mail.template.generation.DefaultPlaceholders;
+import org.apache.streampipes.mail.template.generation.MailTemplateBuilder;
 import org.apache.streampipes.mail.template.part.LinkPart;
 import org.apache.streampipes.mail.template.part.MailTemplatePart;
 import org.apache.streampipes.mail.utils.MailUtils;
@@ -54,9 +55,8 @@ public class PasswordRecoveryMailTemplate extends AbstractMailTemplate {
   }
 
   @Override
-  protected void addTemplateParts(Map<String, MailTemplatePart> templateParts) {
-    templateParts.put(DefaultPlaceholders.INNER.key(), MailTemplatePart.MAIL_TEMPLATE_INNER_BUTTON);
-    templateParts.put(DefaultPlaceholders.FOOTER.key(), MailTemplatePart.MAIL_TEMPLATE_FOOTER);
+  protected void configureTemplate(MailTemplateBuilder builder) {
+    builder.withInnerPart(MailTemplatePart.MAIL_TEMPLATE_INNER_BUTTON);
   }
 
   private String makeLink() {
diff --git a/streampipes-mail/src/main/java/org/apache/streampipes/mail/template/generation/DefaultPlaceholders.java b/streampipes-mail/src/main/java/org/apache/streampipes/mail/template/generation/DefaultPlaceholders.java
index 757c8571ea..3f80e578f0 100644
--- a/streampipes-mail/src/main/java/org/apache/streampipes/mail/template/generation/DefaultPlaceholders.java
+++ b/streampipes-mail/src/main/java/org/apache/streampipes/mail/template/generation/DefaultPlaceholders.java
@@ -21,8 +21,6 @@ public enum DefaultPlaceholders {
 
   TITLE("TITLE"),
   PREHEADER("PREHEADER"),
-  FOOTER("FOOTER"),
-  FOOTER_TEXT("FOOTER_TEXT"),
   INNER("INNER"),
 
   BUTTON_TEXT("BUTTON_TEXT"),
diff --git a/streampipes-mail/src/main/java/org/apache/streampipes/mail/template/generation/MailTemplateBuilder.java b/streampipes-mail/src/main/java/org/apache/streampipes/mail/template/generation/MailTemplateBuilder.java
index 4109c1dc33..c16cc8b261 100644
--- a/streampipes-mail/src/main/java/org/apache/streampipes/mail/template/generation/MailTemplateBuilder.java
+++ b/streampipes-mail/src/main/java/org/apache/streampipes/mail/template/generation/MailTemplateBuilder.java
@@ -18,7 +18,6 @@
 package org.apache.streampipes.mail.template.generation;
 
 import org.apache.streampipes.mail.template.part.MailTemplatePart;
-import org.apache.streampipes.mail.utils.MailUtils;
 
 import java.io.IOException;
 import java.util.HashMap;
@@ -26,33 +25,30 @@ import java.util.Map;
 
 public class MailTemplateBuilder {
 
-  private final MailTemplatePart outerPart;
-  private final Map<String, MailTemplatePart> innerParts;
+  private final String outerTemplate;
+  private String innerPart;
   private final Map<String, String> placeholders;
 
-  private MailTemplateBuilder(MailTemplatePart outerPart) {
-    this.outerPart = outerPart;
-    this.innerParts = new HashMap<>();
+  private MailTemplateBuilder(String outerTemplate) {
+    this.outerTemplate = outerTemplate;
     this.placeholders = new HashMap<>();
   }
 
-  public static MailTemplateBuilder create(MailTemplatePart outerPart) {
-    return new MailTemplateBuilder(outerPart);
+  public static MailTemplateBuilder create(String outerTemplate) {
+    return new MailTemplateBuilder(outerTemplate);
   }
 
-  public MailTemplateBuilder addSubpart(String placeholder, MailTemplatePart templatePart) {
-    this.innerParts.put(placeholder, templatePart);
-
+  public MailTemplateBuilder withInnerPart(MailTemplatePart innerPart) {
+    try {
+      this.innerPart = innerPart.getContent();
+    } catch (IOException e) {
+      throw new RuntimeException(e);
+    }
     return this;
   }
 
-  public MailTemplateBuilder addSubpart(DefaultPlaceholders placeholder, MailTemplatePart templatePart) {
-    return addSubpart(placeholder.key(), templatePart);
-
-  }
-
-  public MailTemplateBuilder addSubparts(Map<String, MailTemplatePart> templateParts) {
-    this.innerParts.putAll(templateParts);
+  public MailTemplateBuilder withInnerPart(String content) {
+    this.innerPart = content;
     return this;
   }
 
@@ -72,8 +68,8 @@ public class MailTemplateBuilder {
     return withPlaceholder(placeholder.key(), content);
   }
 
-  public String generateHtmlTemplate() throws IOException {
-    String fullTemplate = getAndApplyPlaceholders(outerPart);
+  public String generateHtmlTemplate() {
+    String fullTemplate = getAndApplyPlaceholders(outerTemplate);
 
     for (String key : placeholders.keySet()) {
       String placeholder = makeKey(key);
@@ -83,30 +79,15 @@ public class MailTemplateBuilder {
     return fullTemplate;
   }
 
-  private String readFileContentsToString(MailTemplatePart part) throws IOException {
-    return MailUtils.readResourceFileToString(part.getTemplateFilename());
+  private String getAndApplyPlaceholders(String outerTemplate) {
+    return applyInnerTemplate(outerTemplate);
   }
 
-  private String getAndApplyPlaceholders(MailTemplatePart partContent) throws IOException {
-    String partContentAsString = readFileContentsToString(partContent);
-    for (String innerPartKey : innerParts.keySet()) {
-      String templateKey = makeKey(innerPartKey);
-      if (hasPlaceholder(partContentAsString, templateKey)) {
-        partContentAsString =
-            partContentAsString.replaceAll(templateKey, getAndApplyPlaceholders(innerParts.get(innerPartKey)));
-      }
-    }
-
-    return partContentAsString;
-  }
-
-  private boolean hasPlaceholder(String content, String placeholder) {
-    return content.contains(placeholder);
+  private String applyInnerTemplate(String content) {
+    return content.replaceAll(makeKey(DefaultPlaceholders.INNER.key()), innerPart);
   }
 
   private String makeKey(String key) {
     return "###" + key + "###";
   }
-
-
 }
diff --git a/streampipes-mail/src/main/java/org/apache/streampipes/mail/template/part/MailTemplatePart.java b/streampipes-mail/src/main/java/org/apache/streampipes/mail/template/part/MailTemplatePart.java
index a3526fd13b..addd162857 100644
--- a/streampipes-mail/src/main/java/org/apache/streampipes/mail/template/part/MailTemplatePart.java
+++ b/streampipes-mail/src/main/java/org/apache/streampipes/mail/template/part/MailTemplatePart.java
@@ -17,20 +17,22 @@
  */
 package org.apache.streampipes.mail.template.part;
 
+import org.apache.streampipes.mail.utils.MailUtils;
+
+import java.io.IOException;
+
 public enum MailTemplatePart {
 
-  MAIL_TEMPLATE_OUTER("mail-template-outer.html"),
   MAIL_TEMPLATE_INNER_BUTTON("mail-template-inner-button.html"),
-  MAIL_TEMPLATE_INNER_PLAIN("mail-template-inner-plain.html"),
-  MAIL_TEMPLATE_FOOTER("mail-template-footer.html");
+  MAIL_TEMPLATE_INNER_PLAIN("mail-template-inner-plain.html");
 
-  private String templateFilename;
+  private final String templateFilename;
 
   MailTemplatePart(String templateFilename) {
     this.templateFilename = templateFilename;
   }
 
-  public String getTemplateFilename() {
-    return this.templateFilename;
+  public String getContent() throws IOException {
+    return MailUtils.readResourceFileToString(templateFilename);
   }
 }
diff --git a/streampipes-mail/src/main/resources/mail-template-footer.html b/streampipes-mail/src/main/resources/mail-template-footer.html
deleted file mode 100644
index 3c0aaca246..0000000000
--- a/streampipes-mail/src/main/resources/mail-template-footer.html
+++ /dev/null
@@ -1,39 +0,0 @@
-<!--
-  ~ MIT License
-  ~
-  ~ Copyright (c) 2018 SendGrid, Inc.
-  ~
-  ~ Permission is hereby granted, free of charge, to any person obtaining a copy
-  ~ of this software and associated documentation files (the "Software"), to deal
-  ~ in the Software without restriction, including without limitation the rights
-  ~ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-  ~ copies of the Software, and to permit persons to whom the Software is
-  ~ furnished to do so, subject to the following conditions:
-  ~
-  ~ The above copyright notice and this permission notice shall be included in all
-  ~ copies or substantial portions of the Software.
-  ~
-  ~ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-  ~ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-  ~ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-  ~ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-  ~ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-  ~ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-  ~ SOFTWARE.
--->
-
-<tr>
-    <td align="center" bgcolor="#e9ecef" style="padding: 24px;">
-        <table border="0" cellpadding="0" cellspacing="0" style="max-width: 600px;" width="100%">
-            <tr>
-                <td align="center" bgcolor="#e9ecef"
-                    style="padding: 12px 24px; font-family: 'Source Sans Pro', Helvetica, Arial, sans-serif; font-size: 14px; line-height: 20px; color: #666;">
-                    <p style="margin: 0;">This mail was automatically sent from a local installation of Apache
-                        StreamPipes - contact your administrator if you think you shouldn't receive this mail.</p>
-                    <p>This mail is not sent by the Apache StreamPipes team.</p>
-                    </p>
-                </td>
-            </tr>
-        </table>
-    </td>
-</tr>
diff --git a/streampipes-mail/src/main/resources/mail-template-inner-button.html b/streampipes-mail/src/main/resources/mail-template-inner-button.html
index f9bc7aecd7..98878a0358 100644
--- a/streampipes-mail/src/main/resources/mail-template-inner-button.html
+++ b/streampipes-mail/src/main/resources/mail-template-inner-button.html
@@ -22,42 +22,38 @@
   ~ SOFTWARE.
 -->
 
-<tr>
-    <td align="center" bgcolor="#e9ecef">
-        <table border="0" cellpadding="0" cellspacing="0" style="max-width: 600px;" width="100%">
-            <tr>
-                <td align="left" bgcolor="#ffffff"
-                    style="padding: 24px; font-family: 'Source Sans Pro', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 24px;">
-                    <p style="margin: 0;">###MANUAL###</p>
-                </td>
-            </tr>
-            <tr>
-                <td align="left" bgcolor="#ffffff">
-                    <table border="0" cellpadding="0" cellspacing="0" width="100%">
-                        <tr>
-                            <td align="center" bgcolor="#ffffff" style="padding: 12px;">
-                                <table border="0" cellpadding="0" cellspacing="0">
-                                    <tr>
-                                        <td align="center" bgcolor="#1b1464" style="border-radius: 6px;">
-                                            <a href="###LINK###"
-                                               style="display: inline-block; padding: 16px 36px; font-family: 'Source Sans Pro', Helvetica, Arial, sans-serif; font-size: 16px; color: #ffffff; text-decoration: none; border-radius: 6px;"
-                                               target="_blank">###BUTTON_TEXT###</a>
-                                        </td>
-                                    </tr>
-                                </table>
-                            </td>
-                        </tr>
-                    </table>
-                </td>
-            </tr>
-            <tr>
-                <td align="left" bgcolor="#ffffff"
-                    style="padding: 24px; font-family: 'Source Sans Pro', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 24px;">
-                    <p style="margin: 0;">###LINK_DESCRIPTION###</p>
-                    <p style="margin: 0;"><a href="###LINK###" target="_blank">###LINK###</a>
-                    </p>
+<table border="0" cellpadding="0" cellspacing="0" style="max-width: 600px;" width="100%">
+  <tr>
+    <td align="left" bgcolor="#ffffff"
+        style="padding: 24px; font-family: 'Source Sans Pro', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 24px;">
+      <p style="margin: 0;">###MANUAL###</p>
+    </td>
+  </tr>
+  <tr>
+    <td align="left" bgcolor="#ffffff">
+      <table border="0" cellpadding="0" cellspacing="0" width="100%">
+        <tr>
+          <td align="center" bgcolor="#ffffff" style="padding: 12px;">
+            <table border="0" cellpadding="0" cellspacing="0">
+              <tr>
+                <td align="center" bgcolor="#1b1464" style="border-radius: 6px;">
+                  <a href="###LINK###"
+                     style="display: inline-block; padding: 16px 36px; font-family: 'Source Sans Pro', Helvetica, Arial, sans-serif; font-size: 16px; color: #ffffff; text-decoration: none; border-radius: 6px;"
+                     target="_blank">###BUTTON_TEXT###</a>
                 </td>
-            </tr>
-        </table>
+              </tr>
+            </table>
+          </td>
+        </tr>
+      </table>
+    </td>
+  </tr>
+  <tr>
+    <td align="left" bgcolor="#ffffff"
+        style="padding: 24px; font-family: 'Source Sans Pro', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 24px;">
+      <p style="margin: 0;">###LINK_DESCRIPTION###</p>
+      <p style="margin: 0;"><a href="###LINK###" target="_blank">###LINK###</a>
+      </p>
     </td>
-</tr>
+  </tr>
+</table>
diff --git a/streampipes-mail/src/main/resources/mail-template-inner-plain.html b/streampipes-mail/src/main/resources/mail-template-inner-plain.html
index 90ef04ec93..6974a6d124 100644
--- a/streampipes-mail/src/main/resources/mail-template-inner-plain.html
+++ b/streampipes-mail/src/main/resources/mail-template-inner-plain.html
@@ -22,18 +22,17 @@
   ~ SOFTWARE.
 -->
 
-<tr>
-    <td align="center" bgcolor="#e9ecef">
-        <table align="center" border="0" cellpadding="0" cellspacing="0" width="600">
-            <tr>
-                <td align="center" valign="top" width="600">
-                    <table border="0" cellpadding="0" cellspacing="0" style="max-width: 600px;" width="100%">
-                        <tr>
-                            <td align="left" bgcolor="#ffffff"
-                                style="padding: 24px; font-family: 'Source Sans Pro', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 24px;">
-                                <p style="margin: 0;">###TEXT###</p>
-                            </td>
-                        </tr>
-                    </table>
-                </td>
-            </tr>
+<table align="center" border="0" cellpadding="0" cellspacing="0" width="600">
+  <tr>
+    <td align="center" valign="top" width="600">
+      <table border="0" cellpadding="0" cellspacing="0" style="max-width: 600px;" width="100%">
+        <tr>
+          <td align="left" bgcolor="#ffffff"
+              style="padding: 24px; font-family: 'Source Sans Pro', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 24px;">
+            <p style="margin: 0;">###TEXT###</p>
+          </td>
+        </tr>
+      </table>
+    </td>
+  </tr>
+</table>
diff --git a/streampipes-mail/src/main/resources/mail-template-outer.html b/streampipes-mail/src/main/resources/mail-template-outer.html
deleted file mode 100644
index 30eceded55..0000000000
--- a/streampipes-mail/src/main/resources/mail-template-outer.html
+++ /dev/null
@@ -1,154 +0,0 @@
-<!--
-  ~ MIT License
-  ~
-  ~ Copyright (c) 2018 SendGrid, Inc.
-  ~
-  ~ Permission is hereby granted, free of charge, to any person obtaining a copy
-  ~ of this software and associated documentation files (the "Software"), to deal
-  ~ in the Software without restriction, including without limitation the rights
-  ~ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-  ~ copies of the Software, and to permit persons to whom the Software is
-  ~ furnished to do so, subject to the following conditions:
-  ~
-  ~ The above copyright notice and this permission notice shall be included in all
-  ~ copies or substantial portions of the Software.
-  ~
-  ~ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-  ~ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-  ~ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-  ~ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-  ~ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-  ~ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-  ~ SOFTWARE.
--->
-
-<!DOCTYPE html>
-<html>
-<head>
-
-    <meta charset="utf-8">
-    <meta content="ie=edge" http-equiv="x-ua-compatible">
-    <title>###TITLE###</title>
-    <meta content="width=device-width, initial-scale=1" name="viewport">
-    <style type="text/css">
-        /**
-         * Google webfonts. Recommended to include the .woff version for cross-client compatibility.
-         */
-        @media screen {
-            @font-face {
-                font-family: 'Source Sans Pro';
-                font-style: normal;
-                font-weight: 400;
-                src: local('Source Sans Pro Regular'), local('SourceSansPro-Regular'), url(https://fonts.gstatic.com/s/sourcesanspro/v10/ODelI1aHBYDBqgeIAH2zlBM0YzuT7MdOe03otPbuUS0.woff) format('woff');
-            }
-
-            @font-face {
-                font-family: 'Source Sans Pro';
-                font-style: normal;
-                font-weight: 700;
-                src: local('Source Sans Pro Bold'), local('SourceSansPro-Bold'), url(https://fonts.gstatic.com/s/sourcesanspro/v10/toadOcfmlt9b38dHJxOBGFkQc6VGVFSmCnC_l7QZG60.woff) format('woff');
-            }
-        }
-
-        body,
-        table,
-        td,
-        a {
-            -ms-text-size-adjust: 100%; /* 1 */
-            -webkit-text-size-adjust: 100%; /* 2 */
-        }
-
-        table,
-        td {
-            mso-table-rspace: 0pt;
-            mso-table-lspace: 0pt;
-        }
-
-        img {
-            -ms-interpolation-mode: bicubic;
-        }
-
-        a[x-apple-data-detectors] {
-            font-family: inherit !important;
-            font-size: inherit !important;
-            font-weight: inherit !important;
-            line-height: inherit !important;
-            color: inherit !important;
-            text-decoration: none !important;
-        }
-
-        div[style*="margin: 16px 0;"] {
-            margin: 0 !important;
-        }
-
-        body {
-            width: 100% !important;
-            height: 100% !important;
-            padding: 0 !important;
-            margin: 0 !important;
-        }
-
-        table {
-            border-collapse: collapse !important;
-        }
-
-        a {
-            color: #1b1464;
-        }
-
-        img {
-            height: auto;
-            line-height: 100%;
-            text-decoration: none;
-            border: 0;
-            outline: none;
-        }
-
-    </style>
-
-</head>
-<body style="background-color: #e9ecef;">
-
-<div class="preheader"
-     style="display: none; max-width: 0; max-height: 0; overflow: hidden; font-size: 1px; line-height: 1px; color: #fff; opacity: 0;">
-    ###PREHEADER###
-</div>
-
-<table border="0" cellpadding="0" cellspacing="0" width="100%">
-
-    <tr>
-        <td align="center" bgcolor="#e9ecef">
-            <table border="0" cellpadding="0" cellspacing="0" style="max-width: 600px;" width="100%">
-                <tr>
-                    <td align="center" style="padding: 36px 24px;" valign="top">
-                        <a href="###BASE_URL###" style="display: inline-block;" target="_blank">
-                            <img alt="Logo" border="0" src="###LOGO###"
-                                 style="display: block; width: 150px; max-width: 150px; min-width: 150px;"
-                                 width="150">
-                        </a>
-                    </td>
-                </tr>
-            </table>
-        </td>
-    </tr>
-
-    <tr>
-        <td align="center" bgcolor="#e9ecef">
-            <table border="0" cellpadding="0" cellspacing="0" style="max-width: 600px;" width="100%">
-                <tr>
-                    <td align="left" bgcolor="#ffffff"
-                        style="padding: 36px 24px 0; font-family: 'Source Sans Pro', Helvetica, Arial, sans-serif; border-top: 3px solid #d4dadf;">
-                        <h1 style="margin: 0; font-size: 32px; font-weight: 700; letter-spacing: -1px; line-height: 48px;">
-                            ###TITLE###</h1>
-                    </td>
-                </tr>
-            </table>
-        </td>
-    </tr>
-
-    ###INNER###
-
-    ###FOOTER###
-</table>
-</body>
-</html>
diff --git a/streampipes-model/src/main/java/org/apache/streampipes/model/configuration/DefaultEmailTemplateConfiguration.java b/streampipes-model/src/main/java/org/apache/streampipes/model/configuration/DefaultEmailTemplateConfiguration.java
new file mode 100644
index 0000000000..2f1e37dd2d
--- /dev/null
+++ b/streampipes-model/src/main/java/org/apache/streampipes/model/configuration/DefaultEmailTemplateConfiguration.java
@@ -0,0 +1,58 @@
+/*
+ * 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.streampipes.model.configuration;
+
+import org.apache.streampipes.commons.resources.Resources;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import java.util.stream.Collectors;
+
+public class DefaultEmailTemplateConfiguration {
+
+  private static final Logger LOG = LoggerFactory.getLogger(DefaultEmailTemplateConfiguration.class);
+
+  private static final String DefaultTemplateFilename = "default-mail-template.html";
+
+  public EmailTemplateConfig getDefaultTemplates() {
+    String template = "";
+    try {
+      template = removeHeader(getTemplate());
+    } catch (IOException e) {
+      LOG.warn(
+          "Default email template could not be loaded - set this manually in the UI under Configuration->Mail"
+      );
+    }
+    return new EmailTemplateConfig(template);
+  }
+
+  private String getTemplate() throws IOException {
+    return Resources.asString(DefaultTemplateFilename, StandardCharsets.UTF_8);
+  }
+
+  private String removeHeader(String template) {
+    return Arrays.stream(template.split("\n"))
+        .skip(22)
+        .collect(Collectors.joining("\n"));
+  }
+}
diff --git a/streampipes-model/src/main/java/org/apache/streampipes/model/configuration/DefaultSpCoreConfiguration.java b/streampipes-model/src/main/java/org/apache/streampipes/model/configuration/DefaultSpCoreConfiguration.java
index 044d1f5c42..4f654bd992 100644
--- a/streampipes-model/src/main/java/org/apache/streampipes/model/configuration/DefaultSpCoreConfiguration.java
+++ b/streampipes-model/src/main/java/org/apache/streampipes/model/configuration/DefaultSpCoreConfiguration.java
@@ -32,6 +32,7 @@ public class DefaultSpCoreConfiguration {
     coreCfg.setGeneralConfig(new GeneralConfig());
     coreCfg.setMessagingSettings(new DefaultMessagingSettings().make());
     coreCfg.setEmailConfig(EmailConfig.fromDefaults());
+    coreCfg.setEmailTemplateConfig(new DefaultEmailTemplateConfiguration().getDefaultTemplates());
     coreCfg.setFilesDir(makeFileLocation());
     coreCfg.setAssetDir(makeAssetLocation());
     coreCfg.setLocalAuthConfig(LocalAuthConfig.fromDefaults(getJwtSecret()));
diff --git a/streampipes-model/src/main/java/org/apache/streampipes/model/configuration/EmailTemplateConfig.java b/streampipes-model/src/main/java/org/apache/streampipes/model/configuration/EmailTemplateConfig.java
new file mode 100644
index 0000000000..2e8d2df6e6
--- /dev/null
+++ b/streampipes-model/src/main/java/org/apache/streampipes/model/configuration/EmailTemplateConfig.java
@@ -0,0 +1,41 @@
+/*
+ * 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.streampipes.model.configuration;
+
+public class EmailTemplateConfig {
+
+  private String template;
+
+  public EmailTemplateConfig() {
+  }
+
+  public EmailTemplateConfig(String template) {
+    this.template = template;
+  }
+
+  public String getTemplate() {
+    return template;
+  }
+
+  public void setTemplate(String template) {
+    this.template = template;
+  }
+}
+
+
diff --git a/streampipes-model/src/main/java/org/apache/streampipes/model/configuration/SpCoreConfiguration.java b/streampipes-model/src/main/java/org/apache/streampipes/model/configuration/SpCoreConfiguration.java
index 32ea21d4ee..3e3bd251d0 100644
--- a/streampipes-model/src/main/java/org/apache/streampipes/model/configuration/SpCoreConfiguration.java
+++ b/streampipes-model/src/main/java/org/apache/streampipes/model/configuration/SpCoreConfiguration.java
@@ -30,6 +30,7 @@ public class SpCoreConfiguration {
   private MessagingSettings messagingSettings;
   private LocalAuthConfig localAuthConfig;
   private EmailConfig emailConfig;
+  private EmailTemplateConfig emailTemplateConfig;
   private GeneralConfig generalConfig;
 
   private boolean isConfigured;
@@ -111,4 +112,12 @@ public class SpCoreConfiguration {
   public void setFilesDir(String filesDir) {
     this.filesDir = filesDir;
   }
+
+  public EmailTemplateConfig getEmailTemplateConfig() {
+    return this.emailTemplateConfig;
+  }
+
+  public void setEmailTemplateConfig(EmailTemplateConfig emailTemplateConfig) {
+    this.emailTemplateConfig = emailTemplateConfig;
+  }
 }
diff --git a/streampipes-model/src/main/java/org/apache/streampipes/model/extensions/svcdiscovery/SpServiceTagPrefix.java b/streampipes-model/src/main/java/org/apache/streampipes/model/extensions/svcdiscovery/SpServiceTagPrefix.java
index 006a00af29..346e0b2224 100644
--- a/streampipes-model/src/main/java/org/apache/streampipes/model/extensions/svcdiscovery/SpServiceTagPrefix.java
+++ b/streampipes-model/src/main/java/org/apache/streampipes/model/extensions/svcdiscovery/SpServiceTagPrefix.java
@@ -23,8 +23,7 @@ public enum SpServiceTagPrefix {
   ADAPTER("adapter"),
   DATA_STREAM("dstream"),
   DATA_PROCESSOR("dprocessor"),
-  DATA_SINK("dsink"),
-  DATA_SET("dset");
+  DATA_SINK("dsink");
 
   private String prefix;
 
diff --git a/streampipes-model/src/main/java/org/apache/streampipes/model/mail/SpEmail.java b/streampipes-model/src/main/java/org/apache/streampipes/model/mail/SpEmail.java
index a1a1efd614..3fcf592cdf 100644
--- a/streampipes-model/src/main/java/org/apache/streampipes/model/mail/SpEmail.java
+++ b/streampipes-model/src/main/java/org/apache/streampipes/model/mail/SpEmail.java
@@ -22,6 +22,7 @@ import java.util.List;
 public class SpEmail {
 
   private List<String> recipients;
+  private String preheader;
   private String subject;
   private String message;
 
@@ -30,16 +31,25 @@ public class SpEmail {
 
   public SpEmail(List<String> recipients,
                  String subject,
+                 String preheader,
                  String message) {
     this.recipients = recipients;
     this.subject = subject;
+    this.preheader = preheader;
     this.message = message;
   }
 
   public static SpEmail from(List<String> recipients,
                              String subject,
                              String message) {
-    return new SpEmail(recipients, subject, message);
+    return new SpEmail(recipients, subject, "", message);
+  }
+
+  public static SpEmail from(List<String> recipients,
+                             String subject,
+                             String preheader,
+                             String message) {
+    return new SpEmail(recipients, subject, preheader, message);
   }
 
   public List<String> getRecipients() {
@@ -58,6 +68,14 @@ public class SpEmail {
     this.subject = subject;
   }
 
+  public String getPreheader() {
+    return preheader;
+  }
+
+  public void setPreheader(String preheader) {
+    this.preheader = preheader;
+  }
+
   public String getMessage() {
     return message;
   }
diff --git a/streampipes-model/src/main/resources/default-mail-template.html b/streampipes-model/src/main/resources/default-mail-template.html
new file mode 100644
index 0000000000..956658011b
--- /dev/null
+++ b/streampipes-model/src/main/resources/default-mail-template.html
@@ -0,0 +1,150 @@
+<!--
+  ~ 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.
+  ~
+  -->
+<!--
+  When changing the default template, the first 22 lines of this file (license header + blank line) will be removed.
+  Make sure to keep these lines or change the settings in DefaultEmailTemplateConfiguration
+-->
+
+<!DOCTYPE html>
+<html>
+<head>
+
+  <meta charset="utf-8">
+  <meta content="ie=edge" http-equiv="x-ua-compatible">
+  <title>###TITLE###</title>
+  <meta content="width=device-width, initial-scale=1" name="viewport">
+  <style type="text/css">
+      body,
+      table,
+      td,
+      a {
+          -ms-text-size-adjust: 100%; /* 1 */
+          -webkit-text-size-adjust: 100%; /* 2 */
+      }
+
+      table,
+      td {
+          mso-table-rspace: 0pt;
+          mso-table-lspace: 0pt;
+      }
+
+      img {
+          -ms-interpolation-mode: bicubic;
+      }
+
+      a[x-apple-data-detectors] {
+          font-family: inherit !important;
+          font-size: inherit !important;
+          font-weight: inherit !important;
+          line-height: inherit !important;
+          color: inherit !important;
+          text-decoration: none !important;
+      }
+
+      div[style*="margin: 16px 0;"] {
+          margin: 0 !important;
+      }
+
+      body {
+          width: 100% !important;
+          height: 100% !important;
+          padding: 0 !important;
+          margin: 0 !important;
+      }
+
+      table {
+          border-collapse: collapse !important;
+      }
+
+      a {
+          color: #1b1464;
+      }
+
+      img {
+          height: auto;
+          line-height: 100%;
+          text-decoration: none;
+          border: 0;
+          outline: none;
+      }
+
+  </style>
+
+</head>
+<body style="background-color: #e9ecef;">
+
+<div class="preheader"
+     style="display: none; max-width: 0; max-height: 0; overflow: hidden; font-size: 1px; line-height: 1px; color: #fff; opacity: 0;">
+  ###PREHEADER###
+</div>
+
+<table border="0" cellpadding="0" cellspacing="0" width="100%">
+
+  <tr>
+    <td align="center" bgcolor="#e9ecef">
+      <table border="0" cellpadding="0" cellspacing="0" style="max-width: 600px;" width="100%">
+        <tr>
+          <td align="center" style="padding: 36px 24px;" valign="top">
+            <a href="###BASE_URL###" style="display: inline-block;" target="_blank">
+              <img alt="Logo" border="0" src="###LOGO###"
+                   style="display: block; width: 150px; max-width: 150px; min-width: 150px;"
+                   width="150">
+            </a>
+          </td>
+        </tr>
+      </table>
+    </td>
+  </tr>
+
+  <tr>
+    <td align="center" bgcolor="#e9ecef">
+      <table border="0" cellpadding="0" cellspacing="0" style="max-width: 600px;" width="100%">
+        <tr>
+          <td align="left" bgcolor="#ffffff"
+              style="padding: 36px 24px 0; font-family: 'Source Sans Pro', Helvetica, Arial, sans-serif; border-top: 3px solid #d4dadf;">
+            <h1 style="margin: 0; font-size: 32px; font-weight: 700; letter-spacing: -1px; line-height: 48px;">
+              ###TITLE###</h1>
+          </td>
+        </tr>
+      </table>
+    </td>
+  </tr>
+
+  <tr>
+    <td align="center" bgcolor="#e9ecef">
+      ###INNER###
+    </td>
+  </tr>
+  <tr>
+    <td align="center" bgcolor="#e9ecef" style="padding: 24px;">
+      <table border="0" cellpadding="0" cellspacing="0" style="max-width: 600px;" width="100%">
+        <tr>
+          <td align="center" bgcolor="#e9ecef"
+              style="padding: 12px 24px; font-family: 'Source Sans Pro', Helvetica, Arial, sans-serif; font-size: 14px; line-height: 20px; color: #666;">
+            <p style="margin: 0;">This mail was automatically sent from a local installation of Apache
+              StreamPipes - contact your administrator if you think you shouldn't receive this mail.</p>
+            <p>This mail is not sent by the Apache StreamPipes team.</p>
+            </p>
+          </td>
+        </tr>
+      </table>
+    </td>
+  </tr>
+</table>
+</body>
+</html>
diff --git a/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/admin/EmailConfigurationResource.java b/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/admin/EmailConfigurationResource.java
index a14e8f7bcf..6b6f646975 100644
--- a/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/admin/EmailConfigurationResource.java
+++ b/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/admin/EmailConfigurationResource.java
@@ -19,6 +19,7 @@ package org.apache.streampipes.rest.impl.admin;
 
 import org.apache.streampipes.mail.MailTester;
 import org.apache.streampipes.model.configuration.EmailConfig;
+import org.apache.streampipes.model.configuration.EmailTemplateConfig;
 import org.apache.streampipes.rest.core.base.impl.AbstractAuthGuardedRestResource;
 import org.apache.streampipes.rest.security.AuthConstants;
 import org.apache.streampipes.rest.shared.annotation.JacksonSerialized;
@@ -51,6 +52,27 @@ public class EmailConfigurationResource extends AbstractAuthGuardedRestResource
     return ok(getSpCoreConfigurationStorage().get().getEmailConfig());
   }
 
+  @GET
+  @Path("templates")
+  @JacksonSerialized
+  @Produces(MediaType.APPLICATION_JSON)
+  @PreAuthorize(AuthConstants.IS_ADMIN_ROLE)
+  public Response getMailTemplates() {
+    return ok(getSpCoreConfigurationStorage().get().getEmailTemplateConfig());
+  }
+
+  @PUT
+  @Path("templates")
+  @JacksonSerialized
+  @Consumes(MediaType.APPLICATION_JSON)
+  @PreAuthorize(AuthConstants.IS_ADMIN_ROLE)
+  public Response updateMailTemplate(EmailTemplateConfig templateConfig) {
+    var config = getSpCoreConfigurationStorage().get();
+    config.setEmailTemplateConfig(templateConfig);
+    getSpCoreConfigurationStorage().updateElement(config);
+    return ok();
+  }
+
   @PUT
   @JacksonSerialized
   @Consumes(MediaType.APPLICATION_JSON)
diff --git a/streampipes-service-core/src/main/java/org/apache/streampipes/service/core/migrations/AvailableMigrations.java b/streampipes-service-core/src/main/java/org/apache/streampipes/service/core/migrations/AvailableMigrations.java
index 3336f462de..ce673b5e5c 100644
--- a/streampipes-service-core/src/main/java/org/apache/streampipes/service/core/migrations/AvailableMigrations.java
+++ b/streampipes-service-core/src/main/java/org/apache/streampipes/service/core/migrations/AvailableMigrations.java
@@ -25,6 +25,7 @@ import org.apache.streampipes.service.core.migrations.v070.CreateFileAssetTypeMi
 import org.apache.streampipes.service.core.migrations.v090.UpdateUsernameViewMigration;
 import org.apache.streampipes.service.core.migrations.v093.AdapterMigration;
 import org.apache.streampipes.service.core.migrations.v093.ConsulConfigMigration;
+import org.apache.streampipes.service.core.migrations.v093.StoreEmailTemplatesMigration;
 
 import java.util.Arrays;
 import java.util.List;
@@ -38,7 +39,8 @@ public class AvailableMigrations {
         new CreateFileAssetTypeMigration(),
         new UpdateUsernameViewMigration(),
         new AdapterMigration(),
-        new ConsulConfigMigration()
+        new ConsulConfigMigration(),
+        new StoreEmailTemplatesMigration()
     );
   }
 }
diff --git a/streampipes-service-core/src/main/java/org/apache/streampipes/service/core/migrations/v093/StoreEmailTemplatesMigration.java b/streampipes-service-core/src/main/java/org/apache/streampipes/service/core/migrations/v093/StoreEmailTemplatesMigration.java
new file mode 100644
index 0000000000..534a070b48
--- /dev/null
+++ b/streampipes-service-core/src/main/java/org/apache/streampipes/service/core/migrations/v093/StoreEmailTemplatesMigration.java
@@ -0,0 +1,58 @@
+/*
+ * 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.streampipes.service.core.migrations.v093;
+
+import org.apache.streampipes.model.configuration.DefaultEmailTemplateConfiguration;
+import org.apache.streampipes.service.core.migrations.Migration;
+import org.apache.streampipes.storage.api.ISpCoreConfigurationStorage;
+import org.apache.streampipes.storage.management.StorageDispatcher;
+
+import java.io.IOException;
+
+public class StoreEmailTemplatesMigration implements Migration {
+
+  private final ISpCoreConfigurationStorage storage;
+
+  public StoreEmailTemplatesMigration() {
+    this.storage = StorageDispatcher.INSTANCE.getNoSqlStore().getSpCoreConfigurationStorage();
+  }
+
+  @Override
+  public boolean shouldExecute() {
+    var config = storage.get();
+    if (config == null) {
+      return false;
+    } else {
+      return config.getEmailTemplateConfig() == null;
+    }
+  }
+
+  @Override
+  public void executeMigration() throws IOException {
+    var config = storage.get();
+    config.setEmailTemplateConfig(new DefaultEmailTemplateConfiguration().getDefaultTemplates());
+
+    storage.updateElement(config);
+  }
+
+  @Override
+  public String getDescription() {
+    return "Moving email templates to configuration storage";
+  }
+}
diff --git a/streampipes-service-discovery-api/src/main/java/org/apache/streampipes/svcdiscovery/api/model/SpServicePathPrefix.java b/streampipes-service-discovery-api/src/main/java/org/apache/streampipes/svcdiscovery/api/model/SpServicePathPrefix.java
index 970ca735ee..573563a325 100644
--- a/streampipes-service-discovery-api/src/main/java/org/apache/streampipes/svcdiscovery/api/model/SpServicePathPrefix.java
+++ b/streampipes-service-discovery-api/src/main/java/org/apache/streampipes/svcdiscovery/api/model/SpServicePathPrefix.java
@@ -22,7 +22,6 @@ public class SpServicePathPrefix {
   public static final String DATA_PROCESSOR = "sepa";
   public static final String DATA_SINK = "sec";
   public static final String DATA_STREAM = "stream";
-  public static final String DATA_SET = "set";
   public static final String ADAPTER = "api/v1/worker/adapters";
   public static final String PROTOCOL = "api/v1/worker/protocols";
 
diff --git a/streampipes-service-discovery-api/src/main/java/org/apache/streampipes/svcdiscovery/api/model/SpServiceUrlProvider.java b/streampipes-service-discovery-api/src/main/java/org/apache/streampipes/svcdiscovery/api/model/SpServiceUrlProvider.java
index 0e0da154e8..892086d073 100644
--- a/streampipes-service-discovery-api/src/main/java/org/apache/streampipes/svcdiscovery/api/model/SpServiceUrlProvider.java
+++ b/streampipes-service-discovery-api/src/main/java/org/apache/streampipes/svcdiscovery/api/model/SpServiceUrlProvider.java
@@ -25,7 +25,6 @@ public enum SpServiceUrlProvider {
   DATA_PROCESSOR(SpServicePathPrefix.DATA_PROCESSOR, SpServiceTagPrefix.DATA_PROCESSOR),
   DATA_SINK(SpServicePathPrefix.DATA_SINK, SpServiceTagPrefix.DATA_SINK),
   DATA_STREAM(SpServicePathPrefix.DATA_STREAM, SpServiceTagPrefix.DATA_STREAM),
-  DATA_SET(SpServicePathPrefix.DATA_SET, SpServiceTagPrefix.DATA_SET),
   ADAPTER(SpServicePathPrefix.ADAPTER, SpServiceTagPrefix.ADAPTER);
 
   private final String http = "http://";
diff --git a/ui/deployment/_variables.scss b/ui/deployment/_variables.scss
index c8de0a5a40..c91264f819 100644
--- a/ui/deployment/_variables.scss
+++ b/ui/deployment/_variables.scss
@@ -28,7 +28,6 @@ $sp-color-accent-light-transparent: rgba(156, 156, 156, 0.4);
 $sp-color-accent-dark: #83a3de;
 
 $sp-color-stream: #ffeb3b;
-$sp-color-set: #ffa23b;
 $sp-color-processor: #009688;
 $sp-color-sink: #3f51b5;
 
@@ -38,7 +37,7 @@ body {
     --color-data-view: rgb(122, 206, 227);
     --color-dashboard: rgb(76, 115, 164);
     --color-adapter: rgb(182, 140, 97);
-    --color-data-source: #ffa23b;
+    --color-data-source: #ffeb3b;
     --color-pipeline: rgb(102, 185, 114);
     --color-measurement: rgb(39, 164, 155);
     --color-file: rgb(163, 98, 190);
diff --git a/ui/projects/streampipes/platform-services/src/lib/apis/mail-config.service.ts b/ui/projects/streampipes/platform-services/src/lib/apis/mail-config.service.ts
index afc270735d..a90e98897b 100644
--- a/ui/projects/streampipes/platform-services/src/lib/apis/mail-config.service.ts
+++ b/ui/projects/streampipes/platform-services/src/lib/apis/mail-config.service.ts
@@ -21,7 +21,7 @@ import { HttpClient } from '@angular/common/http';
 import { map } from 'rxjs/operators';
 import { Observable } from 'rxjs';
 import { PlatformServicesCommons } from './commons.service';
-import { EmailConfig } from '../model/email-config.model';
+import { EmailConfig, EmailTemplate } from '../model/email-config.model';
 
 @Injectable({
     providedIn: 'root',
@@ -38,10 +38,20 @@ export class MailConfigService {
             .pipe(map(response => response as EmailConfig));
     }
 
+    getMailTemplate(): Observable<EmailTemplate> {
+        return this.http
+            .get(`${this.mailConfigPath}/templates`)
+            .pipe(map(response => response as EmailTemplate));
+    }
+
     updateMailConfig(config: EmailConfig): Observable<any> {
         return this.http.put(this.mailConfigPath, config);
     }
 
+    updateMailTemplate(template: EmailTemplate): Observable<any> {
+        return this.http.put(`${this.mailConfigPath}/templates`, template);
+    }
+
     sendTestMail(config: EmailConfig) {
         return this.http.post(`${this.mailConfigPath}/test`, config);
     }
diff --git a/ui/projects/streampipes/platform-services/src/lib/model/email-config.model.ts b/ui/projects/streampipes/platform-services/src/lib/model/email-config.model.ts
index 638b61a778..fea8fefb68 100644
--- a/ui/projects/streampipes/platform-services/src/lib/model/email-config.model.ts
+++ b/ui/projects/streampipes/platform-services/src/lib/model/email-config.model.ts
@@ -44,3 +44,7 @@ export interface EmailConfig {
     smtpPassEncrypted: boolean;
     proxyPassEncrypted: boolean;
 }
+
+export interface EmailTemplate {
+    template: string;
+}
diff --git a/ui/projects/streampipes/platform-services/src/lib/model/gen/streampipes-model.ts b/ui/projects/streampipes/platform-services/src/lib/model/gen/streampipes-model.ts
index 36a810b513..04a5fb54cf 100644
--- a/ui/projects/streampipes/platform-services/src/lib/model/gen/streampipes-model.ts
+++ b/ui/projects/streampipes/platform-services/src/lib/model/gen/streampipes-model.ts
@@ -20,7 +20,7 @@
 /* tslint:disable */
 /* eslint-disable */
 // @ts-nocheck
-// Generated using typescript-generator version 3.1.1185 on 2023-08-06 11:37:37.
+// Generated using typescript-generator version 3.2.1263 on 2023-10-19 12:31:37.
 
 export class NamedStreamPipesEntity {
     '@class':
@@ -198,6 +198,7 @@ export class TransformationRuleDescription {
         | 'org.apache.streampipes.model.connect.rules.schema.DeleteRuleDescription'
         | 'org.apache.streampipes.model.connect.rules.schema.RenameRuleDescription'
         | 'org.apache.streampipes.model.connect.rules.schema.MoveRuleDescription';
+    'rulePriority': number;
 
     static 'fromData'(
         data: TransformationRuleDescription,
@@ -208,6 +209,7 @@ export class TransformationRuleDescription {
         }
         const instance = target || new TransformationRuleDescription();
         instance['@class'] = data['@class'];
+        instance.rulePriority = data.rulePriority;
         return instance;
     }
 
@@ -3521,6 +3523,7 @@ export class SpServiceRegistration {
     scheme: string;
     svcGroup: string;
     svcId: string;
+    svcType: string;
     tags: SpServiceTag[];
 
     static fromData(
@@ -3540,6 +3543,7 @@ export class SpServiceRegistration {
         instance.scheme = data.scheme;
         instance.svcGroup = data.svcGroup;
         instance.svcId = data.svcId;
+        instance.svcType = data.svcType;
         instance.tags = __getCopyArrayFn(SpServiceTag.fromData)(data.tags);
         return instance;
     }
diff --git a/ui/src/app/add/components/endpoint-item/endpoint-item.component.ts b/ui/src/app/add/components/endpoint-item/endpoint-item.component.ts
index f77b1cedf1..cbf9f25952 100644
--- a/ui/src/app/add/components/endpoint-item/endpoint-item.component.ts
+++ b/ui/src/app/add/components/endpoint-item/endpoint-item.component.ts
@@ -112,8 +112,6 @@ export class EndpointItemComponent implements OnInit {
         const baseType = 'pe-label ';
         if (this.item.type === 'stream') {
             this.itemTypeStyle = baseType + 'stream-label';
-        } else if (this.item.type === 'set') {
-            this.itemTypeStyle = baseType + 'set-label';
         } else if (this.item.type === 'sepa') {
             this.itemTypeStyle = baseType + 'processor-label';
         } else {
diff --git a/ui/src/app/assets/dialog/edit-asset-link/edit-asset-link-dialog.component.html b/ui/src/app/assets/dialog/edit-asset-link/edit-asset-link-dialog.component.html
index b65fc359f1..5e7a3246f8 100644
--- a/ui/src/app/assets/dialog/edit-asset-link/edit-asset-link-dialog.component.html
+++ b/ui/src/app/assets/dialog/edit-asset-link/edit-asset-link-dialog.component.html
@@ -72,7 +72,7 @@
                 class="link-configuration"
             >
                 <mat-form-field color="accent" fxFlex="100">
-                    <mat-label>Data Source</mat-label>
+                    <mat-label>Data Stream</mat-label>
                     <mat-select
                         (selectionChange)="
                             changeLabel(
diff --git a/ui/src/app/assets/dialog/manage-asset-links/manage-asset-links-dialog.component.html b/ui/src/app/assets/dialog/manage-asset-links/manage-asset-links-dialog.component.html
index 2acb401b42..d27e5d4f43 100644
--- a/ui/src/app/assets/dialog/manage-asset-links/manage-asset-links-dialog.component.html
+++ b/ui/src/app/assets/dialog/manage-asset-links/manage-asset-links-dialog.component.html
@@ -190,7 +190,7 @@
                 <div fxLayout="row" fxLayoutAlign="start center" class="mb-10">
                     <div fxLayout="row" fxLayoutAlign="start center">
                         <span class="general-options-header mb-0"
-                            >Data Sources</span
+                            >Data Streams</span
                         >
                     </div>
                     <div fxLayout="row" fxLayoutAlign="end center" fxFlex>
diff --git a/ui/src/app/configuration/configuration.module.ts b/ui/src/app/configuration/configuration.module.ts
index f0cc6b5915..05efe24c59 100644
--- a/ui/src/app/configuration/configuration.module.ts
+++ b/ui/src/app/configuration/configuration.module.ts
@@ -65,6 +65,8 @@ import { SpRegisteredExtensionsServiceComponent } from './extensions-service-man
 import { SpExtensionsServiceConfigurationComponent } from './extensions-service-management/extensions-service-configuration/extensions-service-configuration.component';
 import { SpMessagingBrokerConfigComponent } from './messaging-configuration/broker-config/broker-config.component';
 import { SpExtensionsServiceDetailsDialogComponent } from './dialog/extensions-service-details/extensions-service-details-dialog.component';
+import { SpEmailTemplateConfigurationComponent } from './email-configuration/email-template-configuration/email-template-configuration.component';
+import { CodemirrorModule } from '@ctrl/ngx-codemirror';
 
 @NgModule({
     imports: [
@@ -130,6 +132,7 @@ import { SpExtensionsServiceDetailsDialogComponent } from './dialog/extensions-s
         ]),
         SharedUiModule,
         ColorPickerModule,
+        CodemirrorModule,
     ],
     declarations: [
         ServiceConfigsComponent,
@@ -155,6 +158,7 @@ import { SpExtensionsServiceDetailsDialogComponent } from './dialog/extensions-s
         SpDataExportItemComponent,
         SpDataImportDialogComponent,
         SpEditLabelComponent,
+        SpEmailTemplateConfigurationComponent,
         SpExtensionsServiceDetailsDialogComponent,
         SpLabelConfigurationComponent,
         SpMessagingBrokerConfigComponent,
diff --git a/ui/src/app/configuration/email-configuration/email-configuration.component.html b/ui/src/app/configuration/email-configuration/email-configuration.component.html
index fd2d87cb96..c941aeb583 100644
--- a/ui/src/app/configuration/email-configuration/email-configuration.component.html
+++ b/ui/src/app/configuration/email-configuration/email-configuration.component.html
@@ -216,6 +216,12 @@
                 </form>
             </sp-split-section>
         </div>
+        <div fxFlex="100" fxLayout="column" fxLayoutAlign="start start">
+            <sp-email-template-configuration
+                fxFlex="100"
+                class="w-100"
+            ></sp-email-template-configuration>
+        </div>
         <mat-divider></mat-divider>
     </div>
 </sp-basic-nav-tabs>
diff --git a/ui/src/app/configuration/email-configuration/email-template-configuration/email-template-configuration.component.html b/ui/src/app/configuration/email-configuration/email-template-configuration/email-template-configuration.component.html
new file mode 100644
index 0000000000..9ddfe8ca7c
--- /dev/null
+++ b/ui/src/app/configuration/email-configuration/email-template-configuration/email-template-configuration.component.html
@@ -0,0 +1,87 @@
+<!--
+  ~ 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.
+  ~
+  -->
+
+<div fxFlex="100">
+    <sp-split-section
+        title="Mail Template"
+        subtitle="Email Template Configuration"
+    >
+        <div fxFlex="100" fxLayout="column">
+            <div fxFlex="100" fxLayout="row" fxLayoutAlign="start center">
+                <button
+                    mat-button
+                    mat-raised-button
+                    color="accent"
+                    class="small-button"
+                    (click)="restoreTemplate()"
+                    style="margin-right: 10px; margin-bottom: 10px"
+                >
+                    Reset changes
+                </button>
+            </div>
+            <div fxLayout="row" fxLayoutAlign="start center">
+                <div class="placeholder-explanation">
+                    <div fxLayout="column">
+                        <div>
+                            You can set various placeholder variables that will
+                            be replaced with the actual values when sending an
+                            email:
+                        </div>
+                        <div
+                            fxLayout="row wrap"
+                            class="mt-10"
+                            fxLayoutGap="5px"
+                        >
+                            <div
+                                class="placeholder-item"
+                                *ngFor="let placeholder of allowedPlaceholders"
+                            >
+                                <span class="placeholder-variable">{{
+                                    placeholder.placeholder
+                                }}</span>
+                                <span class="placeholder-description">{{
+                                    placeholder.description
+                                }}</span>
+                            </div>
+                        </div>
+                    </div>
+                </div>
+            </div>
+            <div fxFlex="100" fxLayout="row">
+                <ngx-codemirror
+                    [(ngModel)]="template.template"
+                    style="width: 100%"
+                    [options]="editorOptions"
+                >
+                </ngx-codemirror>
+            </div>
+            <div fxLayout="row" class="mt-10">
+                <button
+                    mat-button
+                    mat-raised-button
+                    color="accent"
+                    (click)="saveTemplate()"
+                    style="margin-right: 10px"
+                    [disabled]="template.template === ''"
+                >
+                    <i class="material-icons">save</i><span>&nbsp;Save</span>
+                </button>
+            </div>
+        </div>
+    </sp-split-section>
+</div>
diff --git a/ui/src/app/configuration/email-configuration/email-template-configuration/email-template-configuration.component.scss b/ui/src/app/configuration/email-configuration/email-template-configuration/email-template-configuration.component.scss
new file mode 100644
index 0000000000..65b0f3963b
--- /dev/null
+++ b/ui/src/app/configuration/email-configuration/email-template-configuration/email-template-configuration.component.scss
@@ -0,0 +1,40 @@
+/*!
+ * 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.
+ *
+ */
+
+.placeholder-explanation {
+    padding: 8px;
+    border: 1px solid var(--color-accent);
+    background: var(--color-bg-1);
+    margin-bottom: 10px;
+    width: 100%;
+}
+
+.placeholder-item {
+    padding: 3px;
+    border-radius: 5px;
+    border: 1px solid var(--color-default-text);
+    margin: 3px 5px;
+}
+
+.placeholder-variable {
+    margin-right: 5px;
+    font-weight: bold;
+}
+
+.placeholder-description {
+}
diff --git a/ui/src/app/configuration/email-configuration/email-template-configuration/email-template-configuration.component.ts b/ui/src/app/configuration/email-configuration/email-template-configuration/email-template-configuration.component.ts
new file mode 100644
index 0000000000..e138a5538c
--- /dev/null
+++ b/ui/src/app/configuration/email-configuration/email-template-configuration/email-template-configuration.component.ts
@@ -0,0 +1,92 @@
+/*
+ * 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.
+ *
+ */
+
+import { Component, OnInit } from '@angular/core';
+import { UntypedFormBuilder } from '@angular/forms';
+import {
+    EmailTemplate,
+    MailConfigService,
+} from '@streampipes/platform-services';
+
+import 'codemirror/mode/htmlembedded/htmlembedded';
+
+@Component({
+    selector: 'sp-email-template-configuration',
+    templateUrl: './email-template-configuration.component.html',
+    styleUrls: ['./email-template-configuration.component.scss'],
+})
+export class SpEmailTemplateConfigurationComponent implements OnInit {
+    template: EmailTemplate;
+    originalTemplate: string;
+    templateLoaded = false;
+    templateStored = false;
+
+    editorOptions = {
+        mode: 'htmlembedded',
+        autoRefresh: true,
+        theme: 'dracula',
+        autoCloseBrackets: true,
+        lineNumbers: true,
+        lineWrapping: true,
+        gutters: ['CodeMirror-lint-markers'],
+        lint: true,
+    };
+
+    allowedPlaceholders: { placeholder: string; description: string }[] = [
+        { placeholder: '###LOGO###', description: 'The default logo' },
+        { placeholder: '###BASE_URL###', description: 'The base URL' },
+        { placeholder: '###TITLE###', description: 'Email title' },
+        { placeholder: '###PREHEADER###', description: 'Email preheader' },
+        {
+            placeholder: '###INNER###',
+            description: 'Email custom inner content (mandatory)',
+        },
+    ];
+
+    constructor(
+        private fb: UntypedFormBuilder,
+        private mailConfigService: MailConfigService,
+    ) {}
+
+    ngOnInit(): void {
+        this.loadTemplate();
+    }
+
+    loadTemplate(): void {
+        this.templateLoaded = false;
+        this.mailConfigService.getMailTemplate().subscribe(template => {
+            this.originalTemplate = template.template;
+            this.template = template;
+            this.templateLoaded = true;
+        });
+    }
+
+    restoreTemplate(): void {
+        this.template.template = this.originalTemplate;
+    }
+
+    saveTemplate(): void {
+        this.templateStored = false;
+        this.mailConfigService
+            .updateMailTemplate(this.template)
+            .subscribe(() => {
+                this.templateStored = true;
+                this.loadTemplate();
+            });
+    }
+}
diff --git a/ui/src/app/configuration/export/export-dialog/data-export-dialog.component.html b/ui/src/app/configuration/export/export-dialog/data-export-dialog.component.html
index b4682524a4..9be2f957fc 100644
--- a/ui/src/app/configuration/export/export-dialog/data-export-dialog.component.html
+++ b/ui/src/app/configuration/export/export-dialog/data-export-dialog.component.html
@@ -40,7 +40,7 @@
                 ></sp-data-export-item>
                 <sp-data-export-item
                     [exportItems]="config.dataSources"
-                    sectionTitle="Data Sources"
+                    sectionTitle="Data Streams"
                 ></sp-data-export-item>
                 <sp-data-export-item
                     [exportItems]="config.dataLakeMeasures"
diff --git a/ui/src/app/configuration/export/import-dialog/data-import-dialog.component.html b/ui/src/app/configuration/export/import-dialog/data-import-dialog.component.html
index 6e0bff5244..9e60148469 100644
--- a/ui/src/app/configuration/export/import-dialog/data-import-dialog.component.html
+++ b/ui/src/app/configuration/export/import-dialog/data-import-dialog.component.html
@@ -96,7 +96,7 @@
             ></sp-data-export-item>
             <sp-data-export-item
                 [exportItems]="importConfiguration.dataSources"
-                sectionTitle="Data Sources"
+                sectionTitle="Data Streams"
             ></sp-data-export-item>
             <sp-data-export-item
                 [exportItems]="importConfiguration.dataLakeMeasures"
diff --git a/ui/src/app/connect/components/adapter-configuration/schema-editor/error-message/error-message.component.html b/ui/src/app/connect/components/adapter-configuration/schema-editor/error-message/error-message.component.html
index 254695234d..bdd48b8862 100644
--- a/ui/src/app/connect/components/adapter-configuration/schema-editor/error-message/error-message.component.html
+++ b/ui/src/app/connect/components/adapter-configuration/schema-editor/error-message/error-message.component.html
@@ -20,7 +20,7 @@
     <div fxLayout="row" fxLayoutAlign="center center" fxFlex="100">
         <div fxLayoutAlign="start center" class="error-text">
             &nbsp;There was an error while guessing the schema of your
-            configured data source:
+            configured data stream:
         </div>
     </div>
     <div fxLayout="row" fxLayoutAlign="center center" class="mt-10">
diff --git a/ui/src/app/connect/components/adapter-configuration/schema-editor/event-schema/event-schema.component.scss b/ui/src/app/connect/components/adapter-configuration/schema-editor/event-schema/event-schema.component.scss
index 3fd9bb2b24..170574abd8 100644
--- a/ui/src/app/connect/components/adapter-configuration/schema-editor/event-schema/event-schema.component.scss
+++ b/ui/src/app/connect/components/adapter-configuration/schema-editor/event-schema/event-schema.component.scss
@@ -135,10 +135,6 @@ ul {
     opacity: 1 !important;
 }
 
-.historic {
-    cursor: pointer;
-    color: #ffa23b;
-}
 .real-time {
     cursor: pointer;
     color: #ffeb3b;
diff --git a/ui/src/app/connect/components/data-marketplace/adapter-description/adapter-description.component.scss b/ui/src/app/connect/components/data-marketplace/adapter-description/adapter-description.component.scss
index 9390988753..927230b630 100644
--- a/ui/src/app/connect/components/data-marketplace/adapter-description/adapter-description.component.scss
+++ b/ui/src/app/connect/components/data-marketplace/adapter-description/adapter-description.component.scss
@@ -58,10 +58,6 @@
     max-height: 50px;
     max-width: 80%;
 }
-.historic {
-    cursor: pointer;
-    color: #ffa23b;
-}
 .real-time {
     cursor: pointer;
     color: #ffeb3b;
diff --git a/ui/src/app/connect/components/existing-adapters/existing-adapters.component.scss b/ui/src/app/connect/components/existing-adapters/existing-adapters.component.scss
index b083b3e02d..fedeedba79 100644
--- a/ui/src/app/connect/components/existing-adapters/existing-adapters.component.scss
+++ b/ui/src/app/connect/components/existing-adapters/existing-adapters.component.scss
@@ -30,11 +30,6 @@
 .adapter-icon {
     max-height: 50px;
 }
-
-.historic {
-    cursor: pointer;
-    color: #ffa23b;
-}
 .real-time {
     cursor: pointer;
     color: #ffeb3b;
diff --git a/ui/src/app/editor/components/pipeline-element-icon-stand/pipeline-element-icon-stand.component.ts b/ui/src/app/editor/components/pipeline-element-icon-stand/pipeline-element-icon-stand.component.ts
index 7754f1ad36..e93281cde6 100644
--- a/ui/src/app/editor/components/pipeline-element-icon-stand/pipeline-element-icon-stand.component.ts
+++ b/ui/src/app/editor/components/pipeline-element-icon-stand/pipeline-element-icon-stand.component.ts
@@ -44,11 +44,8 @@ export class PipelineElementIconStandComponent
 {
     availableTypes = [
         {
-            title: 'Data Sources',
-            filters: [
-                PipelineElementType.DataStream,
-                PipelineElementType.DataSet,
-            ],
+            title: 'Data Streams',
+            filters: [PipelineElementType.DataStream],
             open: true,
             color: 'var(--color-stream)',
             sort: 'name',
diff --git a/ui/src/app/editor/model/editor.model.ts b/ui/src/app/editor/model/editor.model.ts
index 5d6114fc84..57f939eba6 100644
--- a/ui/src/app/editor/model/editor.model.ts
+++ b/ui/src/app/editor/model/editor.model.ts
@@ -64,7 +64,6 @@ export interface PipelineElementRecommendationLayout {
 }
 
 export enum PipelineElementType {
-    DataSet,
     DataStream,
     DataProcessor,
     DataSink,
diff --git a/ui/src/app/editor/utils/editor.utils.ts b/ui/src/app/editor/utils/editor.utils.ts
index c3138798f2..9bc74654bb 100644
--- a/ui/src/app/editor/utils/editor.utils.ts
+++ b/ui/src/app/editor/utils/editor.utils.ts
@@ -51,8 +51,6 @@ export class PipelineElementTypeUtils {
     static toCssShortHand(elementType: PipelineElementType) {
         if (PipelineElementType.DataStream === elementType) {
             return 'stream';
-        } else if (PipelineElementType.DataSet === elementType) {
-            return 'set';
         } else if (PipelineElementType.DataProcessor === elementType) {
             return 'sepa';
         } else {
diff --git a/ui/src/app/services/tour/adapter-tour-2.constants.ts b/ui/src/app/services/tour/adapter-tour-2.constants.ts
index 8a9e520040..bdbfc5103e 100644
--- a/ui/src/app/services/tour/adapter-tour-2.constants.ts
+++ b/ui/src/app/services/tour/adapter-tour-2.constants.ts
@@ -115,7 +115,7 @@ export default {
             {
                 stepId: 'step-9',
                 title: 'Congratulation',
-                text: 'Congratulation you have created your first adapter and finished the tutorial. Go to the pipeline editor to see the new data source',
+                text: 'Congratulation you have created your first adapter and finished the tutorial. Go to the pipeline editor to see the new data stream',
                 classes: 'shepherd shepherd-welcome',
                 buttons: ['cancel'],
             },
diff --git a/ui/src/app/services/tour/adapter-tour-3.constants.ts b/ui/src/app/services/tour/adapter-tour-3.constants.ts
index dc26631ed5..857ee121d0 100644
--- a/ui/src/app/services/tour/adapter-tour-3.constants.ts
+++ b/ui/src/app/services/tour/adapter-tour-3.constants.ts
@@ -90,7 +90,7 @@ export default {
             {
                 stepId: 'step-9',
                 title: 'Congratulation',
-                text: 'Congratulation you have created your second adapter and finished the tutorial. Go to the pipeline editor to see the new data source',
+                text: 'Congratulation you have created your second adapter and finished the tutorial. Go to the pipeline editor to see the new data stream',
                 classes: 'shepherd shepherd-welcome',
                 buttons: ['cancel'],
             },
diff --git a/ui/src/app/services/tour/adapter-tour.constants.ts b/ui/src/app/services/tour/adapter-tour.constants.ts
index eea1b7d19a..185d2853b7 100644
--- a/ui/src/app/services/tour/adapter-tour.constants.ts
+++ b/ui/src/app/services/tour/adapter-tour.constants.ts
@@ -90,7 +90,7 @@ export default {
                 title: 'Adapter was started successfully',
                 text:
                     '<p>The adpater is now deployed in the background and the data is fetched and processed from the OpenSenseMap endpoint. Here you will see some example data, which is coming directly ' +
-                    'the data source. This might take a minute. On the left you can see the runtime names of the properties you defined before. On the right side the values are shown. After having a look at ' +
+                    'displayed from the data stream. This might take a minute. On the left you can see the runtime names of the properties you defined before. On the right side the values are shown. After having a look at ' +
                     'the data <b>click on Close</b> to continue.</p>',
                 attachToElement: '#confirm_adapter_started_button',
                 attachPosition: 'bottom',
@@ -99,7 +99,7 @@ export default {
             {
                 stepId: 'step-9',
                 title: 'Congratulation',
-                text: '<b></b>Congratulation!</b> You have created your first adapter and finished the tutorial. Go to the pipeline editor to see the new data source',
+                text: '<b></b>Congratulation!</b> You have created your first adapter and finished the tutorial. Go to the pipeline editor to see the new data stream',
                 classes: 'shepherd shepherd-welcome',
                 buttons: ['cancel'],
             },
diff --git a/ui/src/scss/sp/main.scss b/ui/src/scss/sp/main.scss
index 80ebae4a33..a486652480 100644
--- a/ui/src/scss/sp/main.scss
+++ b/ui/src/scss/sp/main.scss
@@ -38,7 +38,6 @@ body {
     height: auto !important;
     width: 100%;
 
-    --color-set: #{$sp-color-set};
     --color-stream: #{$sp-color-stream};
     --color-processor: #{$sp-color-processor};
     --color-sink: #{$sp-color-sink};
@@ -531,11 +530,6 @@ md-content {
     color: black;
 }
 
-.set-label {
-    background: $sp-color-set;
-    color: black;
-}
-
 .processor-label {
     background: $sp-color-processor;
 }
@@ -549,11 +543,6 @@ md-content {
     border: 2px solid $sp-color-stream;
 }
 
-.set {
-    border-radius: 50%;
-    border: 2px solid $sp-color-set;
-}
-
 .action {
     border: 2px solid $sp-color-sink;
 }
diff --git a/ui/src/scss/sp/pipeline-element-options.scss b/ui/src/scss/sp/pipeline-element-options.scss
index 762d355c0f..5b6340e026 100644
--- a/ui/src/scss/sp/pipeline-element-options.scss
+++ b/ui/src/scss/sp/pipeline-element-options.scss
@@ -70,8 +70,3 @@
     background: $sp-color-stream;
     color: #343434;
 }
-
-.pe-info-set {
-    background: $sp-color-set;
-    color: white;
-}


Test patch:
diff --git a/streampipes-mail/src/main/java/org/apache/streampipes/mail/template/TestMailTemplate.java b/streampipes-mail/src/main/java/org/apache/streampipes/mail/template/TestMailTemplate.java
index dc02613a5d..d6f913f4cf 100644
--- a/streampipes-mail/src/main/java/org/apache/streampipes/mail/template/TestMailTemplate.java
+++ b/streampipes-mail/src/main/java/org/apache/streampipes/mail/template/TestMailTemplate.java
@@ -18,6 +18,7 @@
 package org.apache.streampipes.mail.template;
 
 import org.apache.streampipes.mail.template.generation.DefaultPlaceholders;
+import org.apache.streampipes.mail.template.generation.MailTemplateBuilder;
 import org.apache.streampipes.mail.template.part.MailTemplatePart;
 import org.apache.streampipes.mail.utils.MailUtils;
 
@@ -41,8 +42,8 @@ public class TestMailTemplate extends AbstractMailTemplate {
   }
 
   @Override
-  protected void addTemplateParts(Map<String, MailTemplatePart> templateParts) {
-    templateParts.put(DefaultPlaceholders.INNER.key(), MailTemplatePart.MAIL_TEMPLATE_INNER_PLAIN);
+  protected void configureTemplate(MailTemplateBuilder builder) {
+    builder.withInnerPart(MailTemplatePart.MAIL_TEMPLATE_INNER_PLAIN);
   }
 
   private String makeText() {
diff --git a/ui/cypress/tests/adapter/editAdapter.smoke.spec.ts b/ui/cypress/tests/adapter/editAdapter.smoke.spec.ts
index cb297e1d7b..5bad56c0a0 100644
--- a/ui/cypress/tests/adapter/editAdapter.smoke.spec.ts
+++ b/ui/cypress/tests/adapter/editAdapter.smoke.spec.ts
@@ -73,7 +73,7 @@ describe('Test Edit Adapter', () => {
 
         ConnectUtils.startAndValidateAdapter(3);
 
-        // Validate that name of adapter and data source
+        // Validate that name of adapter and data stream
         cy.dataCy('adapter-name').contains(newAdapterName);
         cy.get('.sp-dialog-content').contains(newAdapterName);
     });