diff --git a/.liquibase/Data_alias/basic/2021.0.2/CommunicationBlacklist/changelog.xml b/.liquibase/Data_alias/basic/2021.0.2/CommunicationBlacklist/changelog.xml
new file mode 100644
index 0000000000000000000000000000000000000000..de169ea5bfc61c615c96663b65dea30519245fff
--- /dev/null
+++ b/.liquibase/Data_alias/basic/2021.0.2/CommunicationBlacklist/changelog.xml
@@ -0,0 +1,7 @@
+<?xml version="1.1" encoding="UTF-8" standalone="no"?>
+<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.6.xsd">
+    <include relativeToChangelogFile="true" file="create_communicationBlacklist.xml"/>
+    <include relativeToChangelogFile="true" file="insert_blacklistTypeKeyword.xml"/>
+</databaseChangeLog>
\ No newline at end of file
diff --git a/.liquibase/Data_alias/basic/2021.0.2/CommunicationBlacklist/create_communicationBlacklist.xml b/.liquibase/Data_alias/basic/2021.0.2/CommunicationBlacklist/create_communicationBlacklist.xml
new file mode 100644
index 0000000000000000000000000000000000000000..faf00d8192c66f4d93f76288fd73a64243d37807
--- /dev/null
+++ b/.liquibase/Data_alias/basic/2021.0.2/CommunicationBlacklist/create_communicationBlacklist.xml
@@ -0,0 +1,27 @@
+<?xml version="1.1" encoding="UTF-8" standalone="no"?>
+<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.6.xsd">
+  <changeSet author="s.listl" id="9415eb4f-ea93-433c-8a69-1bdb77c6ec87">
+    <createTable tableName="COMMUNICATIONBLACKLIST">
+          <column name="COMMUNICATIONBLACKLISTID" type="CHAR(36)">
+              <constraints nullable="false" primaryKey="true" primaryKeyName="PK_COMMUNICATIONBLACKLISTID"/>
+          </column>
+          <column name="BLACKLIST_TYPE" type="VARCHAR(36)">
+              <constraints nullable="false"/>
+          </column>
+          <column name="BLACKLIST_FILTER" type="NCLOB"/>
+          <column name="REASON" type="NVARCHAR(500)"/>
+          <column name="START_DATE" type="DATETIME"/>
+          <column name="END_DATE" type="DATETIME"/>
+          <column name="DATE_NEW" type="DATETIME">
+              <constraints nullable="false"/>
+          </column>
+          <column name="USER_NEW" type="VARCHAR(50)">
+              <constraints nullable="false"/>
+          </column>
+          <column name="DATE_EDIT" type="DATETIME"/>
+          <column name="USER_EDIT" type="VARCHAR(50)"/>
+      </createTable>
+  </changeSet>
+</databaseChangeLog>
\ No newline at end of file
diff --git a/.liquibase/Data_alias/basic/2021.0.2/CommunicationBlacklist/insert_blacklistTypeKeyword.xml b/.liquibase/Data_alias/basic/2021.0.2/CommunicationBlacklist/insert_blacklistTypeKeyword.xml
new file mode 100644
index 0000000000000000000000000000000000000000..c2a6c8057c98bad7b41e2298ed807dbb4032dfc7
--- /dev/null
+++ b/.liquibase/Data_alias/basic/2021.0.2/CommunicationBlacklist/insert_blacklistTypeKeyword.xml
@@ -0,0 +1,23 @@
+<?xml version="1.1" encoding="UTF-8" standalone="no"?>
+<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.6.xsd">
+  <changeSet author="s.listl" id="89ba3828-288c-4af7-bf98-feccc01ce312">
+    <insert tableName="AB_KEYWORD_CATEGORY">
+      <column name="AB_KEYWORD_CATEGORYID" value="e980999c-0f8e-484b-852b-92d60c38c14f"/>
+      <column name="NAME" value="CommunicationBlacklistType"/>
+      <column name="SORTINGBY" valueNumeric="0"/>
+      <column name="SORTINGDIRECTION" value="ASC"/>
+    </insert>
+    <insert tableName="AB_KEYWORD_ENTRY">
+        <column name="AB_KEYWORD_ENTRYID" value="a1c8d2c6-54c7-4e9d-9792-dd576ac6f43e"/>
+        <column name="AB_KEYWORD_CATEGORY_ID" value="e980999c-0f8e-484b-852b-92d60c38c14f"/>
+        <column name="KEYID" value="BLACKLIST_TYPE_EMAILRECIPIENT_FILTER"/>
+        <column name="TITLE" value="Recipient filter"/>
+        <column name="CONTAINER" value="CommunicationBlacklistType"/>
+        <column name="SORTING" valueNumeric="1"/>
+        <column name="ISACTIVE" valueNumeric="1"/>
+        <column name="ISESSENTIAL" valueNumeric="0"/>
+    </insert>
+  </changeSet>
+</databaseChangeLog>
\ No newline at end of file
diff --git a/.liquibase/Data_alias/basic/2021.0.2/changelog.xml b/.liquibase/Data_alias/basic/2021.0.2/changelog.xml
index 53aa034099c548707609dbcbe2b635b4f0f9352d..f73015f9608794f9dafe98ab79e7e6ee741baca2 100644
--- a/.liquibase/Data_alias/basic/2021.0.2/changelog.xml
+++ b/.liquibase/Data_alias/basic/2021.0.2/changelog.xml
@@ -7,4 +7,5 @@
     <include relativeToChangelogFile="true" file="CommunicationSettings/changelog.xml"/>
     <include relativeToChangelogFile="true" file="change_SalesprojectMemberRole.xml"/>
     <include relativeToChangelogFile="true" file="Interest/changelog.xml"/>
+    <include relativeToChangelogFile="true" file="CommunicationBlacklist/changelog.xml"/>
 </databaseChangeLog>
\ No newline at end of file
diff --git a/aliasDefinition/Data_alias/Data_alias.aod b/aliasDefinition/Data_alias/Data_alias.aod
index 70a35bfc8c893af56d49c4a09324e20f26533614..d2efab6fb2dcbdc8ad9b15821a721d085f9ad094 100644
--- a/aliasDefinition/Data_alias/Data_alias.aod
+++ b/aliasDefinition/Data_alias/Data_alias.aod
@@ -19729,6 +19729,166 @@
               </entityFieldDb>
             </entityFields>
           </entityDb>
+          <entityDb>
+            <name>COMMUNICATIONBLACKLIST</name>
+            <dbName></dbName>
+            <idColumn>COMMUNICATIONBLACKLISTID</idColumn>
+            <idGeneratorType v="0" />
+            <idGeneratorInterval v="1" />
+            <documentation></documentation>
+            <title></title>
+            <description></description>
+            <auditSyncConfig>
+              <name>auditSyncConfig</name>
+              <auditMode v="0" />
+              <syncActive v="false" />
+              <syncComplete v="true" />
+              <syncDirection v="1" />
+              <syncIds></syncIds>
+            </auditSyncConfig>
+            <entityFields>
+              <entityFieldDb>
+                <name>BLACKLIST_FILTER</name>
+                <dbName></dbName>
+                <primaryKey v="false" />
+                <columnType v="-1" />
+                <size v="2147483647" />
+                <scale v="0" />
+                <notNull v="false" />
+                <isUnique v="false" />
+                <index v="false" />
+                <documentation></documentation>
+                <title></title>
+                <description></description>
+              </entityFieldDb>
+              <entityFieldDb>
+                <name>DATE_EDIT</name>
+                <dbName></dbName>
+                <primaryKey v="false" />
+                <columnType v="93" />
+                <size v="19" />
+                <scale v="0" />
+                <notNull v="false" />
+                <isUnique v="false" />
+                <index v="false" />
+                <documentation></documentation>
+                <title></title>
+                <description></description>
+              </entityFieldDb>
+              <entityFieldDb>
+                <name>END_DATE</name>
+                <dbName></dbName>
+                <primaryKey v="false" />
+                <columnType v="93" />
+                <size v="19" />
+                <scale v="0" />
+                <notNull v="false" />
+                <isUnique v="false" />
+                <index v="false" />
+                <documentation></documentation>
+                <title></title>
+                <description></description>
+              </entityFieldDb>
+              <entityFieldDb>
+                <name>START_DATE</name>
+                <dbName></dbName>
+                <primaryKey v="false" />
+                <columnType v="93" />
+                <size v="19" />
+                <scale v="0" />
+                <notNull v="false" />
+                <isUnique v="false" />
+                <index v="false" />
+                <documentation></documentation>
+                <title></title>
+                <description></description>
+              </entityFieldDb>
+              <entityFieldDb>
+                <name>DATE_NEW</name>
+                <dbName></dbName>
+                <primaryKey v="false" />
+                <columnType v="93" />
+                <size v="19" />
+                <scale v="0" />
+                <notNull v="true" />
+                <isUnique v="false" />
+                <index v="false" />
+                <documentation></documentation>
+                <title></title>
+                <description></description>
+              </entityFieldDb>
+              <entityFieldDb>
+                <name>USER_NEW</name>
+                <dbName></dbName>
+                <primaryKey v="false" />
+                <columnType v="12" />
+                <size v="50" />
+                <scale v="0" />
+                <notNull v="true" />
+                <isUnique v="false" />
+                <index v="false" />
+                <documentation></documentation>
+                <title></title>
+                <description></description>
+              </entityFieldDb>
+              <entityFieldDb>
+                <name>COMMUNICATIONBLACKLISTID</name>
+                <dbName></dbName>
+                <primaryKey v="true" />
+                <columnType v="1" />
+                <size v="36" />
+                <scale v="0" />
+                <notNull v="true" />
+                <isUnique v="true" />
+                <index v="true" />
+                <documentation></documentation>
+                <title></title>
+                <description></description>
+              </entityFieldDb>
+              <entityFieldDb>
+                <name>REASON</name>
+                <dbName></dbName>
+                <primaryKey v="false" />
+                <columnType v="12" />
+                <size v="500" />
+                <scale v="0" />
+                <notNull v="false" />
+                <isUnique v="false" />
+                <index v="false" />
+                <documentation></documentation>
+                <title></title>
+                <description></description>
+              </entityFieldDb>
+              <entityFieldDb>
+                <name>USER_EDIT</name>
+                <dbName></dbName>
+                <primaryKey v="false" />
+                <columnType v="12" />
+                <size v="50" />
+                <scale v="0" />
+                <notNull v="false" />
+                <isUnique v="false" />
+                <index v="false" />
+                <documentation></documentation>
+                <title></title>
+                <description></description>
+              </entityFieldDb>
+              <entityFieldDb>
+                <name>BLACKLIST_TYPE</name>
+                <dbName></dbName>
+                <primaryKey v="false" />
+                <columnType v="12" />
+                <size v="36" />
+                <scale v="0" />
+                <notNull v="true" />
+                <isUnique v="false" />
+                <index v="false" />
+                <documentation></documentation>
+                <title></title>
+                <description></description>
+              </entityFieldDb>
+            </entityFields>
+          </entityDb>
         </entities>
       </entityGroup>
     </aliasDefDb>
diff --git a/application/_____SYSTEM_APPLICATION_NEON/_____SYSTEM_APPLICATION_NEON.aod b/application/_____SYSTEM_APPLICATION_NEON/_____SYSTEM_APPLICATION_NEON.aod
index 7af92c53743f3adb952f2f0fa986a937f9183ccd..5f7436d899887b2367b9f90bf001c6fd9ba924cb 100644
--- a/application/_____SYSTEM_APPLICATION_NEON/_____SYSTEM_APPLICATION_NEON.aod
+++ b/application/_____SYSTEM_APPLICATION_NEON/_____SYSTEM_APPLICATION_NEON.aod
@@ -200,6 +200,10 @@
                 <name>Interest</name>
                 <kind v="10077" />
               </entityNode>
+              <entityNode>
+                <name>CommunicationBlacklist</name>
+                <kind v="10077" />
+              </entityNode>
             </childNodes>
           </entityNode>
           <entityNode>
diff --git a/entity/BulkMailRecipient_entity/BulkMailRecipient_entity.aod b/entity/BulkMailRecipient_entity/BulkMailRecipient_entity.aod
index 5cd67de037d75128d0b09319209d18296dc70b30..e799fd72fcc3c4068ba9f766f62feb22959da90c 100644
--- a/entity/BulkMailRecipient_entity/BulkMailRecipient_entity.aod
+++ b/entity/BulkMailRecipient_entity/BulkMailRecipient_entity.aod
@@ -82,7 +82,6 @@
     <entityField>
       <name>EMAIL_ADDRESS</name>
       <title>Email</title>
-      <consumer>EmailAdresses</consumer>
       <textInputAllowed v="true" />
       <valueProcess>%aditoprj%/entity/BulkMailRecipient_entity/entityfields/email_address/valueProcess.js</valueProcess>
       <displayValueProcess>%aditoprj%/entity/BulkMailRecipient_entity/entityfields/email_address/displayValueProcess.js</displayValueProcess>
@@ -108,7 +107,7 @@
       <state>READONLY</state>
     </entityField>
     <entityField>
-      <name>HASCOMMRESTRICTION</name>
+      <name>HASCOMMUNICATIONREJECTION</name>
       <title>Advertising ban</title>
     </entityField>
     <entityActionGroup>
@@ -176,6 +175,36 @@
         </entityActionField>
       </children>
     </entityActionGroup>
+    <entityParameter>
+      <name>ExcludeCommunicationRejecting_param</name>
+      <expose v="true" />
+    </entityParameter>
+    <entityParameter>
+      <name>ExcludedStatus_param</name>
+      <expose v="true" />
+    </entityParameter>
+    <entityProvider>
+      <name>RecipientsToBeMailed</name>
+      <children>
+        <entityParameter>
+          <name>ExcludedStatus_param</name>
+          <valueProcess>%aditoprj%/entity/BulkMailRecipient_entity/entityfields/recipientstobemailed/children/excludedstatus_param/valueProcess.js</valueProcess>
+          <expose v="false" />
+        </entityParameter>
+        <entityParameter>
+          <name>ExcludeCommunicationRejecting_param</name>
+          <valueProcess>%aditoprj%/entity/BulkMailRecipient_entity/entityfields/recipientstobemailed/children/excludecommunicationrejecting_param/valueProcess.js</valueProcess>
+          <expose v="false" />
+        </entityParameter>
+      </children>
+    </entityProvider>
+    <entityParameter>
+      <name>IsTestMail_param</name>
+      <expose v="true" />
+    </entityParameter>
+    <entityParameter>
+      <name>ExcludeBlacklisted_param</name>
+    </entityParameter>
   </entityFields>
   <recordContainers>
     <dbRecordContainer>
@@ -232,13 +261,10 @@
           <name>CONTACT_ID.displayValue</name>
           <expression>%aditoprj%/entity/BulkMailRecipient_entity/recordcontainers/db/recordfieldmappings/contact_id.displayvalue/expression.js</expression>
         </dbRecordFieldMapping>
-        <dbRecordFieldMapping>
-          <name>HASCOMMRESTRICTION.value</name>
-          <expression>%aditoprj%/entity/BulkMailRecipient_entity/recordcontainers/db/recordfieldmappings/hascommrestriction.value/expression.js</expression>
-        </dbRecordFieldMapping>
         <dbRecordFieldMapping>
           <name>EMAIL_ADDRESS.value</name>
           <recordfield>BULKMAILRECIPIENT.EMAIL_ADDRESS</recordfield>
+          <isFilterable v="true" />
         </dbRecordFieldMapping>
         <dbRecordFieldMapping>
           <name>PROBLEM.value</name>
@@ -252,6 +278,10 @@
           <name>TEST_RECIPIENT.value</name>
           <recordfield>BULKMAILRECIPIENT.TEST_RECIPIENT</recordfield>
         </dbRecordFieldMapping>
+        <dbRecordFieldMapping>
+          <name>HASCOMMUNICATIONREJECTION.value</name>
+          <expression>%aditoprj%/entity/BulkMailRecipient_entity/recordcontainers/db/recordfieldmappings/hascommunicationrejection.value/expression.js</expression>
+        </dbRecordFieldMapping>
       </recordFieldMappings>
       <linkInformation>
         <linkInformation>
diff --git a/entity/BulkMailRecipient_entity/entityfields/icon/colorProcess.js b/entity/BulkMailRecipient_entity/entityfields/icon/colorProcess.js
index f4a5d6a797f85b33b0d1d819d44e0015a28d24c9..32b81b7db33e5508f206cc74db74609c9adae950 100644
--- a/entity/BulkMailRecipient_entity/entityfields/icon/colorProcess.js
+++ b/entity/BulkMailRecipient_entity/entityfields/icon/colorProcess.js
@@ -2,5 +2,5 @@ import("system.vars");
 import("system.result");
 import("system.neon");
 
-if (vars.get("$field.HASCOMMRESTRICTION") == "true" || !vars.get("$field.EMAIL_ADDRESS"))
+if (vars.get("$field.HASCOMMUNICATIONREJECTION") == "true" || !vars.get("$field.EMAIL_ADDRESS"))
     result.string(neon.PRIORITY_HIGH_COLOR);
\ No newline at end of file
diff --git a/entity/BulkMailRecipient_entity/entityfields/icon/valueProcess.js b/entity/BulkMailRecipient_entity/entityfields/icon/valueProcess.js
index c67e7c5712da325df0f23f47eee957ce28a3ed54..711c154ccbca6c140fd523e0d296c08b66fb094f 100644
--- a/entity/BulkMailRecipient_entity/entityfields/icon/valueProcess.js
+++ b/entity/BulkMailRecipient_entity/entityfields/icon/valueProcess.js
@@ -4,7 +4,7 @@ import("Contact_lib");
 
 var type = ContactUtils.getContactTypeByPersOrg(vars.get("$field.PERSON_ID"), vars.get("$field.ORGANISATION_ID"));
 var icon;
-if (vars.get("$field.HASCOMMRESTRICTION") == "true")
+if (vars.get("$field.HASCOMMUNICATIONREJECTION") == "true")
     icon = "VAADIN:BAN";
 else if (type == 1)
     icon = "VAADIN:BUILDING";
diff --git a/entity/BulkMailRecipient_entity/entityfields/recipientstobemailed/children/excludecommunicationrejecting_param/valueProcess.js b/entity/BulkMailRecipient_entity/entityfields/recipientstobemailed/children/excludecommunicationrejecting_param/valueProcess.js
new file mode 100644
index 0000000000000000000000000000000000000000..40effa0178464da0c7850912345f19c7fa95975a
--- /dev/null
+++ b/entity/BulkMailRecipient_entity/entityfields/recipientstobemailed/children/excludecommunicationrejecting_param/valueProcess.js
@@ -0,0 +1,3 @@
+import("system.result");
+
+result.string(true);
\ No newline at end of file
diff --git a/entity/BulkMailRecipient_entity/entityfields/recipientstobemailed/children/excludedstatus_param/valueProcess.js b/entity/BulkMailRecipient_entity/entityfields/recipientstobemailed/children/excludedstatus_param/valueProcess.js
new file mode 100644
index 0000000000000000000000000000000000000000..ae0c566f408d9f9a4bd2181699b7fadeab9a02a4
--- /dev/null
+++ b/entity/BulkMailRecipient_entity/entityfields/recipientstobemailed/children/excludedstatus_param/valueProcess.js
@@ -0,0 +1,6 @@
+import("KeywordRegistry_basic");
+import("system.result");
+
+result.string(JSON.stringify([
+    $KeywordRegistry.bulkMailRecipientStatus$sent()
+]));
\ No newline at end of file
diff --git a/entity/BulkMailRecipient_entity/recordcontainers/db/conditionProcess.js b/entity/BulkMailRecipient_entity/recordcontainers/db/conditionProcess.js
index 01d4a5dc9c76ee5f350521eea0f4a2bb6016a3ec..7e2155661d1e08884d88ba7c0fe4c970c3cab458 100644
--- a/entity/BulkMailRecipient_entity/recordcontainers/db/conditionProcess.js
+++ b/entity/BulkMailRecipient_entity/recordcontainers/db/conditionProcess.js
@@ -1,5 +1,38 @@
+import("KeywordRegistry_basic");
+import("system.vars");
+import("Util_lib");
 import("system.db");
 import("system.result");
 import("Sql_lib");
+import("MarketingCondition_lib");
 
-result.string(newWhere("BULKMAILRECIPIENT.BULKMAIL_ID", "$param.BulkMailId_param").toString());
\ No newline at end of file
+var excludeWithCommunicationRejection = Utils.toBoolean(vars.get("$param.ExcludeCommunicationRejecting_param"));
+var excludeBlacklisted = Utils.toBoolean(vars.get("$param.ExcludeBlacklisted_param"));
+var excludedStatus = Utils.parseJSON(vars.get("$param.ExcludedStatus_param"));
+var isTestMail = Utils.toBoolean(vars.get("$param.IsTestMail_param"));
+
+var condition = newWhere("BULKMAILRECIPIENT.BULKMAIL_ID", "$param.BulkMailId_param");
+if (isTestMail)
+{
+    condition.and("BULKMAILRECIPIENT.TEST_RECIPIENT", 1);
+}
+else
+{
+    if (excludeWithCommunicationRejection)
+    {
+        condition.and(new CommunicationSettingsCondition()
+            .medium($KeywordRegistry.communicationMediumCampaign$mail(), "BULKMAILRECIPIENT.EMAIL_ADDRESS")
+            .rejected()
+            .buildNotExistsCondition());
+    }
+    if (excludeBlacklisted)
+    {
+        condition.and("not (" + CommunicationBlacklist.getMailRecipientBlacklist().buildCondition() + ")");
+    }
+    if (!Utils.isNullOrEmpty(excludedStatus))
+    {
+        condition.and("BULKMAILRECIPIENT.STATUS", excludedStatus, SqlBuilder.NOT_IN());
+    }
+}
+
+result.string(condition.toSource());
\ No newline at end of file
diff --git a/entity/BulkMailRecipient_entity/recordcontainers/db/recordfieldmappings/hascommrestriction.value/expression.js b/entity/BulkMailRecipient_entity/recordcontainers/db/recordfieldmappings/hascommrestriction.value/expression.js
deleted file mode 100644
index 04cd6d4dc91c5a7e36cd82b78301acf456fc05ff..0000000000000000000000000000000000000000
--- a/entity/BulkMailRecipient_entity/recordcontainers/db/recordfieldmappings/hascommrestriction.value/expression.js
+++ /dev/null
@@ -1,9 +0,0 @@
-import("Sql_lib");
-import("KeywordRegistry_basic");
-import("Contact_lib");
-import("system.db");
-import("system.result");
-
-var commRestrictionCond = ContactUtils.getCommRestrictionCondition($KeywordRegistry.communicationMediumCampaign$mail());
-var sql = SqlBuilder.caseWhen(commRestrictionCond).thenString("true").elseString("false");
-result.string(sql.toString());
diff --git a/entity/BulkMailRecipient_entity/recordcontainers/db/recordfieldmappings/hascommunicationrejection.value/expression.js b/entity/BulkMailRecipient_entity/recordcontainers/db/recordfieldmappings/hascommunicationrejection.value/expression.js
new file mode 100644
index 0000000000000000000000000000000000000000..cd8b6dfbc38990c87c8b99aff9833f3730a71c6f
--- /dev/null
+++ b/entity/BulkMailRecipient_entity/recordcontainers/db/recordfieldmappings/hascommunicationrejection.value/expression.js
@@ -0,0 +1,25 @@
+import("system.vars");
+import("Util_lib");
+import("MarketingCondition_lib");
+import("Sql_lib");
+import("KeywordRegistry_basic");
+import("Contact_lib");
+import("system.db");
+import("system.result");
+
+var sql;
+//if recipients with communication restriction are excluded by the where-condition, this can never be true
+if (Utils.toBoolean(vars.get("$param.ExcludeCommunicationRejecting_param")))
+{
+    sql = "'false'";
+}
+else
+{
+    var communicationSettingsCondition = new CommunicationSettingsCondition()
+        .medium($KeywordRegistry.communicationMediumCampaign$mail(), "BULKMAILRECIPIENT.EMAIL_ADDRESS")
+        .rejected()
+        .buildNotExistsCondition();
+    sql = SqlBuilder.caseWhen(communicationSettingsCondition).thenString("true").elseString("false").toString();
+}
+
+result.string(sql);
diff --git a/entity/CommunicationBlacklist_entity/CommunicationBlacklist_entity.aod b/entity/CommunicationBlacklist_entity/CommunicationBlacklist_entity.aod
new file mode 100644
index 0000000000000000000000000000000000000000..58cbbc2265ecbeff8ff3fb8e80e2c5a44d4b5866
--- /dev/null
+++ b/entity/CommunicationBlacklist_entity/CommunicationBlacklist_entity.aod
@@ -0,0 +1,110 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<entity xmlns="http://www.adito.de/2018/ao/Model" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" VERSION="1.3.18" xsi:schemaLocation="http://www.adito.de/2018/ao/Model adito://models/xsd/entity/1.3.18">
+  <name>CommunicationBlacklist_entity</name>
+  <title>Blacklist</title>
+  <majorModelMode>DISTRIBUTED</majorModelMode>
+  <onValidation>%aditoprj%/entity/CommunicationBlacklist_entity/onValidation.js</onValidation>
+  <iconId>VAADIN:BAN</iconId>
+  <recordContainer>db</recordContainer>
+  <entityFields>
+    <entityProvider>
+      <name>#PROVIDER</name>
+    </entityProvider>
+    <entityProvider>
+      <name>#PROVIDER_AGGREGATES</name>
+      <useAggregates v="true" />
+    </entityProvider>
+    <entityField>
+      <name>COMMUNICATIONBLACKLISTID</name>
+    </entityField>
+    <entityField>
+      <name>START_DATE</name>
+      <title>Start date</title>
+      <contentType>DATE</contentType>
+      <resolution>DAY</resolution>
+    </entityField>
+    <entityField>
+      <name>END_DATE</name>
+      <title>End date</title>
+      <contentType>DATE</contentType>
+      <resolution>DAY</resolution>
+    </entityField>
+    <entityField>
+      <name>BLACKLIST_FILTER</name>
+      <title>Condition</title>
+      <contentType>FILTER_TREE</contentType>
+      <stateProcess>%aditoprj%/entity/CommunicationBlacklist_entity/entityfields/blacklist_filter/stateProcess.js</stateProcess>
+      <valueProcess>%aditoprj%/entity/CommunicationBlacklist_entity/entityfields/blacklist_filter/valueProcess.js</valueProcess>
+    </entityField>
+    <entityField>
+      <name>REASON</name>
+      <title>Reason</title>
+      <contentType>LONG_TEXT</contentType>
+      <mandatory v="true" />
+    </entityField>
+    <entityField>
+      <name>BLACKLIST_TYPE</name>
+      <title>Type</title>
+      <consumer>BlacklistTypeKeyword</consumer>
+      <mandatory v="true" />
+    </entityField>
+    <entityConsumer>
+      <name>BlacklistTypeKeyword</name>
+      <dependency>
+        <name>dependency</name>
+        <entityName>KeywordEntry_entity</entityName>
+        <fieldName>SpecificContainerKeywords</fieldName>
+      </dependency>
+      <children>
+        <entityParameter>
+          <name>ContainerName_param</name>
+          <valueProcess>%aditoprj%/entity/CommunicationBlacklist_entity/entityfields/blacklisttypekeyword/children/containername_param/valueProcess.js</valueProcess>
+        </entityParameter>
+      </children>
+    </entityConsumer>
+  </entityFields>
+  <recordContainers>
+    <dbRecordContainer>
+      <name>db</name>
+      <alias>Data_alias</alias>
+      <recordFieldMappings>
+        <dbRecordFieldMapping>
+          <name>BLACKLIST_FILTER.value</name>
+          <recordfield>COMMUNICATIONBLACKLIST.BLACKLIST_FILTER</recordfield>
+        </dbRecordFieldMapping>
+        <dbRecordFieldMapping>
+          <name>BLACKLIST_TYPE.value</name>
+          <recordfield>COMMUNICATIONBLACKLIST.BLACKLIST_TYPE</recordfield>
+          <isFilterable v="true" />
+        </dbRecordFieldMapping>
+        <dbRecordFieldMapping>
+          <name>COMMUNICATIONBLACKLISTID.value</name>
+          <recordfield>COMMUNICATIONBLACKLIST.COMMUNICATIONBLACKLISTID</recordfield>
+        </dbRecordFieldMapping>
+        <dbRecordFieldMapping>
+          <name>END_DATE.value</name>
+          <recordfield>COMMUNICATIONBLACKLIST.END_DATE</recordfield>
+          <isFilterable v="true" />
+        </dbRecordFieldMapping>
+        <dbRecordFieldMapping>
+          <name>REASON.value</name>
+          <recordfield>COMMUNICATIONBLACKLIST.REASON</recordfield>
+        </dbRecordFieldMapping>
+        <dbRecordFieldMapping>
+          <name>START_DATE.value</name>
+          <recordfield>COMMUNICATIONBLACKLIST.START_DATE</recordfield>
+          <isFilterable v="true" />
+        </dbRecordFieldMapping>
+      </recordFieldMappings>
+      <linkInformation>
+        <linkInformation>
+          <name>007ad42a-0a7a-49a9-a71d-917ee3c94aa7</name>
+          <tableName>COMMUNICATIONBLACKLIST</tableName>
+          <primaryKey>COMMUNICATIONBLACKLISTID</primaryKey>
+          <isUIDTable v="true" />
+          <readonly v="false" />
+        </linkInformation>
+      </linkInformation>
+    </dbRecordContainer>
+  </recordContainers>
+</entity>
diff --git a/entity/CommunicationBlacklist_entity/entityfields/blacklist_filter/stateProcess.js b/entity/CommunicationBlacklist_entity/entityfields/blacklist_filter/stateProcess.js
new file mode 100644
index 0000000000000000000000000000000000000000..234a26e9827c511b12b09fbbe1f5552bfd78f912
--- /dev/null
+++ b/entity/CommunicationBlacklist_entity/entityfields/blacklist_filter/stateProcess.js
@@ -0,0 +1,12 @@
+import("system.result");
+import("KeywordRegistry_basic");
+import("system.vars");
+import("system.neon");
+
+var state = neon.COMPONENTSTATE_INVISIBLE;
+if (vars.get("$field.BLACKLIST_TYPE") == $KeywordRegistry.communicationBlacklistType$emailRecipientFilter())
+{
+    state = neon.COMPONENTSTATE_EDITABLE;
+}
+
+result.string(state);
\ No newline at end of file
diff --git a/entity/CommunicationBlacklist_entity/entityfields/blacklist_filter/valueProcess.js b/entity/CommunicationBlacklist_entity/entityfields/blacklist_filter/valueProcess.js
new file mode 100644
index 0000000000000000000000000000000000000000..0bb45e823a21888b77385d2950bfb48fc5d530d9
--- /dev/null
+++ b/entity/CommunicationBlacklist_entity/entityfields/blacklist_filter/valueProcess.js
@@ -0,0 +1,9 @@
+import("system.neon");
+import("system.vars");
+import("system.result");
+
+if (!vars.get("$this.value") && (vars.get("$sys.recordstate") == neon.OPERATINGSTATE_NEW || vars.get("$sys.recordstate") == neon.OPERATINGSTATE_EDIT))
+{
+    var emptyFilter = {entity: "BulkMailRecipient_entity", filter: {type: "group", operator: "AND", childs: []}};
+    result.string(JSON.stringify(emptyFilter));
+}
\ No newline at end of file
diff --git a/entity/CommunicationBlacklist_entity/entityfields/blacklisttypekeyword/children/containername_param/valueProcess.js b/entity/CommunicationBlacklist_entity/entityfields/blacklisttypekeyword/children/containername_param/valueProcess.js
new file mode 100644
index 0000000000000000000000000000000000000000..1e28921b3c32d79d55a29aebe44878a9c766c91d
--- /dev/null
+++ b/entity/CommunicationBlacklist_entity/entityfields/blacklisttypekeyword/children/containername_param/valueProcess.js
@@ -0,0 +1,4 @@
+import("KeywordRegistry_basic");
+import("system.result");
+
+result.string($KeywordRegistry.communicationBlacklistType());
\ No newline at end of file
diff --git a/entity/CommunicationBlacklist_entity/onValidation.js b/entity/CommunicationBlacklist_entity/onValidation.js
new file mode 100644
index 0000000000000000000000000000000000000000..0652d9c0f9ff0ba032e0b7561c04ee345ba99223
--- /dev/null
+++ b/entity/CommunicationBlacklist_entity/onValidation.js
@@ -0,0 +1,15 @@
+import("system.translate");
+import("Util_lib");
+import("system.result");
+import("KeywordRegistry_basic");
+import("system.vars");
+import("system.neon");
+import("JditoFilter_lib");
+
+if (vars.get("$field.BLACKLIST_TYPE") == $KeywordRegistry.communicationBlacklistType$emailRecipientFilter())
+{
+    var filter = Utils.parseJSON(vars.get("$field.BLACKLIST_FILTER"));
+    filter = new FilterConditionGroup(filter);
+    if (filter.isEmpty())
+        result.string(translate.text("Filter can't be empty"));
+}
\ No newline at end of file
diff --git a/entity/KeywordEntry_entity/KeywordEntry_entity.aod b/entity/KeywordEntry_entity/KeywordEntry_entity.aod
index c2d0201747e72019fda806a3f8407b090ee0d97e..d7ba8e60fc2cf43e6a449d999124287d7e15de23 100644
--- a/entity/KeywordEntry_entity/KeywordEntry_entity.aod
+++ b/entity/KeywordEntry_entity/KeywordEntry_entity.aod
@@ -684,6 +684,12 @@
           <fieldName>StatusKeyword</fieldName>
           <isConsumer v="false" />
         </entityDependency>
+        <entityDependency>
+          <name>a4d04777-82dc-4384-a4a5-c6a4a71e7a65</name>
+          <entityName>CommunicationBlacklist_entity</entityName>
+          <fieldName>BlacklistTypeKeyword</fieldName>
+          <isConsumer v="false" />
+        </entityDependency>
       </dependencies>
       <children>
         <entityParameter>
diff --git a/neonContext/CommunicationBlacklist/CommunicationBlacklist.aod b/neonContext/CommunicationBlacklist/CommunicationBlacklist.aod
index e6c6084fa90164ca1d5628feb39af6a61629c164..9e69819413f07b0c7b52eb16983d9409f9226edf 100644
--- a/neonContext/CommunicationBlacklist/CommunicationBlacklist.aod
+++ b/neonContext/CommunicationBlacklist/CommunicationBlacklist.aod
@@ -1,5 +1,19 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <neonContext xmlns="http://www.adito.de/2018/ao/Model" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" VERSION="1.1.1" xsi:schemaLocation="http://www.adito.de/2018/ao/Model adito://models/xsd/neonContext/1.1.1">
   <name>CommunicationBlacklist</name>
+  <title>Blacklist</title>
   <majorModelMode>DISTRIBUTED</majorModelMode>
+  <filterView>CommunicationBlacklistFilter_view</filterView>
+  <editView>CommunicationBlacklistEdit_view</editView>
+  <entity>CommunicationBlacklist_entity</entity>
+  <references>
+    <neonViewReference>
+      <name>664796b3-d90b-439d-b960-a1f09e00c99d</name>
+      <view>CommunicationBlacklistFilter_view</view>
+    </neonViewReference>
+    <neonViewReference>
+      <name>c08fe896-0181-4243-8639-cb96e302d3c8</name>
+      <view>CommunicationBlacklistEdit_view</view>
+    </neonViewReference>
+  </references>
 </neonContext>
diff --git a/neonView/CommunicationBlacklistEdit_view/CommunicationBlacklistEdit_view.aod b/neonView/CommunicationBlacklistEdit_view/CommunicationBlacklistEdit_view.aod
new file mode 100644
index 0000000000000000000000000000000000000000..7ca289f4bd074cc4bb8c460be628c7e0492d683b
--- /dev/null
+++ b/neonView/CommunicationBlacklistEdit_view/CommunicationBlacklistEdit_view.aod
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<neonView xmlns="http://www.adito.de/2018/ao/Model" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" VERSION="1.1.8" xsi:schemaLocation="http://www.adito.de/2018/ao/Model adito://models/xsd/neonView/1.1.8">
+  <name>CommunicationBlacklistEdit_view</name>
+  <majorModelMode>DISTRIBUTED</majorModelMode>
+  <size>SMALL</size>
+  <layout>
+    <boxLayout>
+      <name>layout</name>
+    </boxLayout>
+  </layout>
+  <children>
+    <genericViewTemplate>
+      <name>Generic</name>
+      <editMode v="true" />
+      <fields>
+        <entityFieldLink>
+          <name>5bf6cb8b-20da-4e64-ab86-e654e1f9f6a5</name>
+          <entityField>BLACKLIST_TYPE</entityField>
+        </entityFieldLink>
+        <entityFieldLink>
+          <name>bad5d056-972e-4d10-b3c7-a25bf6233cb5</name>
+          <entityField>BLACKLIST_FILTER</entityField>
+        </entityFieldLink>
+        <entityFieldLink>
+          <name>f74c2a6a-1b70-4914-8059-8866a0e5db77</name>
+          <entityField>START_DATE</entityField>
+        </entityFieldLink>
+        <entityFieldLink>
+          <name>2b2bb0e7-7cdf-42ca-8c0b-89d1d869fa44</name>
+          <entityField>END_DATE</entityField>
+        </entityFieldLink>
+        <entityFieldLink>
+          <name>947da750-50bd-47bb-af2d-7446b3756482</name>
+          <entityField>REASON</entityField>
+        </entityFieldLink>
+      </fields>
+    </genericViewTemplate>
+  </children>
+</neonView>
diff --git a/neonView/CommunicationBlacklistFilter_view/CommunicationBlacklistFilter_view.aod b/neonView/CommunicationBlacklistFilter_view/CommunicationBlacklistFilter_view.aod
new file mode 100644
index 0000000000000000000000000000000000000000..232b308af1e76c7e8cfb74d7ba3b0c25fa595806
--- /dev/null
+++ b/neonView/CommunicationBlacklistFilter_view/CommunicationBlacklistFilter_view.aod
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<neonView xmlns="http://www.adito.de/2018/ao/Model" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" VERSION="1.1.8" xsi:schemaLocation="http://www.adito.de/2018/ao/Model adito://models/xsd/neonView/1.1.8">
+  <name>CommunicationBlacklistFilter_view</name>
+  <majorModelMode>DISTRIBUTED</majorModelMode>
+  <filterable v="true" />
+  <layout>
+    <groupLayout>
+      <name>layout</name>
+    </groupLayout>
+  </layout>
+  <children>
+    <tableViewTemplate>
+      <name>Table</name>
+      <columns>
+        <neonTableColumn>
+          <name>86497fa4-acda-4041-8cbc-8d11838b4876</name>
+          <entityField>BLACKLIST_TYPE</entityField>
+        </neonTableColumn>
+        <neonTableColumn>
+          <name>304b2589-aabf-42f6-916f-a69cff5a1b00</name>
+          <entityField>START_DATE</entityField>
+        </neonTableColumn>
+        <neonTableColumn>
+          <name>fd5077c1-942b-42f0-8f44-80e9c568db44</name>
+          <entityField>END_DATE</entityField>
+        </neonTableColumn>
+        <neonTableColumn>
+          <name>d7050f91-da78-4ffc-a31c-30985d808473</name>
+          <entityField>REASON</entityField>
+        </neonTableColumn>
+      </columns>
+    </tableViewTemplate>
+  </children>
+</neonView>
diff --git a/process/Bulkmail_lib/process.js b/process/Bulkmail_lib/process.js
index 79427698a5871ead92f07e407e4b5e383cbe4e49..e2f169b247f42930d8870f0f15c789c10625d430 100644
--- a/process/Bulkmail_lib/process.js
+++ b/process/Bulkmail_lib/process.js
@@ -1,3 +1,6 @@
+import("system.logging");
+import("system.entities");
+import("MarketingCondition_lib");
 import("system.fileIO");
 import("system.project");
 import("Util_lib");
@@ -18,7 +21,6 @@ import("Email_lib");
 import("system.process");
 import("system.notification");
 import("Document_lib");
-import("system.db");
 
 /**
  * Functions for bulk mails.
@@ -59,57 +61,60 @@ BulkMailUtils.sendBulkMailOnServer = function (pBulkMailId, pTestRun, pUser)
  * 
  * @param {String} pBulkMailId                  <p>
  *                                              Id of the bulk mail.<br>
- * @param {Bool} pTestRun (optional)            <p>
+ * @param {Bool} pIsTestRun (optional)            <p>
  *                                              True indicates a Testrun<br>
  * @return {Object}                             <p>
  *                                              Count of sucessful and failed mails.<br>
  */
-BulkMailUtils.sendBulkMail = function (pBulkMailId, pTestRun)
+BulkMailUtils.sendBulkMail = function (pBulkMailId, pIsTestRun)
 {
+    if (pIsTestRun == undefined)
+    {
+        pIsTestRun = false;
+    }
+    
     var [templateId, subject, emailSender, createActivity, bulkMailName, useTemplateAttachments] = 
                 newSelect("DOCUMENTTEMPLATE_ID, SUBJECT, SENDER_EMAIL_ADDRESS, CREATEACTIVITIES, NAME, USE_TEMPLATE_ATTACHMENTS")
                     .from("BULKMAIL")
                     .where("BULKMAIL.BULKMAILID", pBulkMailId)
                     .arrayRow();
 
-    useTemplateAttachments = useTemplateAttachments == "1";
+    useTemplateAttachments = Utils.toBoolean(useTemplateAttachments);
         
     var template = BulkMailUtils.getBulkMailTemplate(pBulkMailId, templateId, true, useTemplateAttachments);
-    var recipientData;
     var testRecipientData;
     
-    if (pTestRun)
+    var recipientLoadConfig = entities.createConfigForLoadingRows()
+        .fields(["BULKMAILRECIPIENTID", "CONTACT_ID", "EMAIL_ADDRESS", "PERSON_ID", "ORGANISATION_ID"])
+        .entity("BulkmailRecipient_entity")
+        .provider("RecipientsToBeMailed")
+        .addParameter("BulkMailId_param", pBulkMailId)
+        .addParameter("IsTestMail_param", pIsTestRun);
+    
+    var recipientData = entities.getRows(recipientLoadConfig);
+    
+    if (pIsTestRun)
     {
-        recipientData = newSelect("BULKMAILRECIPIENTID, BULKMAILRECIPIENT.CONTACT_ID, BULKMAILRECIPIENT.EMAIL_ADDRESS, PERSON_ID, ORGANISATION_ID")
-            .from("CONTACT")
-            .join("BULKMAILRECIPIENT", "BULKMAILRECIPIENT.CONTACT_ID = CONTACT.CONTACTID")
-            .where("BULKMAILRECIPIENT.BULKMAIL_ID", pBulkMailId)
-            .and("BULKMAILRECIPIENT.TEST_RECIPIENT",1)
-            .table();
-            
         testRecipientData = newSelect("BULKMAILTESTRECIPIENT.CONTACT_ID, BULKMAILTESTRECIPIENT.EMAIL_ADDRESS")
             .from("BULKMAILTESTRECIPIENT")
-            .where("BULKMAILTESTRECIPIENT.BULKMAIL_ID",pBulkMailId)
-            .table();
-            
-    }
-    else
-    {
-        recipientData = newSelect("BULKMAILRECIPIENTID, BULKMAILRECIPIENT.CONTACT_ID, BULKMAILRECIPIENT.EMAIL_ADDRESS, PERSON_ID, ORGANISATION_ID")
-            .from("CONTACT")
-            .join("BULKMAILRECIPIENT", "BULKMAILRECIPIENT.CONTACT_ID = CONTACT.CONTACTID")
-            .where("BULKMAILRECIPIENT.BULKMAIL_ID", pBulkMailId)
-            .and("BULKMAILRECIPIENT.STATUS", $KeywordRegistry.bulkMailRecipientStatus$sent(), SqlBuilder.NOT_EQUAL())
-            .and(ContactUtils.getCommRestrictionCondition($KeywordRegistry.communicationMediumCampaign$mail(), true))
+            .where("BULKMAILTESTRECIPIENT.BULKMAIL_ID", pBulkMailId)
             .table();
     }
     var mailrunId = util.getNewUUID();
     
-    
-    db.insertData("MAIL_RUN", ["MAIL_RUNID","OBJECT_ROWID","OBJECT_TYPE","DATE_RUN_START","STATUS","TESTRUN"], null, [mailrunId,pBulkMailId,"BULKMAIL",vars.get("$sys.date"),$KeywordRegistry.bulkMailStatus$beingSent(),(pTestRun?1:0)]);
+    new SqlBuilder()
+        .tableName("MAIL_RUN")
+        .insertFields({
+            "MAIL_RUNID": mailrunId,
+            "OBJECT_ROWID": pBulkMailId,
+            "OBJECT_TYPE": "Bulkmail",
+            "DATE_RUN_START": vars.get("$sys.date"),
+            "STATUS": $KeywordRegistry.bulkMailStatus$beingSent(),
+            "TESTRUN": pIsTestRun ? 1 : 0
+        });
     
     
-    var contactIds = recipientData.map(function (e) {return e[1];});
+    var contactIds = recipientData.map(function (recipient) {return recipient["CONTACT_ID"];});
     var successIds = [];
     var failedIds = [];
     var sentDate = vars.get("$sys.date");
@@ -120,96 +125,115 @@ BulkMailUtils.sendBulkMail = function (pBulkMailId, pTestRun)
     
     var bulkMailLink = [["BulkMail", pBulkMailId]];
     var activitySubject = translate.withArguments("Bulk mail \"%0\" sent", [bulkMailName]);
-    if (!pTestRun){
-            for (let i = 0, l = recipientData.length; i < l; i++)
+    if (!pIsTestRun)
+    {
+        recipientData.forEach(function (recipient)
+        {
+            let isSuccess = false;
+            let recipientId = recipient["BULKMAILRECIPIENTID"];
+            let contactId = recipient["CONTACT_ID"];
+            let emailAddress = recipient["EMAIL_ADDRESS"];
+            let personId = recipient["PERSON_ID"];
+            let organisationId = recipient["ORGANISATION_ID"];
+            let email = mails[contactId];
+            let mailLogId = util.getNewUUID();
+            if (email !== undefined && emailAddress)
+            {
+                email.toRecipients = [emailAddress];
+                email.sender = emailSender;
+                email.subject = subjects[contactId];
 
+                BulkMailUtils.storeEmlFile(pBulkMailId, mailrunId, mailLogId,email.getEML());
+                isSuccess = email.send();
+            }
+
+            //set the recipient status to 'sent' or 'failed'
+            new SqlBuilder()
+                .tableName("MAIL_LOG")
+                .insertFields({
+                    "MAIL_LOGID": mailLogId,
+                    "MAIL_RUN_ID": mailrunId,
+                    "CONTACT_ID": contactId,
+                    "STATUS": isSuccess ? $KeywordRegistry.bulkMailRecipientStatus$sent() : $KeywordRegistry.bulkMailRecipientStatus$failed(),
+                    "SENDER_EMAIL": emailSender,
+                    "RECIPIENT_EMAIL": emailAddress,
+                    "MAILING_SUBJECT": subjects[contactId],
+                    "DATE_SEND": vars.get("$sys.date")
+                });
+                
+            //TODO: Klären was von alter Logik noch bleiben soll. Status macht nur Sinn wenn jede Bulkmail nur einmal gesendet wird. Bleiben Activitys?
+            Array.prototype.push.call(isSuccess ? successIds : failedIds, recipientId);
+            if (isSuccess && createActivity == "1")
             {
-        
-                let isSuccess = false;
-                let contactId = recipientData[i][1];
-                let email = mails[contactId];
-                let mailLogId = util.getNewUUID();
-                if (email !== undefined && recipientData[i][2])
-                {
-                    email.toRecipients = [recipientData[i][2]];
-                    email.sender = emailSender;
-                    email.subject = subjects[contactId];
-            
-            
-                    
-                    this.storeEmlFile(pBulkMailId, mailrunId, mailLogId,email.getEML());
-                    isSuccess = email.send();
-                }
-        
-                if (recipientData[i][0])    //set the recipient status to 'sent' or 'failed'
-                {
-            
-                    db.insertData("MAIL_LOG", ["MAIL_LOGID","MAIL_RUN_ID","CONTACT_ID","STATUS","SENDER_EMAIL","RECIPIENT_EMAIL","MAILING_SUBJECT","DATE_SEND"], null, [mailLogId,mailrunId,contactId,(isSuccess ?$KeywordRegistry.bulkMailRecipientStatus$sent(): $KeywordRegistry.bulkMailRecipientStatus$failed()),emailSender,recipientData[i][2],subjects[contactId],vars.get("$sys.date")]);
-                    //TODO: Klären was von alter Logik noch bleiben soll. Status macht nur Sinn wenn jede Bulkmail nur einmal gesendet wird. Bleiben Activitys?
-                    Array.prototype.push.call(isSuccess ? successIds : failedIds, recipientData[i][0]);
-                    if (isSuccess && createActivity == "1")
-                    {
-                        let activityData = {
-                            categoryKeywordId : $KeywordRegistry.activityCategory$mail(),
-                            directionKeywordId : $KeywordRegistry.activityDirection$outgoing(),
-                            subject : activitySubject,
-                            content : email.body
-                        };
-                        let contactLink = [[ContactUtils.getContextByPersOrg(recipientData[i][3], recipientData[i][4]), recipientData[i][1]]];
-                        ActivityUtils.insertNewActivity(activityData, bulkMailLink.concat(contactLink));
-                    }
-                }
+                let activityData = {
+                    categoryKeywordId : $KeywordRegistry.activityCategory$mail(),
+                    directionKeywordId : $KeywordRegistry.activityDirection$outgoing(),
+                    subject : activitySubject,
+                    content : email.body
+                };
+                let contactLink = [[ContactUtils.getContextByPersOrg(personId, organisationid), contactId]];
+                ActivityUtils.insertNewActivity(activityData, bulkMailLink.concat(contactLink));
             }
-    
-            newWhereIfSet("BULKMAILRECIPIENT.BULKMAILRECIPIENTID", successIds, SqlBuilder.IN())
-            .updateData(true, "BULKMAILRECIPIENT", ["STATUS", "SENTDATE"], null, [$KeywordRegistry.bulkMailRecipientStatus$sent(), sentDate]);
-    
-    
-            newWhereIfSet("BULKMAILRECIPIENT.BULKMAILRECIPIENTID", failedIds, SqlBuilder.IN())
-            .updateData(true, "BULKMAILRECIPIENT", ["STATUS", "SENTDATE"], null, [$KeywordRegistry.bulkMailRecipientStatus$failed(), sentDate]);
-    
-            newWhere("MAIL_RUN.MAIL_RUNID",mailrunId)
-            .updateData(true,"MAIL_RUN",["STATUS","DATE_RUN_FINISHED"],null,[$KeywordRegistry.bulkMailStatus$sent(),vars.get("$sys.date")]);
-            
-            
-            newWhere("BULKMAIL.BULKMAILID", pBulkMailId)
-            .updateData(true, "BULKMAIL", ["STATUS"], null, [$KeywordRegistry.bulkMailStatus$sent()]);
+        });
+
+        newWhereIfSet("BULKMAILRECIPIENT.BULKMAILRECIPIENTID", successIds, SqlBuilder.IN())
+            .updateFields({
+                "STATUS": $KeywordRegistry.bulkMailRecipientStatus$sent(),
+                "SENTDATE": sentDate
+            });
+
+        newWhereIfSet("BULKMAILRECIPIENT.BULKMAILRECIPIENTID", failedIds, SqlBuilder.IN())
+            .updateFields({
+                "STATUS": $KeywordRegistry.bulkMailRecipientStatus$failed(),
+                "SENTDATE": sentDate
+            });
+
+        newWhere("MAIL_RUN.MAIL_RUNID", mailrunId)
+            .updateFields({
+                "STATUS": $KeywordRegistry.bulkMailStatus$sent(),
+                "DATE_RUN_FINISHED": vars.get("$sys.date")
+            });
+
+        newWhere("BULKMAIL.BULKMAILID", pBulkMailId)
+            .updateFields({
+                "STATUS": $KeywordRegistry.bulkMailStatus$sent()
+            });
+    }
+    else
+    {
+        for (let i = 0, l = recipientData.length; i < l; i++)
+        {
             
-        }else{
-            for (let i = 0, l = recipientData.length; i < l; i++){
-                logging.log("here");
-                let isSuccess = false;
-                let contactId = recipientData[i][1];
-                let email = mails[contactId];
-                
-                
-                if (email !== undefined)
-                {
-                    logging.log("email not undefined");
-                    logging.log(JSON.stringify(testRecipientData));
-                    email.sender = emailSender;
-                    email.subject = "Test: "+subjects[contactId];
-                    for (let j =0; j<testRecipientData.length;j++){
-                        if(testRecipientData[j][1]){
-                            email.toRecipients = [testRecipientData[j][1]];
-                            isSuccess = email.send();
-                            
-                            Array.prototype.push.call(isSuccess ? successIds : failedIds, recipientData[i][0]);
-                            
-                            if(testRecipientData[j][0]){
-                                let mailLogId = util.getNewUUID();
-                                db.insertData("MAIL_LOG", ["MAIL_LOGID","MAIL_RUN_ID","CONTACT_ID","STATUS","SENDER_EMAIL","RECIPIENT_EMAIL","MAILING_SUBJECT","DATE_SEND"], null, [mailLogId,mailrunId,testRecipientData[j][0],(isSuccess ?$KeywordRegistry.bulkMailRecipientStatus$sent(): $KeywordRegistry.bulkMailRecipientStatus$failed()),emailSender,testRecipientData[j][1],email.subject,vars.get("$sys.date")]);
-                                this.storeEmlFile(pBulkMailId, mailrunId, mailLogId,email.getEML());
-                            }
+            let isSuccess = false;
+            let contactId = recipientData[i][1];
+            let email = mails[contactId];
+
+            if (email !== undefined)
+            {
+                email.sender = emailSender;
+                email.subject = "Test: "+subjects[contactId];
+                for (let j =0; j<testRecipientData.length;j++){
+                    if(testRecipientData[j][1]){
+                        email.toRecipients = [testRecipientData[j][1]];
+                        isSuccess = email.send();
+
+                        Array.prototype.push.call(isSuccess ? successIds : failedIds, recipientData[i][0]);
+
+                        if (testRecipientData[j][0])
+                        {
+                            let mailLogId = util.getNewUUID();
+                            db.insertData("MAIL_LOG", ["MAIL_LOGID","MAIL_RUN_ID","CONTACT_ID","STATUS","SENDER_EMAIL","RECIPIENT_EMAIL","MAILING_SUBJECT","DATE_SEND"], null, [mailLogId,mailrunId,testRecipientData[j][0],(isSuccess ?$KeywordRegistry.bulkMailRecipientStatus$sent(): $KeywordRegistry.bulkMailRecipientStatus$failed()),emailSender,testRecipientData[j][1],email.subject,vars.get("$sys.date")]);
+                            this.storeEmlFile(pBulkMailId, mailrunId, mailLogId,email.getEML());
                         }
-                }
-    
-                }
+                    }
             }
-            newWhere("MAIL_RUN.MAIL_RUNID",mailrunId)
-            .updateData(true,"MAIL_RUN",["STATUS","DATE_RUN_FINISHED"],null,[$KeywordRegistry.bulkMailStatus$sent(),vars.get("$sys.date")]);
 
+            }
         }
+        newWhere("MAIL_RUN.MAIL_RUNID",mailrunId)
+            .updateData(true,"MAIL_RUN",["STATUS","DATE_RUN_FINISHED"],null,[$KeywordRegistry.bulkMailStatus$sent(),vars.get("$sys.date")]);
+
+    }
         
     return {
         sucessful : successIds.length,
diff --git a/process/KeywordRegistry_basic/process.js b/process/KeywordRegistry_basic/process.js
index 551fda9451a78bd17c16bd403f32ea7c48b3a288..c653469b40f1867b5889e7c3d60a77802e30d897 100644
--- a/process/KeywordRegistry_basic/process.js
+++ b/process/KeywordRegistry_basic/process.js
@@ -376,4 +376,7 @@ $KeywordRegistry.interestStatus$inactive = function(){return "INTEREST_INACTIVE"
 
 $KeywordRegistry.interestLinkStatus = function(){return "InterestLinkStatus";};
 $KeywordRegistry.interestLinkStatus$subscribed = function(){return "INTERESTLINK_SUBSCRIBED";};
-$KeywordRegistry.interestLinkStatus$notSubscribed = function(){return "INTERESTLINK_NOTSUBSCRIBED";};
\ No newline at end of file
+$KeywordRegistry.interestLinkStatus$notSubscribed = function(){return "INTERESTLINK_NOTSUBSCRIBED";};
+
+$KeywordRegistry.communicationBlacklistType = function(){return "CommunicationBlacklistType";};
+$KeywordRegistry.communicationBlacklistType$emailRecipientFilter = function(){return "BLACKLIST_TYPE_EMAILRECIPIENT_FILTER";};
\ No newline at end of file
diff --git a/process/MarketingCondition_lib/MarketingCondition_lib.aod b/process/MarketingCondition_lib/MarketingCondition_lib.aod
index 3644f27fcc64d6312777024bbb7304b85749f27a..5e55b885b065114498b81f6bb9b0c95e4eafbb67 100644
--- a/process/MarketingCondition_lib/MarketingCondition_lib.aod
+++ b/process/MarketingCondition_lib/MarketingCondition_lib.aod
@@ -3,6 +3,7 @@
   <name>MarketingCondition_lib</name>
   <majorModelMode>DISTRIBUTED</majorModelMode>
   <process>%aditoprj%/process/MarketingCondition_lib/process.js</process>
+  <alias>Data_alias</alias>
   <variants>
     <element>LIBRARY</element>
   </variants>
diff --git a/process/MarketingCondition_lib/process.js b/process/MarketingCondition_lib/process.js
index 8e14db2b5057fabbffe1b5bd71d1340c32ae5326..b6dfc6cd6aa52a0818f2e148004ecad454b82c9d 100644
--- a/process/MarketingCondition_lib/process.js
+++ b/process/MarketingCondition_lib/process.js
@@ -1,13 +1,203 @@
+import("Util_lib");
+import("JditoFilter_lib");
+import("system.vars");
+import("Sql_lib");
+import("KeywordRegistry_basic");
 
-function MarketingCondition () 
+/**
+ * Object for building communication settings sql conditions.
+ */
+function CommunicationSettingsCondition ()
 {
+    this._contactIdSql = "CONTACT.CONTACTID";
+    this._channelType = null;
+    this._medium = null;
+    this._channelIdSql = null;
+    this._settingStatus = null;
+    this._existsOperator = SqlBuilder.EXISTS();
+}
+
+CommunicationSettingsCondition.prototype.contactIdField = function (pContactIdSql)
+{
+    this._contactIdSql = pContactIdSql;
+    return this;
+}
+
+CommunicationSettingsCondition.prototype.existNoSettings = function ()
+{
+    this._existsOperator = SqlBuilder.NOT_EXISTS();
+    return this;
+}
+
+CommunicationSettingsCondition.prototype.existSettings = function ()
+{
+    this._existsOperator = SqlBuilder.EXISTS();
+    return this;
+}
+
+/**
+ * Sets the medium and the source of the communication address for filtering the communication settings.
+ * 
+ * @param {String} pMedium      communication medium
+ * @param {String} pAddressSql  sql field or expression for the communication address
+ * @return {CommunicationSettingsCondition} current object
+ */
+CommunicationSettingsCondition.prototype.medium = function (pMedium, pAddressSql)
+{
+    this._channelType = $KeywordRegistry.communicationChannelType$communication();
+    this._medium = pMedium;
+    if (pAddressSql)
+        this._channelIdSql = pAddressSql;
+    return this;
+}
+
+/**
+ * Sets the medium to email and the source of the email address for filtering the communication settings.
+ * 
+ * @param {String} pAddressSql  sql field or expression for the email address
+ * @return {CommunicationSettingsCondition} current object
+ */
+CommunicationSettingsCondition.prototype.emails = function (pAddressSql)
+{
+    return this.medium($KeywordRegistry.communicationMediumCampaign$mail(), pAddressSql);
+}
+
+/**
+ * Sets the source of the address to filter the communication settings by address.
+ * 
+ * @param {String} pAddressSql  sql field or expression for the address id
+ * @return {CommunicationSettingsCondition} current object
+ */
+CommunicationSettingsCondition.prototype.address = function (pAddressSql)
+{
+    this._channelType = $KeywordRegistry.communicationChannelType$address();
+    this._channelIdSql = pAddressSql;
+    return this;
+}
+
+/**
+ * Only fetch communication settings with the status 'rejected'
+ * 
+ * @return {CommunicationSettingsCondition} current object
+ */
+CommunicationSettingsCondition.prototype.rejected = function ()
+{
+    this._settingStatus = $KeywordRegistry.communicationSettingStatus$rejected();
+    return this;
+}
+
+/**
+ * Only fetch communication settings with the status 'allowed'
+ * 
+ * @return {CommunicationSettingsCondition} current object
+ */
+CommunicationSettingsCondition.prototype.allowed = function ()
+{
+    this._settingStatus = $KeywordRegistry.communicationSettingStatus$allowed();
+    return this;
+}
+
+/**
+ * Only fetch communication settings with the status 'pending'
+ * 
+ * @return {CommunicationSettingsCondition} current object
+ */
+CommunicationSettingsCondition.prototype.pending = function ()
+{
+    this._settingStatus = $KeywordRegistry.communicationSettingStatus$pending();
+    return this;
+}
+
+/**
+ * Builds a sub select for fetching the communication settings with the properies as configured.
+ * 
+ * @param {String|Array} [pSelectFields=COMMUNICATIONSETTINGSID]  columns to select
+ * @return {SqlBuilder} SqlBuilder object with the full sql select
+ */
+CommunicationSettingsCondition.prototype.buildSelect = function (pSelectFields)
+{
+    if (pSelectFields == undefined)
+        pSelectFields = "COMMUNICATIONSETTINGS.COMMUNICATIONSETTINGSID";
+    var sql = newSelect(pSelectFields)
+        .from("COMMUNICATIONSETTINGS")
+        .where("COMMUNICATIONSETTINGS.CONTACT_ID = " + this._contactIdSql)
+        .andIfSet("COMMUNICATIONSETTINGS.STATUS", this._settingStatus);
+    
+    var channelCondition = newWhere("COMMUNICATIONSETTINGS.CHANNEL_TYPE", $KeywordRegistry.communicationChannelType$global());
+    if (this._channelType)
+    {
+        var specificChannelCondition = newWhere("COMMUNICATIONSETTINGS.CHANNEL_TYPE", this._channelType)
+            .andIfSet("COMMUNICATIONSETTINGS.MEDIUM", this._medium);
+        if (this._channelIdSql)
+        {
+            specificChannelCondition.and(newWhere("COMMUNICATIONSETTINGS.CHANNEL_ID = " + this._channelIdSql)
+                .or("COMMUNICATIONSETTINGS.CHANNEL_ID is null"));
+        }
+        channelCondition.or(specificChannelCondition);
+    }
+    sql.and(channelCondition);
     
+    return sql;
+}
+
+/**
+ * Builds a sql condition for fetching only contacts with communication settings that have the configured properties.
+ * 
+ * @return {SqlBuilder} sql condition
+ */
+CommunicationSettingsCondition.prototype.buildCondition = function ()
+{
+    return newWhere(null, this.buildSelect(), this._existsOperator);
 }
 
+
 /* required functionality:
  * - make communication settings condition
  * - make interest condition
  * - make blacklist condition
  * - make full condition
  * - check for single contact
- */
\ No newline at end of file
+ */
+
+function CommunicationBlacklist () 
+{
+    this.filter = null;
+}
+
+CommunicationBlacklist.getMailRecipientBlacklist = function ()
+{
+    var currentDate = vars.get("$sys.date");
+    var filters = newSelect("BLACKLIST_FILTER")
+        .from("COMMUNICATIONBLACKLIST")
+        .where("COMMUNICATIONBLACKLIST.BLACKLIST_TYPE", $KeywordRegistry.communicationBlacklistType$contactFilter())
+        .and(newWhere("COMMUNICATIONBLACKLIST.START_DATE", currentDate, SqlBuilder.GREATER_OR_EQUAL())
+            .or("COMMUNICATIONBLACKLIST.START_DATE is null"))
+        .and(newWhere("COMMUNICATIONBLACKLIST.END_DATE", currentDate, SqlBuilder.LESS_OR_EQUAL()))
+            .or("COMMUNICATIONBLACKLIST.END_DATE is null")
+        .table();
+    
+    var filterMappingFn = function ([blacklistFilter])
+    {
+        blacklistFilter = JSON.parse(blacklistFilter);
+        if (!blacklistFilter.filter || blacklistFilter.entity != "AnyContact" || Utils.isNullOrEmpty(blacklistFilter.filter.childs))
+            return null;
+        
+        return blacklistFilter.filter;
+    };
+    
+    var blacklist = new CommunicationBlacklist();
+    blacklist.filter = {
+        type: "group",
+        operator: "AND",
+        childs: filters.map(filterMappingFn).filter(Utils.isObject)
+    };
+    return blacklist;
+}
+
+CommunicationBlacklistCondition.prototype.buildCondition = function ()
+{
+    return new FilterSqlTranslator()
+        .filter(this.filter)
+        .table("BULKMAILRECIPIENT")
+        .getSqlCondition();
+}
diff --git a/process/MarketingCondition_test/MarketingCondition_test.aod b/process/MarketingCondition_test/MarketingCondition_test.aod
new file mode 100644
index 0000000000000000000000000000000000000000..e1fabb654e0c28f0747afcbf030302c26d996392
--- /dev/null
+++ b/process/MarketingCondition_test/MarketingCondition_test.aod
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<process xmlns="http://www.adito.de/2018/ao/Model" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" VERSION="1.2.2" xsi:schemaLocation="http://www.adito.de/2018/ao/Model adito://models/xsd/process/1.2.2">
+  <name>MarketingCondition_test</name>
+  <majorModelMode>DISTRIBUTED</majorModelMode>
+  <process>%aditoprj%/process/MarketingCondition_test/process.js</process>
+  <variants>
+    <element>EXECUTABLE</element>
+  </variants>
+</process>
diff --git a/process/MarketingCondition_test/process.js b/process/MarketingCondition_test/process.js
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/process/SetCommunicationSetting_workflowService/SetCommunicationSetting_workflowService.aod b/process/SetCommunicationSetting_workflowService/SetCommunicationSetting_workflowService.aod
new file mode 100644
index 0000000000000000000000000000000000000000..ded82a9d7a27ef0c55d1456a27778be078d51da5
--- /dev/null
+++ b/process/SetCommunicationSetting_workflowService/SetCommunicationSetting_workflowService.aod
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<process xmlns="http://www.adito.de/2018/ao/Model" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" VERSION="1.2.2" xsi:schemaLocation="http://www.adito.de/2018/ao/Model adito://models/xsd/process/1.2.2">
+  <name>SetCommunicationSetting_workflowService</name>
+  <majorModelMode>DISTRIBUTED</majorModelMode>
+  <process>%aditoprj%/process/SetCommunicationSetting_workflowService/process.js</process>
+  <alias>Data_alias</alias>
+  <variants>
+    <element>WORKFLOW</element>
+  </variants>
+</process>
diff --git a/process/SetCommunicationSetting_workflowService/process.js b/process/SetCommunicationSetting_workflowService/process.js
new file mode 100644
index 0000000000000000000000000000000000000000..0af3ff5f171ea831dc43b0f859acb6bde6ed5cb2
--- /dev/null
+++ b/process/SetCommunicationSetting_workflowService/process.js
@@ -0,0 +1,8 @@
+import("KeywordRegistry_basic");
+import("system.vars");
+
+var variables = JSON.parse(vars.get("$local.value"));
+var contactId = variables.contactId || variables.targetId;
+var channelType = variables.channelType;
+var channelId = variables.channelId;
+var status = variables.status || $KeywordRegistry.communicationSettingStatus$rejected();
\ No newline at end of file
diff --git a/process/SetInterestLink_workflowService/SetInterestLink_workflowService.aod b/process/SetInterestLink_workflowService/SetInterestLink_workflowService.aod
new file mode 100644
index 0000000000000000000000000000000000000000..b86df5e7047ab1a0cc534a1f4dfbad242e2d94a7
--- /dev/null
+++ b/process/SetInterestLink_workflowService/SetInterestLink_workflowService.aod
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<process xmlns="http://www.adito.de/2018/ao/Model" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" VERSION="1.2.2" xsi:schemaLocation="http://www.adito.de/2018/ao/Model adito://models/xsd/process/1.2.2">
+  <name>SetInterestLink_workflowService</name>
+  <majorModelMode>DISTRIBUTED</majorModelMode>
+  <process>%aditoprj%/process/SetInterestLink_workflowService/process.js</process>
+  <alias>Data_alias</alias>
+  <variants>
+    <element>WORKFLOW</element>
+  </variants>
+</process>
diff --git a/process/SetInterestLink_workflowService/process.js b/process/SetInterestLink_workflowService/process.js
new file mode 100644
index 0000000000000000000000000000000000000000..5a6447a1a3ecfafc87bf0ab271a52683c138bf81
--- /dev/null
+++ b/process/SetInterestLink_workflowService/process.js
@@ -0,0 +1,34 @@
+import("KeywordRegistry_basic");
+import("system.vars");
+import("system.util");
+import("Sql_lib");
+
+var variables = JSON.parse(vars.get("$local.value"));
+var contactId = variables.contactId || variables.targetId;
+var interestId = variables.interestId;
+var status = variables.status || $KeywordRegistry.interestLinkStatus$notSubscribed();
+
+var interestLinkId = new SqlBuilder()
+    .select("INTERESTLINKID")
+    .from("INTERESTLINK")
+    .where("INTERESTLINK.CONTACT_ID", contactId)
+    .and("INTERESTLINK.INTEREST_ID", interestId)
+    .and("INTERESTLINK.STATUS", status)
+    .cell();
+
+if (interestLinkId)
+{
+    newWhere("INTERESTLINK.INTERESTLINKID", interestLinkId)
+        .updateFields({"STATUS": status});
+}
+else
+{
+    new SqlBuilder()
+        .tableName("INTERESTLINK")
+        .insertFields({
+            "INTERESTLINKID": util.getNewUUID(),
+            "INTEREST_ID": interestId,
+            "CONTACT_ID": contactId,
+            "STATUS": status
+        });
+}
\ No newline at end of file
diff --git a/process/Sql_lib/process.js b/process/Sql_lib/process.js
index f3708067a34ad01930d3ade922c9520011e1bb91..df4abe68531e7ae543e40806661fde43e4c967c9 100644
--- a/process/Sql_lib/process.js
+++ b/process/Sql_lib/process.js
@@ -1017,6 +1017,22 @@ SqlBuilder.prototype.selectDistinct = function (pFields)
     return this;
 }
 
+/**
+ * Sets the select clause to "select count(...)"
+ * @param {String} [pField=*]   sql column to count, if omitted "count(*)" will be used
+ * @return {SqlBuilder} current SqlBuilder object
+ */
+SqlBuilder.prototype.selectCount = function (pField)
+{
+    if (pField == undefined)
+    {
+        pField = "*";
+    }
+    this._select = SqlBuilder._getStatement(pField, "select count(", ")", true, true);
+    return this;
+}
+
+
 /**
  * sets an alias-name which is added at some places if this SqlBuilder is used as subselect (e.g. in .select(), .join(), .from(), ...)
  * @param {String} pSubselectAlias
diff --git a/process/gitLabWebhook_rest/gitLabWebhook_rest.aod b/process/gitLabWebhook_rest/gitLabWebhook_rest.aod
new file mode 100644
index 0000000000000000000000000000000000000000..76c89ec8f521bfb246461ecd2ce39c83d21954e2
--- /dev/null
+++ b/process/gitLabWebhook_rest/gitLabWebhook_rest.aod
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<process xmlns="http://www.adito.de/2018/ao/Model" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" VERSION="1.2.2" xsi:schemaLocation="http://www.adito.de/2018/ao/Model adito://models/xsd/process/1.2.2">
+  <name>gitLabWebhook_rest</name>
+  <majorModelMode>DISTRIBUTED</majorModelMode>
+  <process>%aditoprj%/process/gitLabWebhook_rest/process.js</process>
+  <publishAsWebservice v="true" />
+  <style>REST</style>
+  <restAcceptedMimeType>application/json</restAcceptedMimeType>
+  <restDeliveredMimeType>application/json</restDeliveredMimeType>
+  <loginTypeId>
+    <element>internal.none</element>
+  </loginTypeId>
+  <variants>
+    <element>EXECUTABLE</element>
+  </variants>
+</process>
diff --git a/process/gitLabWebhook_rest/process.js b/process/gitLabWebhook_rest/process.js
new file mode 100644
index 0000000000000000000000000000000000000000..696a523a0c55d16fc54e2686d819cb5ca2f56a15
--- /dev/null
+++ b/process/gitLabWebhook_rest/process.js
@@ -0,0 +1,134 @@
+import("system.auth");
+import("system.net");
+import("system.util");
+import("system.logging");
+
+function restpost (pRequest)
+{
+    var config = {
+        active: true,
+        gitlabToken: "youtube.com/watch?v=dQw4w9WgXcQ",
+        teamsUrl: "https://aditosoftware.webhook.office.com/webhookb2/85493214-0e4b-4577-8fc4-0e421a2dc196@c7308693-318b-4725-9e59-73b455c6ae05/IncomingWebhook/66a53e7f47c040eda146b372c742169b/70528b00-bbf1-45e2-9925-0cc55ff8951b",
+        push: {
+            active: true,
+            targetBranchRegExp: null
+            
+        },
+        mergeRequest: {
+            active: true,
+            onlyUnassigned: true,
+            excludeWIP: true,
+            priorityLabels: ["critical"]
+        },
+        tag: {
+            active: true
+        }
+    };
+    
+    logging.log(pRequest);
+    var request = JSON.parse(pRequest);
+    var secretToken = request.header["X-Gitlab-Token"] || request.header["X-gitlab-token"];
+    if (secretToken != config.gitlabToken)
+    {
+        request.response.httpStatusCode = 403;
+        return JSON.stringify(request);
+    }
+    var body = JSON.parse(util.decodeBase64String(request.body));
+    var teamsMessage = _getMessage(body, config);
+    if (teamsMessage)
+    {
+        var authConfig = auth.createConfigForNoAuth();
+        var restConfig = net.createConfigForRestWebserviceCall()
+            .actionType(net.POST)
+            .url(config.teamsUrl)
+            .dataTypeSend("application/json")
+            .dataTypeJDitoSend(util.DATA_TEXT)
+            .requestEntity(JSON.stringify(teamsMessage));
+            logging.log(JSON.stringify(teamsMessage))
+        net.callRestWebservice(restConfig, authConfig);
+    }
+    
+    request.response.httpStatusCode = 200;
+    return JSON.stringify(request);
+    
+    function _getMessage (pEvent, pConfig)
+    {
+        if (!pConfig.active)
+            return null;
+        
+        var message = {
+            "@context": "https://schema.org/extensions",
+            "@type": "MessageCard",
+            "title": "Merged branch whatever into whatever",
+            "text": "-link- merged by That Guy",
+            "sections": [{
+                "activityTitle": "420 commits added"
+            }]
+        };
+        
+        if (pEvent.object_kind == "push")
+            return _getPushMessage(pEvent, pConfig.push);
+        if (pEvent.object_kind == "merge_request")
+            return _getMergeRequestMessage(pEvent, pConfig.mergeRequest);
+        if (pEvent.object_kind == "tag_push")
+            return _getTagMessage(pEvent, pConfig.tag);
+
+        return null;
+
+        function _getPushMessage (pEvent, pConfig)
+        {
+            if (!pConfig.active)
+                return null;
+            return null;
+        }
+
+        function _getMergeRequestMessage (pEvent, pConfig)
+        {
+            if (!pConfig.active || pEvent.object_attributes.action != "open")
+                return null;
+            
+            var info = pEvent.object_attributes;
+            var labels = pEvent.labels.map(function (label)
+            {
+                return label.title;
+            });
+            var isCritical = pConfig.priorityLabels.some(function (prioLabel) {return labels.includes(prioLabel);});
+            var isUnassigned = !pEvent.assignees || pEvent.assignees.length === 0;
+            var isMessageRequired = isCritical
+                || ((!pConfig.onlyUnassigned || isUnassigned)
+                    && (!pConfig.excludeWIP || !info.work_in_progress))
+            if (!isMessageRequired)
+                return null;
+            
+            var objectTitle = "Merge Request";
+            if (isCritical)
+                objectTitle = "Critical Merge Request";
+            else if (isUnassigned)
+                objectTitle = "Unassigned Merge Request";
+            
+            var title = objectTitle + " opened by " + pEvent.user.name;
+            var message = {
+                "@context": "https://schema.org/extensions",
+                "@type": "MessageCard",
+                "summary": title,
+                "sections": [{
+                    "activityTitle": title + " in <a href=\"" + pEvent.project.web_url + "\">" + pEvent.project.path_with_namespace + "</a>",
+                    "activitySubtitle": "<a href=\"" + info.url + "\">!" + info.iid + "</a> " + info.title
+                        + " | Request to merge " + info.source_branch + " into " + info.target_branch
+                }]
+            };
+            if (isCritical)
+                message.themeColor = "F00420";
+            
+            return message;
+        }
+
+        function _getTagMessage (pEvent, pConfig)
+        {
+            if (!pConfig.active)
+                return null;
+            return null;
+        }
+    }
+}
+