diff --git a/.liquibase/Data_alias/basic/2019.2/DocuTemplatePlaceholder_Keywords.xml b/.liquibase/Data_alias/basic/2019.2/DocuTemplatePlaceholder_Keywords.xml
deleted file mode 100644
index 6c892fd2a037f26d7eb31176d465b458b0ff241d..0000000000000000000000000000000000000000
--- a/.liquibase/Data_alias/basic/2019.2/DocuTemplatePlaceholder_Keywords.xml
+++ /dev/null
@@ -1,57 +0,0 @@
-<?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="f236804e-e3f2-43e4-8e5a-1ba48522d8c5">
-        <insert tableName="AB_KEYWORD_ENTRY">
-            <column name="AB_KEYWORD_ENTRYID" value="bdecb990-19ce-4710-afd7-321ed5bae93d"/>
-            <column name="KEYID" value="letterSalutation"/>
-            <column name="TITLE" value="Letter salutation"/>
-            <column name="CONTAINER" value="TextPlaceholder"/>
-            <column name="SORTING" valueNumeric="0"/>
-            <column name="ISACTIVE" valueNumeric="1"/>
-            <column name="ISESSENTIAL" valueNumeric="1"/>
-        </insert>
-        <insert tableName="AB_KEYWORD_ENTRY">
-            <column name="AB_KEYWORD_ENTRYID" value="5f31c2cd-dce9-4eeb-872c-1a3005ea3210"/>
-            <column name="KEYID" value="country"/>
-            <column name="TITLE" value="Country"/>
-            <column name="CONTAINER" value="TextPlaceholder"/>
-            <column name="SORTING" valueNumeric="1"/>
-            <column name="ISACTIVE" valueNumeric="1"/>
-            <column name="ISESSENTIAL" valueNumeric="1"/>
-        </insert>
-        <insert tableName="AB_KEYWORD_ENTRY">
-            <column name="AB_KEYWORD_ENTRYID" value="fd9dba2b-92aa-4957-841b-4ec85dc92817"/>
-            <column name="KEYID" value="zipCode"/>
-            <column name="TITLE" value="ZIP"/>
-            <column name="CONTAINER" value="TextPlaceholder"/>
-            <column name="SORTING" valueNumeric="2"/>
-            <column name="ISACTIVE" valueNumeric="1"/>
-            <column name="ISESSENTIAL" valueNumeric="1"/>
-        </insert>
-        <insert tableName="AB_KEYWORD_ATTRIBUTE">
-                <column name="AB_KEYWORD_ATTRIBUTEID" value="ebf7de02-1873-4068-b551-c5348bab4fc6"/>
-                <column name="NAME" value="addressFormat"/>
-                <column name="CONTAINER" value="TextPlaceholder"/>
-                <column name="TYPE" value="CHAR_VALUE"/>
-        </insert>
-        <insert tableName="AB_KEYWORD_ATTRIBUTERELATION">
-                <column name="AB_KEYWORD_ATTRIBUTERELATIONID" value="4cd39532-0cd1-4828-961d-76165af2f4c3"/>
-                <column name="AB_KEYWORD_ENTRY_ID" value="bdecb990-19ce-4710-afd7-321ed5bae93d"/>
-                <column name="AB_KEYWORD_ATTRIBUTE_ID" value="ebf7de02-1873-4068-b551-c5348bab4fc6"/>
-                <column name="CHAR_VALUE" value=""/>
-        </insert>
-        <insert tableName="AB_KEYWORD_ATTRIBUTERELATION">
-                <column name="AB_KEYWORD_ATTRIBUTERELATIONID" value="dd5279a9-920e-4ed3-9438-56fac9c68f31"/>
-                <column name="AB_KEYWORD_ENTRY_ID" value="5f31c2cd-dce9-4eeb-872c-1a3005ea3210"/>
-                <column name="AB_KEYWORD_ATTRIBUTE_ID" value="ebf7de02-1873-4068-b551-c5348bab4fc6"/>
-                <column name="CHAR_VALUE" value="{cc}"/>
-        </insert>
-        <insert tableName="AB_KEYWORD_ATTRIBUTERELATION">
-                <column name="AB_KEYWORD_ATTRIBUTERELATIONID" value="54b34ffe-178e-410b-a32e-3519ebf5f550"/>
-                <column name="AB_KEYWORD_ENTRY_ID" value="fd9dba2b-92aa-4957-841b-4ec85dc92817"/>
-                <column name="AB_KEYWORD_ATTRIBUTE_ID" value="ebf7de02-1873-4068-b551-c5348bab4fc6"/>
-                <column name="CHAR_VALUE" value="{zc}"/>
-        </insert>
-    </changeSet>
-</databaseChangeLog>
diff --git a/entity/DocumentTemplate_entity/DocumentTemplate_entity.aod b/entity/DocumentTemplate_entity/DocumentTemplate_entity.aod
index af59e8becbcb490ed5a03719311864ac5dff567d..d98456c702c6d1ea45f80b19dfc4dda4d0232055 100644
--- a/entity/DocumentTemplate_entity/DocumentTemplate_entity.aod
+++ b/entity/DocumentTemplate_entity/DocumentTemplate_entity.aod
@@ -14,7 +14,7 @@
         <entityDependency>
           <name>5cc2e566-309c-4b47-84f3-52376e919b9b</name>
           <entityName>Email_entity</entityName>
-          <fieldName>DocumnetTemplates</fieldName>
+          <fieldName>DocumentTemplates</fieldName>
           <isConsumer v="false" />
         </entityDependency>
       </dependencies>
@@ -233,6 +233,12 @@
           <fieldName>DocumentTemplateTexFooter</fieldName>
           <isConsumer v="false" />
         </entityDependency>
+        <entityDependency>
+          <name>57f408e3-aeb7-4006-a20d-287dae1f0922</name>
+          <entityName>Mail_entity</entityName>
+          <fieldName>DocumentTemplates</fieldName>
+          <isConsumer v="false" />
+        </entityDependency>
       </dependencies>
     </entityProvider>
     <entityParameter>
diff --git a/entity/Email_entity/Email_entity.aod b/entity/Email_entity/Email_entity.aod
index d681318619470d0f1dc818a390f69ce2c57dbe85..8a3f7dba803446aae2139a84d61f7ea50b86f3cf 100644
--- a/entity/Email_entity/Email_entity.aod
+++ b/entity/Email_entity/Email_entity.aod
@@ -13,11 +13,11 @@
     <entityField>
       <name>DOCUMENT_TEMPLATE</name>
       <title>Document Template</title>
-      <consumer>DocumnetTemplates</consumer>
+      <consumer>DocumentTemplates</consumer>
       <linkedContext>DocumentTemplate</linkedContext>
     </entityField>
     <entityConsumer>
-      <name>DocumnetTemplates</name>
+      <name>DocumentTemplates</name>
       <dependency>
         <name>dependency</name>
         <entityName>DocumentTemplate_entity</entityName>
diff --git a/entity/Email_entity/recordcontainers/jdito/onInsert.js b/entity/Email_entity/recordcontainers/jdito/onInsert.js
index f838dfce9ecf292a6fa77e741074e5cd60beae8f..857bf055dd9ef564ac8a107327423919f10b0bcc 100644
--- a/entity/Email_entity/recordcontainers/jdito/onInsert.js
+++ b/entity/Email_entity/recordcontainers/jdito/onInsert.js
@@ -2,4 +2,4 @@ import("Employee_lib");
 import("system.vars");
 import("Email_lib");
 
-EmailUtils.openMailTemplate(vars.get("$field.RECIPIENT"), EmployeeUtils.getCurrentContactId(), vars.get("$field.DOCUMENT_TEMPLATE"));
\ No newline at end of file
+EmailUtils.openMailTemplate(vars.get("$field.RECIPIENT"), EmployeeUtils.getCurrentContactId(), vars.get("$field.DOCUMENT_TEMPLATE"), vars.get("$param.ContactId_param"));
\ No newline at end of file
diff --git a/entity/Mail_entity/Mail_entity.aod b/entity/Mail_entity/Mail_entity.aod
new file mode 100644
index 0000000000000000000000000000000000000000..b986ba28239d31687deb82cbac971b52f7c13cd4
--- /dev/null
+++ b/entity/Mail_entity/Mail_entity.aod
@@ -0,0 +1,36 @@
+<?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.6" xsi:schemaLocation="http://www.adito.de/2018/ao/Model adito://models/xsd/entity/1.3.6">
+  <name>Mail_entity</name>
+  <majorModelMode>DISTRIBUTED</majorModelMode>
+  <recordContainer>jdito</recordContainer>
+  <entityFields>
+    <entityProvider>
+      <name>#PROVIDER</name>
+    </entityProvider>
+    <entityField>
+      <name>UID</name>
+    </entityField>
+    <entityField>
+      <name>DOCUMENT_TEMPLATE</name>
+      <consumer>DocumentTemplates</consumer>
+      <linkedContext>DocumentTemplate</linkedContext>
+    </entityField>
+    <entityConsumer>
+      <name>DocumentTemplates</name>
+      <dependency>
+        <name>dependency</name>
+        <entityName>DocumentTemplate_entity</entityName>
+        <fieldName>DocumentTemplateProvider</fieldName>
+      </dependency>
+    </entityConsumer>
+  </entityFields>
+  <recordContainers>
+    <jDitoRecordContainer>
+      <name>jdito</name>
+      <jDitoRecordAlias>Data_alias</jDitoRecordAlias>
+      <recordFields>
+        <element>UID.value</element>
+      </recordFields>
+    </jDitoRecordContainer>
+  </recordContainers>
+</entity>
diff --git a/neonContext/Mail/Mail.aod b/neonContext/Mail/Mail.aod
new file mode 100644
index 0000000000000000000000000000000000000000..6c1b4c798c638d24c8953b7635eea0c4b2fa1dee
--- /dev/null
+++ b/neonContext/Mail/Mail.aod
@@ -0,0 +1,6 @@
+<?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.0" xsi:schemaLocation="http://www.adito.de/2018/ao/Model adito://models/xsd/neonContext/1.1.0">
+  <name>Mail</name>
+  <majorModelMode>DISTRIBUTED</majorModelMode>
+  <entity>Mail_entity</entity>
+</neonContext>
diff --git a/process/Address_lib/process.js b/process/Address_lib/process.js
index 2b4406c8124819a4ec41bf95302450c8de6de8c4..5f543024e9ef8cd4876ac36491278caf4ef55070 100644
--- a/process/Address_lib/process.js
+++ b/process/Address_lib/process.js
@@ -1,473 +1,474 @@
-import("system.swing");
-import("system.text");
-import("system.db");
-import("system.logging");
-import("system.vars");
-import("system.translate");
-import("Attribute_lib");
-import("Sql_lib");
-import("Util_lib");
-
-/*
-* Creates a Address Object
-* 
-* @param {String} pRelationID req relationid for which address should be retrieved
-* @param {String} pAddressID opt addressid for which address should be retrieved
-* @param {boolean} pPerson whether the address is from a person, not an organisation
-*  
-* @return {String} the formatted address
-*/
-
-function AddrObject( pRelationID, pPerson, pAddressID )
-{
-    this.Data = fetchAddressData( [ pRelationID ] , [["", "addressformat", ""]], pAddressID, pPerson );
-    this.fmt = this.Data[0][0][26]; 
-	
-    /*
-    * creates a formatted address
-    * 
-    * @param {boolean} pCountry whether the country should be displayed
-    * @param {String} pFormat a fixed format for the address
-    * 
-    * @return {String} formatted address
-    */
-    this.getFormattedAddress = function( pCountry, pFormat )
-    {	
-        return _formatAddrData( _getAddrData(  this.Data[0][0] ), pFormat, pCountry );
-    }
-}
-
-/*
-* creates address data
-* 
-* @param {String} pCondition req SQL-Where-Condition  
-* @param {String [[]]} pConfig req ( name, functionality, details )
-* @param {String} pSenderID opt UserRelationID
-* @param {String} pAddressID opt addressid
-* 
-* @return {[]}  Daten 
-*/
-function getAddressesData( pCondition, pConfig, pSenderID, pAddressID )
-{ 
-    var returndata = [];
-    var senderconfig = [];
-    var employeeconfig = [];
-    var config = [];
-    for ( var i = 0; i < pConfig.length; i++ )
-    {
-        var type = pConfig[i][1].split(".");
-        switch( type[0] )
-        {
-            case "employee":
-                employeeconfig.push([pConfig[i][0], type[1], pConfig[i][2]]);
-                break;
-            case "sender":
-                senderconfig.push([pConfig[i][0], type[1], pConfig[i][2]]);
-                break;
-            default:
-                config.push(pConfig[i]);
-                break;
-        } 
-    }
-    var data = getAddressData( pCondition, config, pAddressID );
-    if ( pSenderID == undefined )  pSenderID = vars.get("$global.user").relationid;
-    if ( senderconfig.length > 0 ) 
-        var senderdata = getAddressData( [ pSenderID ], senderconfig );
-    if ( employeeconfig.length > 0 ) 
-        var employeedata = getAddressData( [ vars.get("$global.user").relationid ], employeeconfig );
-    if ( data.length > 0 && ( senderconfig.length > 0 || employeeconfig.length > 0  ) )
-    { 
-        var ze = data[0];
-        if ( employeeconfig.length > 0 ) ze = ze.concat( employeedata[0] );
-        if ( senderconfig.length > 0 ) ze = ze.concat( senderdata[0] ); 
-        returndata.push(ze);
-        for ( i = 1; i < data.length; i++ )
-        {
-            ze = data[i];
-            if ( employeeconfig.length > 0 ) ze = ze.concat( employeedata[1] );
-            if ( senderconfig.length > 0 ) ze = ze.concat( senderdata[1] ); 
-            returndata.push(ze);
-        }
-        return returndata;
-    }
-    else return data;
-}
-
-/*
-* creates
-* 
-* @param {String} pCondition req SQL-Where-Condition  
-* @param {String [[]]} pConfig req ( name, functionality, details )
-* @param {String} AddressID opt addressid
-* 
-* @return {[]}  Daten 
-*/
-function getAddressData( pCondition, pConfig, AddressID )
-{ 
-    return setAddressData( fetchAddressData( pCondition, pConfig, AddressID ) );
-}
-
-/*
-* reads data from the database
-* 
-* @param {String} pCondition req SQL-Where-Condition  
-* @param {String [[]]} pConfig req ( name, functionality, details )
-* @param {String} AddressID opt addressid
-* @param {boolean} pPerson opt if private person
-* 
-* @return {[]}  data 
-*/
-function fetchAddressData( pCondition, pConfig, AddressID, pPerson )
-{ 
-    if ( typeof(pCondition) == "object") pCondition = "CONTACT.CONTACTID in ('" + pCondition.join("','") + "')";
-    if ( pConfig.length > 0 )
-    { 
-        var header = [];
-        var fields = [];
-        var output = [];
-        var pos = 0;
-        var posaddrfields = -1;
-        var functionCalls = [];
-        var addrfields = ["case when CONTACT.PERSON_ID is null then 1 else case when " + SqlMaskingUtils.prototype.trim("CONTACT.ORGANISATION_ID") + " = '0' then 2 else 3 end end", //0
-        "ADDRESS.ADDRESS", "ADDRESS.BUILDINGNO", "ADDRESS.ZIP", "ADDRESS.CITY", "ADDRESS.COUNTRY", "ADDRESS.ADDRESSADDITION",  // 1-6
-        "ADDRESS.ADDRIDENTIFIER", "ADDRESS.DISTRICT", "ADDRESS.REGION", "ADDRESS.STATE", "CONTACT.DEPARTMENT", "CONTACT.CONTACTROLE", // 7-12
-        "CONTACT.POSITION", "CONTACT.LETTERSALUTATION", "ORGANISATION.NAME", "PERSON.FIRSTNAME", "PERSON.MIDDLENAME", "PERSON.LASTNAME",  // 13-18
-        "PERSON.SALUTATION", "PERSON.TITLE", "PERSON.TITLESUFFIX", // 19-21
-        "coalesce( CONTACT.LANGUAGE, (select C.LANGUAGE from CONTACT C where C.ORGANISATION_ID = CONTACT.ORGANISATION_ID and PERSON_ID is null))", // 22
-        "''", "''", "''", "(select ADDR_FORMAT from AB_COUNTRYINFO where ISO2 = ADDRESS.COUNTRY)", "ADDRESS.ADDR_TYPE"]; // 23-27
-											
-        for (var i=0; i < pConfig.length; i++ )
-        {
-            switch( pConfig[i][1] )
-            {
-                case "fieldname": // database fields
-                    fields.push( pConfig[i][2] );
-                    output.push([pos++, pConfig[i][1]]);
-                    header.push( pConfig[i][0] );
-                    break;
-                case "function": // adito SQL functions
-                    fields.push( evalScript("Address_lib.fetchAddressData", vars.resolveVariables(pConfig[i][2]), {}, ["Attribute_lib", "Sql_lib", "Keyword_lib", "Person_lib"], true) );
-                    output.push([pos++, pConfig[i][1]]);
-                    header.push( pConfig[i][0] );
-                    break;
-                case "afunction": // adito functions
-                    try
-                    {
-                        fields.push( "'" + evalScript("Address_lib.fetchAddressData", vars.resolveVariables(pConfig[i][2]), {}, ["Attribute_lib", "Sql_lib", "Keyword_lib", "Person_lib"], true).replace(new RegExp("'","g"), "''") + "'" ); 
-                        output.push([pos++, pConfig[i][1]]);
-                        header.push( pConfig[i][0] );
-                    }
-                    catch( err )
-                    {                      
-                        logging.log( err )
-                    }
-                    break;
-                case "select": // Subselects
-                    fields.push( "(" + vars.resolveVariables(pConfig[i][2]) + " )" );
-                    output.push([pos++, pConfig[i][1]]);
-                    header.push( pConfig[i][0] );
-                    break;
-                case "addressformat": // Addressformat
-                    if ( posaddrfields == -1 )
-                    {        
-                        var sortfields = ["ORGANISATION.NAME", "PERSON.LASTNAME"];
-                        fields.push( addrfields.join(", ") );
-                        posaddrfields = pos;
-                        pos += addrfields.length;								
-                    }
-                    output.push([posaddrfields, pConfig[i][1], pConfig[i][2]]);
-                    header.push( pConfig[i][0] );
-                    break;
-                case "resolveIDFunction":
-                    var configJSON = pConfig[i][2];
-                    fields.push( configJSON.rowIDField );
-
-                    functionCalls.push([pos,
-                        configJSON.resolveFunction,
-                        configJSON.imports,
-                        configJSON.localVars]);
-                                    
-                    output.push([pos++, pConfig[i][1]]);
-                    header.push( pConfig[i][0] );                
-                    break;
-            }
-        }
-        if (!pPerson) {           
-            var sqlstr =  "select " + fields.join(",") 
-            + " from CONTACT join ORGANISATION on CONTACT.ORGANISATION_ID = ORGANISATION.ORGANISATIONID "
-            + " left join PERSON on CONTACT.PERSON_ID = PERSON.PERSONID "
-            + " left join ADDRESS on CONTACT.ADDRESS_ID = ";
-        } else {
-            
-            sqlstr = "select " + fields.join(",")
-            + " from CONTACT join PERSON on CONTACT.PERSON_ID = PERSON.PERSONID "
-            + " left join ORGANISATION on CONTACT.ORGANISATION_ID = ORGANISATION.ORGANISATIONID "
-            + " left join ADDRESS on CONTACT.ADDRESS_ID = ";
-        }
-        
-        if ( AddressID != undefined && AddressID != "" )  sqlstr += "'" + AddressID + "'"; 
-        else sqlstr += "ADDRESS.ADDRESSID";
-        if ( pCondition != "" ) sqlstr += " where " + pCondition;
-        var data = db.table(sqlstr + (sortfields != undefined ? " order by " + sortfields.join(", ") : "" ));
-
-        // loop over all returned datasets
-        for( var j = 0; j < data.length; j++)
-        {
-            // loop over all possible resolveFunction entries
-            for( var k = 0; k < functionCalls.length; k++ )
-            {
-                // get the local variables, which should be present in the function call
-                var localVars = functionCalls[k][3];
-                // add the row id value to the localVars Object so the funtion can gather the data for this dataset
-                localVars.rowIDValue = data[j][ functionCalls[k][0] ];
-                // replace the row id with its base64 string
-                data[j][ functionCalls[k][0] ] = evalScript("Address_lib.fetchAddressData.resolveFunction", 
-                    vars.resolveVariables( functionCalls[k][1]+"()" ), 
-                    localVars, 
-                    functionCalls[k][2], // imports
-                    true);
-            }
-        }
-        
-        if ( data.length == 0 ) 
-        {   
-            logging.log("Address_lib: " +  pCondition, logging.ERROR);
-            data = [[]];
-            for ( i = 0; i < addrfields.length + fields.length; i++ )   data[0].push("Err.");
-        }
-        data = [ data, output, header, addrfields ];
-    }
-    return data;
-}
-
-/*
-* reads data from the database
-* 
-* @param {String [[]]} pData req array of data
-* 
-* @return {String [[]]}  data 
-*/
-function setAddressData( pData )
-{ 
-    var sqlresult = pData[0];
-    var data = [];
-    if ( sqlresult.length > 0 )
-    {
-        var output = pData[1]; 
-        var header = pData[2];
-        var addrfields = pData[3];
-        data.push( header );
-        for ( var i = 0; i < sqlresult.length; i++ )
-        {		
-            var addrdata = [];
-            var row = [];	
-            for ( var z = 0; z < header.length; z++ )
-            {
-                switch( output[z][1] )
-                {
-                    case "fieldname":
-                    case "function":
-                    case "afunction":
-                    case "resolveIDFunction":
-                    case "select":
-                        row[z] = sqlresult[i][output[z][0]];
-                        break;
-                    case "addressformat":
-                        if (addrdata.length == 0) addrdata = _getAddrData( sqlresult[i].slice(output[z][0], output[z][0] + addrfields.length) );
-                        row[z] = _formatAddrData( addrdata, output[z][2] );
-                        break;
-                }
-            }
-            data.push( row );
-        }
-    }
-    return data;
-}
-/*
-*
-* returns formatted address data
-*
-* @param {String [[]]} pData req data 
-*
-* @return {String [[]]} formatted data
-*/
-function _getAddrData( pData )
-{
-    var lettersalutation = pData[14];
-    var salutation = pData[19];
-    var sformat = "";
-    switch( Number(pData[0]) )
-    {
-        case 1:
-            if ( lettersalutation == "" ) 
-            {
-                sformat = _getSalutation( pData[22] );
-                if ( sformat != undefined && sformat[1] != "" ) lettersalutation = _formatAddrData( pData,  sformat[1] );
-                else lettersalutation = "Sehr geehrte Damen und Herren";
-            }
-            break;
-        case 2:
-            // private -> orgname deleted
-            pData[15] = "";
-        case 3:
-            sformat = _getSalutation( pData[22] + pData[19] + pData[20] );
-            //no language defined
-            if ( sformat == undefined )  sformat = _getSalutation( pData[19] + pData[20] );
-            // no language specific entry in salutation
-            if ( sformat == undefined || sformat[0] == "" || sformat[1] == "" )	sformat = ["{sa} {ti} {fn} {la}", "{sa} {ti} {ln}"];
-            salutation = _formatAddrData( pData,  sformat[0] );
-            // lettersalutation if none existent yet
-            if( lettersalutation == "" ) lettersalutation = _formatAddrData( pData, sformat[1] );
-    }
-
-    pData[23] = salutation;
-    pData[24] = lettersalutation;
-    pData[25] = _getCountryName(pData[5]);
-    return pData;
-}
-
-/*
-* returns a formatted salutation
-* 
-* @param {String} pSalutCode req salutation code
-* 
-* @return {String} translated salutation
-*/
-function _getSalutation( pSalutCode )
-{
-    var salut = new Object();
-    if (vars.exists("$global.Salutation")) {
-        salut = vars.get("$global.Salutation");
-    }
-    else
-    {	
-        var list = db.table("select LANGUAGE, SALUTATION, TITLE, HEADLINE, LETTERSALUTATION from SALUTATION" );
-        for ( var i = 0; i < list.length; i++ )	
-        {   
-            salut[list[i][0] + list[i][1] + list[i][2]] = [list[i][3], list[i][4]];
-            salut[list[i][1] + list[i][2]] = [list[i][3], list[i][4]];
-        }
-        vars.set("$global.Salutation", salut);
-    }
-    return salut[pSalutCode];
-}
-
-/*
-* returns country names 
-*
-* @param {String} pCountryCode req countrycode
-*
-* @return {String} translated countryname
-*/
-function _getCountryName(pCountryCode)
-{
-    var countryname = new Object();
-    if ( vars.exists("$global.CountryName")) countryname = vars.get("$global.CountryName");
-    else
-    {	
-        var list = db.table("select ISO2, NAME_NATIVE from AB_COUNTRYINFO" );
-        for (var i=0; i < list.length; i++ )	countryname[list[i][0]] = translate.text(list[i][1]);
-        vars.set("$global.CountryName", countryname);
-    }
-    return countryname[pCountryCode];
-}
-
-/*
-* returns a formatted address
-*
-* @param {String [[]]} pAddrData req Address data 
-* @param {String} pFormat opt given format
-* @param {boolean} pCountry if the country should be displayed
-*
-* @return {String} formatted address
-*/
-function _formatAddrData( pAddrData, pFormat, pCountry )
-{
-    var placeholerInfo = {
-        "street":       {dataPosition: 1},
-        "buildingno":   {dataPosition: 2},
-        "zip":          {dataPosition: 3},
-        "city":         {dataPosition: 4},
-        "district":     {dataPosition: 8},
-        "region":       {dataPosition: 9},
-        "state":        {dataPosition: 10},
-        "firstname":    {dataPosition: 16},
-        "middlename":   {dataPosition: 17},
-        "lastname":     {dataPosition: 18},
-        "saluation":    {dataPosition: 19},
-        "title":        {dataPosition: 20},
-        "suffix":       {dataPosition: 21},
-        "country":      {dataPosition: 25},
-        "organisation name":    {dataPosition: 15},
-        "salutation_name":      {dataPosition: 23},
-        "letter salutation":    {dataPosition: 24}
-    };
-
-    var format = pFormat || pAddrData[26];       
-    format = _mapFormatPlaceholderTitles(format, pAddrData, pCountry);
-    
-    var res = format;    
-    for (var placeholder in placeholerInfo)
-    {
-        var currentAddrData = pAddrData[placeholerInfo[placeholder].dataPosition];
-        if (currentAddrData != undefined)
-        {
-            res = res.replace(new RegExp("{" + placeholder + "}", "g"), currentAddrData);
-            res = res.replace(new RegExp("{" + placeholder.toUpperCase() + "}", "g"), currentAddrData.toUpperCase());
-        }
-    }
-
-    res = res.replace(/^\n/, "");  // CR am Anfang entfernen;
-    res = res.replace(/  /g, " "); // doppelte leerzeichen entfernen
-    res = res.replace(/\\n/ig, "\n");	// newline marker ersetzen
-    res = res.replace(/ *\n */g, "\n");// leerzeichen am ende und Anfang entfernen
-    res = res.replace(/\s(?=\s)/g, "");	// leerzeilen rauswerfen
-    return res;
-}
-
-/*
-* returns the new format 
-*
-* @param {String [[]]} pAddrData req Daten 
-* @param {String} pFormat req the format string
-* @param {boolean} pCountry if the country should be displayed
-*
-* @return {String} new formate
-* 
-N – Name - salutation
-O – Organisation - orgname
-A – Street Address Line(s) - address + buildingno
-D – Dependent locality - district / region
-C – City or Locality - city
-S – Administrative area - state
-Z – Zip or postal code - zip
-X – Sorting code - not available
-*/
-function _mapFormatPlaceholderTitles(pFormat, pAddrData, pCountry) 
-{
-    //gstatic-paceholders
-    pFormat = pFormat.replace(new RegExp("%N", "g"), "{salutation_name}");
-    pFormat = pFormat.replace(new RegExp("%A", "g"), "{address_street buildingno}");  
-    pFormat = pFormat.replace(new RegExp("%C", "g"), "{city}");
-    pFormat = pFormat.replace(new RegExp("%S", "g"), "{state}");
-    pFormat = pFormat.replace(new RegExp("%Z", "g"), "{zip}");
-    pFormat = pFormat.replace(new RegExp("%O", "g"), "{organisation_name}");
-    pFormat = pFormat.replace(new RegExp("%X", "g"), "");   
-    pFormat = pFormat.replace(new RegExp("%n", "g"), "\n");
-    
-    //shortform adito-placeholders
-    pFormat = pFormat.replace(new RegExp("{fn}", "g"), "{firstname}");
-    pFormat = pFormat.replace(new RegExp("{ln}", "g"), "{lpFormattname}");
-    pFormat = pFormat.replace(new RegExp("{ti}", "g"), "{title}");
-    pFormat = pFormat.replace(new RegExp("{sa}", "g"), "{salutation}");
-    
-    if (pAddrData[8] == pAddrData[9])
-        pFormat = pFormat.replace(new RegExp("%D", "g"), "{district}");
-    else
-        pFormat = pFormat.replace(new RegExp("%D", "g"), "{district} \n {region}");   
-    
-    if(pCountry == undefined || pCountry == null || pCountry == true)
-        pFormat = pFormat + "\n {country}";
-    
-    return pFormat;
+import("system.swing");
+import("system.text");
+import("system.db");
+import("system.logging");
+import("system.vars");
+import("system.translate");
+import("Attribute_lib");
+import("Sql_lib");
+import("Util_lib");
+import("DocumentTemplate_lib");
+
+/*
+* Creates a Address Object
+* 
+* @param {String} pRelationID req relationid for which address should be retrieved
+* @param {String} pAddressID opt addressid for which address should be retrieved
+* @param {boolean} pPerson whether the address is from a person, not an organisation
+*  
+* @return {String} the formatted address
+*/
+
+function AddrObject( pRelationID, pPerson, pAddressID )
+{
+    this.Data = fetchAddressData( [ pRelationID ] , [["", "addressformat", ""]], pAddressID, pPerson );
+    this.fmt = this.Data[0][0][26]; 
+	
+    /*
+    * creates a formatted address
+    * 
+    * @param {boolean} pCountry whether the country should be displayed
+    * @param {String} pFormat a fixed format for the address
+    * 
+    * @return {String} formatted address
+    */
+    this.getFormattedAddress = function( pCountry, pFormat )
+    {	
+        return _formatAddrData( _getAddrData(  this.Data[0][0] ), pFormat, pCountry );
+    }
+}
+
+/*
+* creates address data
+* 
+* @param {String} pCondition req SQL-Where-Condition  
+* @param {Object []} pConfig req ( name, functionality, details )
+* @param {String} pSenderID opt UserRelationID
+* @param {String} pAddressID opt addressid
+* 
+* @return {[]}  Daten 
+*/
+function getAddressesData( pCondition, pConfig, pSenderID, pAddressID )
+{ 
+    var returndata = [];
+    var senderconfig = [];
+    var employeeconfig = [];
+    var config = [];
+    for (let i = 0; i < pConfig.length; i++)
+    {
+        switch (pConfig[i].target)
+        {
+            case PlaceholderUtils.targets.EMPLOYEE:
+                employeeconfig.push(pConfig[i]);
+                break;
+            case PlaceholderUtils.targets.SENDER:
+                senderconfig.push(pConfig[i]);
+                break;
+            case PlaceholderUtils.targets.RECIPIENT:
+            default:
+                config.push(pConfig[i]);
+                break;
+        } 
+    }
+    var data = getAddressData(pCondition, config, pAddressID);
+    if (pSenderID == undefined)  pSenderID = vars.get("$global.user").relationid;
+    if (senderconfig.length > 0) 
+        var senderdata = getAddressData([pSenderID], senderconfig);
+    if (employeeconfig.length > 0) 
+        var employeedata = getAddressData([vars.get("$global.user").relationid], employeeconfig);
+    if (data.length > 0 && (senderconfig.length > 0 || employeeconfig.length > 0))
+    { 
+        var ze = data[0];
+        if (employeeconfig.length > 0) ze = ze.concat(employeedata[0]);
+        if (senderconfig.length > 0) ze = ze.concat(senderdata[0]); 
+        returndata.push(ze);
+        for (let i = 1; i < data.length; i++)
+        {
+            ze = data[i];
+            if (employeeconfig.length > 0) ze = ze.concat(employeedata[1]);
+            if (senderconfig.length > 0) ze = ze.concat(senderdata[1]); 
+            returndata.push(ze);
+        }
+        return returndata;
+    }
+    else return data;
+}
+
+/*
+* creates
+* 
+* @param {String} pCondition req SQL-Where-Condition  
+* @param {Placeholder[]} pConfig req array of placeholders
+* @param {String} AddressID opt addressid
+* 
+* @return {[]}  Daten 
+*/
+function getAddressData( pCondition, pConfig, AddressID )
+{ 
+    return setAddressData( fetchAddressData( pCondition, pConfig, AddressID ) );
+}
+
+/*
+* reads data from the database
+* 
+* @param {String} pCondition req SQL-Where-Condition  
+* @param {Placeholder[]} pConfig req array of placeholders
+* @param {String} AddressID opt addressid
+* @param {boolean} pPerson opt if private person
+* 
+* @return {Array} 2d-Array, structure: [[ data, output, header, addrfields ]]
+*/
+function fetchAddressData( pCondition, pConfig, AddressID, pPerson )
+{ 
+    if ( typeof(pCondition) == "object") pCondition = "CONTACT.CONTACTID in ('" + pCondition.join("','") + "')";
+    if ( pConfig.length > 0 )
+    { 
+        var header = [];
+        var fields = [];
+        var output = [];
+        var pos = 0;
+        var posaddrfields = -1;
+        var functionCalls = [];
+        var addrfields = ["case when CONTACT.PERSON_ID is null then 1 else case when " + SqlMaskingUtils.prototype.trim("CONTACT.ORGANISATION_ID") + " = '0' then 2 else 3 end end", //0
+        "ADDRESS.ADDRESS", "ADDRESS.BUILDINGNO", "ADDRESS.ZIP", "ADDRESS.CITY", "ADDRESS.COUNTRY", "ADDRESS.ADDRESSADDITION",  // 1-6
+        "ADDRESS.ADDRIDENTIFIER", "ADDRESS.DISTRICT", "ADDRESS.REGION", "ADDRESS.STATE", "CONTACT.DEPARTMENT", "CONTACT.CONTACTROLE", // 7-12
+        "CONTACT.POSITION", "CONTACT.LETTERSALUTATION", "ORGANISATION.NAME", "PERSON.FIRSTNAME", "PERSON.MIDDLENAME", "PERSON.LASTNAME",  // 13-18
+        "PERSON.SALUTATION", "PERSON.TITLE", "PERSON.TITLESUFFIX", // 19-21
+        "coalesce( CONTACT.LANGUAGE, (select C.LANGUAGE from CONTACT C where C.ORGANISATION_ID = CONTACT.ORGANISATION_ID and PERSON_ID is null))", // 22
+        "''", "''", "''", "(select ADDR_FORMAT from AB_COUNTRYINFO where ISO2 = ADDRESS.COUNTRY)", "ADDRESS.ADDR_TYPE"]; // 23-27
+											
+        for (let i=0; i < pConfig.length; i++ )
+        {
+            switch( pConfig[i].type )
+            {
+                case PlaceholderUtils.types.SQLPART: //sql part
+                    fields.push( pConfig[i].valueDefinition ); //TODO: maybe do vars.resolveVariables
+                    output.push([pos++, pConfig[i].type]);
+                    header.push( pConfig[i].placeholderName );
+                    break;
+                case PlaceholderUtils.types.SQLPARTFUNCTION: // adito SQL functions
+                    fields.push(pConfig[i].valueDefinition.call());
+                    output.push([pos++, pConfig[i].type]);
+                    header.push( pConfig[i].placeholderName );
+                    break;
+                case "afunction": // adito functions
+                    try
+                    {
+                        fields.push( "'" + evalScript("Address_lib.fetchAddressData", vars.resolveVariables(pConfig[i].valueDefinition), {}, ["Attribute_lib", "Sql_lib", "Keyword_lib", "Person_lib"], true).replace(new RegExp("'","g"), "''") + "'" ); 
+                        output.push([pos++, pConfig[i].type]);
+                        header.push( pConfig[i].placeholderName );
+                    }
+                    catch( err )
+                    {                      
+                        logging.log( err )
+                    }
+                    break;
+                case "select": // Subselects
+                    fields.push( "(" + vars.resolveVariables(pConfig[i].valueDefinition) + " )" );
+                    output.push([pos++, pConfig[i].type]);
+                    header.push( pConfig[i].placeholderName );
+                    break;
+                case PlaceholderUtils.types.ADDRESSFORMAT:
+                    if ( posaddrfields == -1 )
+                    {        
+                        var sortfields = ["ORGANISATION.NAME", "PERSON.LASTNAME"];
+                        fields.push( addrfields.join(", ") );
+                        posaddrfields = pos;
+                        pos += addrfields.length;								
+                    }
+                    output.push([posaddrfields, pConfig[i].type, pConfig[i].valueDefinition]);
+                    header.push( pConfig[i].placeholderName );
+                    break;
+                case "resolveIDFunction":
+                    var configJSON = pConfig[i].valueDefinition;
+                    fields.push( configJSON.rowIDField );
+
+                    functionCalls.push([pos,
+                        configJSON.resolveFunction,
+                        configJSON.imports,
+                        configJSON.localVars]);
+                                    
+                    output.push([pos++, pConfig[i].type]);
+                    header.push( pConfig[i].placeholderName );                
+                    break;
+            }
+        }
+        if (!pPerson) {           
+            var sqlstr =  "select " + fields.join(",") 
+            + " from CONTACT join ORGANISATION on CONTACT.ORGANISATION_ID = ORGANISATION.ORGANISATIONID "
+            + " left join PERSON on CONTACT.PERSON_ID = PERSON.PERSONID "
+            + " left join ADDRESS on CONTACT.ADDRESS_ID = ";
+        } else {
+            
+            sqlstr = "select " + fields.join(",")
+            + " from CONTACT join PERSON on CONTACT.PERSON_ID = PERSON.PERSONID "
+            + " left join ORGANISATION on CONTACT.ORGANISATION_ID = ORGANISATION.ORGANISATIONID "
+            + " left join ADDRESS on CONTACT.ADDRESS_ID = ";
+        }
+        
+        if ( AddressID != undefined && AddressID != "" )  sqlstr += "'" + AddressID + "'"; 
+        else sqlstr += "ADDRESS.ADDRESSID";
+        if ( pCondition != "" ) sqlstr += " where " + pCondition;
+        var data = db.table(sqlstr + (sortfields != undefined ? " order by " + sortfields.join(", ") : "" ));
+
+        // loop over all returned datasets
+        for(let j = 0; j < data.length; j++)
+        {
+            // loop over all possible resolveFunction entries
+            for(let k = 0; k < functionCalls.length; k++ )
+            {
+                // get the local variables, which should be present in the function call
+                var localVars = functionCalls[k][3];
+                // add the row id value to the localVars Object so the funtion can gather the data for this dataset
+                localVars.rowIDValue = data[j][ functionCalls[k][0] ];
+                // replace the row id with its base64 string
+                data[j][ functionCalls[k][0] ] = evalScript("Address_lib.fetchAddressData.resolveFunction", 
+                    vars.resolveVariables( functionCalls[k][1]+"()" ), 
+                    localVars, 
+                    functionCalls[k][2], // imports
+                    true);
+            }
+        }
+        
+        if ( data.length == 0 ) 
+        {   
+            logging.log("Address_lib: " +  pCondition, logging.ERROR);
+            data = [[]];
+            for (let i = 0; i < addrfields.length + fields.length; i++ )   data[0].push("Err.");
+        }
+        data = [ data, output, header, addrfields ];
+    }
+    return data;
+}
+
+/*
+* reads data from the database
+* 
+* @param {String [[]]} pData req array of data
+* 
+* @return {String [[]]}  data 
+*/
+function setAddressData( pData )
+{ 
+    var sqlresult = pData[0];
+    var data = [];
+    if ( sqlresult.length > 0 )
+    {
+        var output = pData[1]; 
+        var header = pData[2];
+        var addrfields = pData[3];
+        data.push( header );
+        for ( var i = 0; i < sqlresult.length; i++ )
+        {		
+            var addrdata = [];
+            var row = [];	
+            for ( var z = 0; z < header.length; z++ )
+            {
+                switch( output[z][1] )
+                {
+                    case PlaceholderUtils.types.SQLPART:
+                    case PlaceholderUtils.types.SQLPARTFUNCTION:
+                    case "afunction":
+                    case "resolveIDFunction":
+                    case "select":
+                        row[z] = sqlresult[i][output[z][0]];
+                        break;
+                    case PlaceholderUtils.types.ADDRESSFORMAT:
+                        if (addrdata.length == 0) addrdata = _getAddrData( sqlresult[i].slice(output[z][0], output[z][0] + addrfields.length) );
+                        row[z] = _formatAddrData( addrdata, output[z][2] );
+                        break;
+                }
+            }
+            data.push( row );
+        }
+    }
+    return data;
+}
+/*
+*
+* returns formatted address data
+*
+* @param {String [[]]} pData req data 
+*
+* @return {String [[]]} formatted data
+*/
+function _getAddrData( pData )
+{
+    var lettersalutation = pData[14];
+    var salutation = pData[19];
+    var sformat = "";
+    switch( Number(pData[0]) )
+    {
+        case 1:
+            if ( lettersalutation == "" ) 
+            {
+                sformat = _getSalutation( pData[22] );
+                if ( sformat != undefined && sformat[1] != "" ) lettersalutation = _formatAddrData( pData,  sformat[1] );
+                else lettersalutation = "Sehr geehrte Damen und Herren";
+            }
+            break;
+        case 2:
+            // private -> orgname deleted
+            pData[15] = "";
+        case 3:
+            sformat = _getSalutation( pData[22] + pData[19] + pData[20] );
+            //no language defined
+            if ( sformat == undefined )  sformat = _getSalutation( pData[19] + pData[20] );
+            // no language specific entry in salutation
+            if ( sformat == undefined || sformat[0] == "" || sformat[1] == "" )	sformat = ["{sa} {ti} {fn} {la}", "{sa} {ti} {ln}"];
+            salutation = _formatAddrData( pData,  sformat[0] );
+            // lettersalutation if none existent yet
+            if( lettersalutation == "" ) lettersalutation = _formatAddrData( pData, sformat[1] );
+    }
+
+    pData[23] = salutation;
+    pData[24] = lettersalutation;
+    pData[25] = _getCountryName(pData[5]);
+    return pData;
+}
+
+/*
+* returns a formatted salutation
+* 
+* @param {String} pSalutCode req salutation code
+* 
+* @return {String} translated salutation
+*/
+function _getSalutation( pSalutCode )
+{
+    var salut = new Object();
+    if (vars.exists("$global.Salutation")) {
+        salut = vars.get("$global.Salutation");
+    }
+    else
+    {	
+        var list = db.table("select LANGUAGE, SALUTATION, TITLE, HEADLINE, LETTERSALUTATION from SALUTATION" );
+        for ( var i = 0; i < list.length; i++ )	
+        {   
+            salut[list[i][0] + list[i][1] + list[i][2]] = [list[i][3], list[i][4]];
+            salut[list[i][1] + list[i][2]] = [list[i][3], list[i][4]];
+        }
+        vars.set("$global.Salutation", salut);
+    }
+    return salut[pSalutCode];
+}
+
+/*
+* returns country names 
+*
+* @param {String} pCountryCode req countrycode
+*
+* @return {String} translated countryname
+*/
+function _getCountryName(pCountryCode)
+{
+    var countryname = new Object();
+    if ( vars.exists("$global.CountryName")) countryname = vars.get("$global.CountryName");
+    else
+    {	
+        var list = db.table("select ISO2, NAME_NATIVE from AB_COUNTRYINFO" );
+        for (var i=0; i < list.length; i++ )	countryname[list[i][0]] = translate.text(list[i][1]);
+        vars.set("$global.CountryName", countryname);
+    }
+    return countryname[pCountryCode];
+}
+
+/*
+* returns a formatted address
+*
+* @param {String [[]]} pAddrData req Address data 
+* @param {String} pFormat opt given format
+* @param {boolean} pCountry if the country should be displayed
+*
+* @return {String} formatted address
+*/
+function _formatAddrData( pAddrData, pFormat, pCountry )
+{
+    var placeholerInfo = {
+        "street":       {dataPosition: 1},
+        "buildingno":   {dataPosition: 2},
+        "zip":          {dataPosition: 3},
+        "city":         {dataPosition: 4},
+        "district":     {dataPosition: 8},
+        "region":       {dataPosition: 9},
+        "state":        {dataPosition: 10},
+        "firstname":    {dataPosition: 16},
+        "middlename":   {dataPosition: 17},
+        "lastname":     {dataPosition: 18},
+        "saluation":    {dataPosition: 19},
+        "title":        {dataPosition: 20},
+        "suffix":       {dataPosition: 21},
+        "country":      {dataPosition: 25},
+        "organisation_name":    {dataPosition: 15},
+        "salutation_name":      {dataPosition: 23},
+        "letter_salutation":    {dataPosition: 24}
+    };
+
+    var format = pFormat || pAddrData[26];       
+    format = _mapFormatPlaceholderTitles(format, pAddrData, pCountry);
+    
+    var res = format;    
+    for (var placeholder in placeholerInfo)
+    {
+        var currentAddrData = pAddrData[placeholerInfo[placeholder].dataPosition];
+        if (currentAddrData != undefined)
+        {
+            res = res.replace(new RegExp("{" + placeholder + "}", "g"), currentAddrData);
+            res = res.replace(new RegExp("{" + placeholder.toUpperCase() + "}", "g"), currentAddrData.toUpperCase());
+        }
+    }
+
+    res = res.replace(/^\n/, "");  // CR am Anfang entfernen;
+    res = res.replace(/  /g, " "); // doppelte leerzeichen entfernen
+    res = res.replace(/\\n/ig, "\n");	// newline marker ersetzen
+    res = res.replace(/ *\n */g, "\n");// leerzeichen am ende und Anfang entfernen
+    res = res.replace(/\s(?=\s)/g, "");	// leerzeilen rauswerfen
+    return res;
+}
+
+/*
+* returns the new format 
+*
+* @param {String [[]]} pAddrData req Daten 
+* @param {String} pFormat req the format string
+* @param {boolean} pCountry if the country should be displayed
+*
+* @return {String} new formate
+* 
+N – Name - salutation
+O – Organisation - orgname
+A – Street Address Line(s) - address + buildingno
+D – Dependent locality - district / region
+C – City or Locality - city
+S – Administrative area - state
+Z – Zip or postal code - zip
+X – Sorting code - not available
+*/
+function _mapFormatPlaceholderTitles(pFormat, pAddrData, pCountry) 
+{
+    //gstatic-paceholders
+    pFormat = pFormat.replace(new RegExp("%N", "g"), "{salutation_name}");
+    pFormat = pFormat.replace(new RegExp("%A", "g"), "{street} {buildingno}");  
+    pFormat = pFormat.replace(new RegExp("%C", "g"), "{city}");
+    pFormat = pFormat.replace(new RegExp("%S", "g"), "{state}");
+    pFormat = pFormat.replace(new RegExp("%Z", "g"), "{zip}");
+    pFormat = pFormat.replace(new RegExp("%O", "g"), "{organisation_name}");
+    pFormat = pFormat.replace(new RegExp("%X", "g"), "");   
+    pFormat = pFormat.replace(new RegExp("%n", "g"), "\n");
+    
+    //shortform adito-placeholders
+    pFormat = pFormat.replace(new RegExp("{fn}", "g"), "{firstname}");
+    pFormat = pFormat.replace(new RegExp("{ln}", "g"), "{lastname}");
+    pFormat = pFormat.replace(new RegExp("{ti}", "g"), "{title}");
+    pFormat = pFormat.replace(new RegExp("{sa}", "g"), "{salutation}");
+    
+    if (pAddrData[8] == pAddrData[9])
+        pFormat = pFormat.replace(new RegExp("%D", "g"), "{district}");
+    else
+        pFormat = pFormat.replace(new RegExp("%D", "g"), "{district} \n {region}");   
+    
+    if(pCountry == undefined || pCountry == null || pCountry == true)
+        pFormat = pFormat + "\n {country}";
+    
+    return pFormat;
 }
\ No newline at end of file
diff --git a/process/DataCaching_lib/process.js b/process/DataCaching_lib/process.js
index 8410bd42ceceb6760b8bc9645dfe445f23467a8a..2aee0dc32b167e0c11e2765cb44026dd6bbb96bd 100644
--- a/process/DataCaching_lib/process.js
+++ b/process/DataCaching_lib/process.js
@@ -66,8 +66,8 @@ CachedData.prototype.load = function(pDataCallbackFunction)
     //currently it's not possible to cache the data within the serer-context, so instead the Data-function is called everytime 
     if (this.runningOnServer)
         cachingEnabled = false;
-    else if (JSON.parse(project.getInstanceConfigValue("custom.dataCaching.client.forceDisable")) == true)
-        cachingEnabled = false;
+//    else if (JSON.parse(project.getInstanceConfigValue("custom.dataCaching.client.forceDisable")) == true)
+//        cachingEnabled = false;
 
     if (!cachingEnabled)
         return pDataCallbackFunction.call(this, this.keepPerLanguage, this.locale);
diff --git a/process/DocumentTemplate_lib/process.js b/process/DocumentTemplate_lib/process.js
index 1d448d37692a606659003910ac72b3c8174d5372..209f427347d35b9dc4a7f6185953ef17cf8103fc 100644
--- a/process/DocumentTemplate_lib/process.js
+++ b/process/DocumentTemplate_lib/process.js
@@ -1,3 +1,4 @@
+import("Employee_lib");
 import("KeywordRegistry_basic");
 import("Document_lib");
 import("KeywordData_lib");
@@ -6,16 +7,12 @@ import("Address_lib");
 import("system.process");
 import("system.vars");
 import("system.db");
-import("system.swing");
 import("system.util");
 import("system.pack");
 import("system.fileIO");
 import("system.translate");
-import("system.question");
 import("system.datetime");
-import("system.logging");
 import("system.text");
-import("system.eMath");
 import("system.mail");
 import("Keyword_lib");
 
@@ -63,15 +60,15 @@ DocumentTemplate.loadTemplate = function (pTemplateId)
 
 DocumentTemplate.prototype.toString = function ()
 {
-    return this.templateText;
+    return this.content;
 }
 
 /**
- * returns the template text with replaced placeholders
+ * returns the template content with replaced placeholders
  * 
- * @param {Object} pReplacements map, the structure is {placeholder : value} (the placeholder should have the prefix @@)
+ * @param {Object} pReplacements map, the structure is {placeholder : value}
  */
-DocumentTemplate.prototype.getReplacedText = function (pReplacements)
+DocumentTemplate.prototype.getReplacedContent = function (pReplacements)
 {
     switch (this.type)
     {
@@ -83,20 +80,24 @@ DocumentTemplate.prototype.getReplacedText = function (pReplacements)
         case DocumentTemplate.types.EML:
             return this._getReplacedEML(pReplacements);
         case DocumentTemplate.types.ODT:
-//            return this._getReplacedODT(pReplacements);
+            return this._getReplacedODT(pReplacements);
         case DocumentTemplate.types.DOCX:
-//            return this._getReplacedDOCX(pReplacements);
+            return this._getReplacedDOCX(pReplacements);
         default:
             return null;
     }
 }
 
-DocumentTemplate.prototype.getReplacedTextByContactId = function (pContactId)
+DocumentTemplate.prototype.getReplacedContentByContactId = function (pContactId)
 {
-    var config = DocumentTemplate._getPlaceholderConfig();
-    var replacements = getAddressesData([pContactId], config, pSenderID, pAddressID);
-    //TODO: build the replacement map here
-    return this.getReplacedText(replacements);
+    var config = PlaceholderUtils.getPlaceholders();
+    var addressData = getAddressesData([pContactId], config, EmployeeUtils.getCurrentContactId()); //TODO: add sender selection
+    var replacements = {};
+    for (let i = 0, l = addressData[0].length; i < l; i++)
+    {
+        replacements[addressData[0][i]] = addressData[1][i];
+    }
+    return this.getReplacedContent(replacements);
 }
 
 DocumentTemplate.prototype._getReplacedEML = function (pReplacements)
@@ -105,92 +106,85 @@ DocumentTemplate.prototype._getReplacedEML = function (pReplacements)
     
 }
 
+
 /*
  * replaces a given Odt-File on the server and returns the replaced base64-file
- * 
- * @deprecated needs refactoring
  *
- * @param {String} pTemplateData req base64-encoded input file with placeholders
- * @param {String} pTemplateName req name of the input file
  * @param {String|String[]} pAddrDataCondition req a SQL-Condition or an Array of Relation-Ids for reducing the default-placeholders
  * @param {String} pAddressID opt if you want to use the standard address you can pass undefined, otherwise you need to specify a Address-Id
- * @param {Object} pAdditionalData opt additional placeholders with data (e.g. offercode); for format check the example
- * @param {Object[]} pTableData opt data for odt-tables; for format check the example
  *
  * @return {String} base64-encoded replaced file
- *
- * @example
- * //examples for additionalData:
- * //2 Methods: 1) SQL 2) Array - no matter what you're using: the first column has to be the RELATIONID-Value
- * //1) you can use a SQL-Statement for passing Data
- * additionalData = {
- * 	 Fields: ["RELID", "myPlaceholder 1","myPlaceholder N"]
- * 	,SQLStr: "select RELATION_ID, ADDR, MEDIUM_ID from COMM"
- * };
- * //if you pass the (optional) property "ID" a condition with ID in <<relationids>> is added to the SQL-Data-query:
- * additionalData.ID = "COMM.RELATION_ID";
- *
- *
- * //2) another option is to pass data as a 2D-Array like this
- * additionalData = {
- * 	 Fields: ["RELID", "myPlaceholder 1","myPlaceholder N"]
- * 	,Data: [
- * 		 ["myRelIdValue 1", "my Value 1", "my Value N"]
- * 		,["myRelIdValue M", "my other Value 1", "my other Value N"]
- * 	]
- * };
- *
- *
- * //examples for tableData:
- * //you can define multible data-sources
- * tableData =  [];
- * //2 Methods: 1) SQL 2) Array - no matter what you're using: the first column has to be the RELATIONID-Value
- * //the placeholder can be accessed by <<Table>>.<<Field>>
- * //1) if you use a SQL-Statement for defining the data you've got some optional properties
- * var sqlSource = {
- * 	 Table: "ADDR"
- * 	,Fields: ["RELID", "Type", "Strasse", "PLZ", "Ort", "Staat", "Land"],
- * 	,SQLStr: "select RELATION_ID, ADDR_TYPE, " + concat(["ADDRESS", "BUILDINGNO"]) + ", ZIP, CITY, STATE, NAME_DE from ADDRESS join COUNTRYINFO on COUNTRY = ISO2"
- * };
- * //if you pass the (optional) property "ID" a condition with ID in <<relationids>> is added to the SQL-Data-query:
- * sqlSource.ID = "ADDRESS.RELATION_ID";
- *
- * //if you pass the (optional) property "SQLOrder" an order-by clause is added
- * sqlSource.SQLOrder = "ADDRESS.ADDR_TYPE asc, ADDRESS.ZIP desc";
- *
- * tableData.push(sqlSource);
- *
- * //2) pass data as an Array like this
- * var arraySource = {
- * 	 Table: "myTablePrefix"
- * 	,Fields: ["RELID", "myPlaceholder 1","myPlaceholder N"]
- * 	,Data: [
- * 		 ["myRelIdValue 1", "my Value 1", "my Value N"]
- * 		,["myRelIdValue M", "my other Value 1", "my other Value N"]
- * 	]
- * }
- * tableData.push(arraySource);
- *
  */
-DocumentTemplateUtils.getReplacedODT = function (pTemplateData, pTemplateName, pAddrDataCondition, pAddressID, pAdditionalData, pTableData)
+DocumentTemplate.prototype._getReplacedODT = function (pAddrDataCondition, pAddressID)
 {
-    //save the file on the server, replace it on the server file system, then load it because that works for neon and swing
+    //save the file on the server so it can be unzipped via pack.getFromZip
     var serverFilePath = vars.get("$sys.servertemp") + "/clientid_" + vars.get("$sys.clientid")
-        + "/" + util.getNewUUID() + "/" + pTemplateName.replace(/\\/g, "/");
+        + "/" + util.getNewUUID();
 
-    fileIO.storeData(serverFilePath, pTemplateData, util.DATA_BINARY, false);
-    if ( ! replaceODTFile(pAddrDataCondition, serverFilePath, pAddressID, pAdditionalData, pTableData ))
+    fileIO.storeData(serverFilePath, this.content, util.DATA_BINARY, false);
+    if (!_replaceODTFile(pAddrDataCondition, serverFilePath, pAddressID))
         return null;
 
     var replacedFileData = fileIO.getData(serverFilePath, util.DATA_BINARY);
     fileIO.remove(serverFilePath);
 
     return replacedFileData;
-}
-
-DocumentTemplate.prototype._getReplacedODT = function (pReplacements)
-{
     
+    /*
+    * ersetzt die Platzhalter in ODT-Datei
+    *
+    * @param {String} pCondition req Condition
+    * @param {String} pODTFileName req Filename des odt-Datei
+    * @param {String} pAddressID opt ID von der die Adressdaten geholt werden
+    *
+    * @return {Boolean}
+    */
+    function _replaceODTFile (pCondition, pODTFileName, pAddressID)
+    {
+        // Configuration für die Platzhalter
+        var config = PlaceholderUtils.getPlaceholders(); //["RELATIONID","fieldname","RELATION.RELATIONID"]
+        var senderRelId = EmployeeUtils.getCurrentContactId();
+        if (senderRelId == null)
+            return false;
+        var addrdata = getAddressesData(pCondition, config, senderRelId, pAddressID);
+        if (addrdata.length > 1)
+        {
+            var relationids = [];
+            for (let i = 1; i < addrdata.length; i++ )	
+                relationids.push(addrdata[i][0]);
+
+            // ersetzen Platzhalter in content.xml
+
+            var textS = util.decodeBase64String(pack.getFromZip(pODTFileName, "content.xml"));
+            var bodybegin = textS.indexOf("<office:body>");
+            var bodyend =  textS.indexOf("</office:body>") + 14;
+            var body = textS.substring( bodybegin, bodyend );
+            var lastbody = textS.substr( bodyend );
+            textS = textS.substring( 0, bodybegin );
+            for (let i = 1; i < addrdata.length; i++)
+            {
+                var bulkbody = body;
+                for (let ii = 0; ii < addrdata[0].length; ii++)
+                {
+                    bulkbody = bulkbody.replace(new RegExp(getDefaultODTplaceholer(addrdata[0][ii]), "ig"),
+                        addrdata[i][ii].replace(/\n/ig, "<text:line-break/>").replace(/&/ig, "&amp;") );
+                }
+                textS += bulkbody;
+            }
+            textS +=  lastbody;
+            pack.addToZip(pODTFile, "content.xml", util.encodeBase64String(textS))
+            // ersetzen Platzhalter in styles.xml
+            var styles = util.decodeBase64String(pack.getFromZip(pODTFileName, "styles.xml"));
+            for (let i = 0; i < addrdata[0].length; i++)
+            {
+                styles = styles.replace(new RegExp( getDefaultODTplaceholer(addrdata[0][i]), "ig"),
+                    addrdata[1][i].replace(/\n/ig, "<text:line-break/>").replace(/&/ig, "&amp;"));
+            }
+            pack.addToZip(pODTFile, "styles.xml", util.encodeBase64String(styles));
+            return true;
+        }
+        return false;
+    }
 }
 
 /*
@@ -204,8 +198,8 @@ DocumentTemplate.prototype._getReplacedDOCX = function (pReplacements)
 {
     //this is executed as a process because of better performance
     var documentData = process.execute("getDocxDocument_serverProcess", {
-        templateb64: this.templateText,
-        placeholderConfig: JSON.stringify(pPlaceholder) //process.execute is only able to handle strings
+        templateb64: this.content,
+        placeholderConfig: JSON.stringify(pReplacements) //process.execute is only able to handle strings
     });
 
     return documentData;
@@ -234,128 +228,68 @@ DocumentTemplateUtils._getPlaceholderConfig = function ()
 }
 
 
-/*
-* ersetzt die Platzhalter in ODT-Datei
-* 
-* @deprecated needs refactoring
-*
-* @param {String} pCondition req Condition
-* @param {String} pODTFile req Filename des odt-Datei
-* @param {String} pAddressID opt ID von der die Adressdaten geholt werden
-*
-* @return {void}
-*/
-DocumentTemplateUtils.replaceODTFile = function (pCondition, pODTFile, pAddressID)
-{
-    // Configuration für die Platzhalter
-    var config = [["RELATIONID","fieldname","RELATION.RELATIONID"]]
-    config = config.concat(DocumentTemplateUtils._getPlaceholderConfig());
-    var senderRelId = getSendRelID();
-    if (senderRelId == null)
-        return false;
-    var addrdata = getAddressesData(pCondition, config, senderRelId, pAddressID);
-    if (addrdata.length > 1)
-    {
-        var relationids = [];
-        for (let i = 1; i < addrdata.length; i++ )	
-            relationids.push(addrdata[i][0]);
-        
-
-        // ersetzen Platzhalter in content.xml
+function PlaceholderUtils () {}
 
-        var textS = util.decodeBase64String(pack.getFromZip(pODTFile, "content.xml"));
-        var bodybegin = textS.indexOf("<office:body>");
-        var bodyend =  textS.indexOf("</office:body>") + 14;
-        var body = textS.substring( bodybegin, bodyend );
-        var lastbody = textS.substr( bodyend );
-        textS = textS.substring( 0, bodybegin );
-        for (let i = 1; i < addrdata.length; i++)
-        {
-            var bulkbody = body;
-            for (let ii = 0; ii < addrdata[0].length; ii++)
-            {
-                bulkbody = bulkbody.replace( new RegExp( getDefaultODTplaceholer(addrdata[0][ii]), "ig"),
-                    addrdata[i][ii].replace( new RegExp( "\n", "ig"), "<text:line-break/>").replace( new RegExp( "&", "ig"), "&amp;") );
-            }
-            textS += bulkbody;
-        }
-        textS +=  lastbody;
-        pack.addToZip(pODTFile, "content.xml", util.encodeBase64String(textS))
-        // ersetzen Platzhalter in styles.xml
-        var styles = util.decodeBase64String(pack.getFromZip(pODTFile, "styles.xml"));
-        for (let i = 0; i < addrdata[0].length; i++)
-        {
-            styles = styles.replace( new RegExp( getDefaultODTplaceholer(addrdata[0][i]), "ig"),
-                addrdata[1][i].replace( new RegExp( "\n", "ig"), "<text:line-break/>").replace( new RegExp( "&", "ig"), "&amp;") );
-        }
-        pack.addToZip(pODTFile, "styles.xml", util.encodeBase64String(styles));
-        return true;
-    }
-    return false;
-}
+PlaceholderUtils.types = {
+    ADDRESSFORMAT : "ADDRESSFORMAT",
+    SQLPART : "SQLPART",
+    SQLPARTFUNCTION : "SQLPARTFUNCTION"
+};
+PlaceholderUtils.targets = {
+    RECIPIENT : "RECIPIENT",
+    SENDER : "SENDER",
+    EMPLOYEE : "EMPLOYEE"
+};
 
-/*
-* Liefert Vorlage mit ersetzen Platzhalter durch den jeweiligen Text.
-* 
-* @deprecated needs refactoring
-*
-* @param {String} pContactId req RELATIONID der relation, von der die Adressdaten geholt werden - SQL where condition für getAddressesData()
-* @param {Integer[]} pDocuType req OATYPE der Vorlage
-* @param {String} pLanguage opt Sprache
-* @param {String} pAddressID opt pAddressID
-* @param {String} pSenderID opt UserRelationID
-* @param {String} pTemplateName opt Name der Vorlage
-*
-* @return {Obj} { id, name, language, attachments:[[Name, Data]], template:{filename, data},
-*                 exportoption:{fieldids, open, file, fieldseperator, fieldlimit, recordseperator} }
-*       oder {Boolean:false} wenn keine Vorlage vorhanden ist oder ausgewählt wurde
-*/
-DocumentTemplateUtils.getReplacedText = function (pContactId, pDocuType, pLanguage, pAddressID, pSenderID, pTemplateName)
+/**
+ * Returns the placeholder with the required prefix and postfix added.
+ * This function defines the format for placeholders.
+ */
+PlaceholderUtils.formatPlaceholder = function (pPlaceholder)
 {
-    var document = chooseTemplate( pDocuType, pLanguage, pTemplateName);
-    if (!document)  return null;
+    return "@@" + pPlaceholder + "@@";
+}
 
-    var isHtml = document.template.data.substr(0, 6) == "<html>"
-    if (document.template.filename != undefined)
-        document.template.data = decode64(document.template.data);
 
-    // Configuration für die Platzhalter
-    var config = KeywordData.getKeywordAttributeRelations("TextPlaceholder"); //->keyword registry
-    var addrdata = getAddressesData( [pContactId], config, pSenderID, pAddressID );
-    var value;
-    var prefix = "@@";
-    for (let i = 0, l = addrdata[0].length; i < l; i++)
+PlaceholderUtils.getPlaceholders = function () 
+{
+    function Placeholder (pName, pType, pValueDef, pTarget) 
     {
-        if (isHtml) 
-            value = addrdata[1][i].replace(new RegExp( "\n", "ig"), "<br>");
-        else 	
-            value = addrdata[1][i];
-        document.template.data = document.template.data.replace(new RegExp(prefix + addrdata[0][i], "ig"), value);
+        this.placeholderName = PlaceholderUtils.formatPlaceholder(pName);
+        this.type = pType;
+        this.target = pTarget || PlaceholderUtils.targets.RECIPIENT;
+        this.valueDefinition = pValueDef;
     }
     
-    return document;
-}
-
-
-
-/*
- * transforms a given placeholerformat into the ODT-placeholer thats in the ODT
- * if you have to change this (e.g. to @@) you can do this at this 1 position
- *
- * @deprecated needs refactoring
- *
- * @param {String} pPlaceholderName req name of the placeholer, e.g. "Anrede"
- * @param {bool} pOnlyStart opt if set to true only the leading-symbols are added
- *
- * @return {String} placeholder with placeholder-symbols, e.g. "{@Anrede@}"
- */
-DocumentTemplateUtils.getDefaultODTplaceholer = function (pPlaceholderName, pOnlyStart)
-{
-    if (pOnlyStart)
-        return "{@" + pPlaceholderName;
-    return "{@" + pPlaceholderName + "@}";
+    function _addAddressFormat (pName, pFormat, pTarget)
+    {
+        placeholders.push(new Placeholder(pName, PlaceholderUtils.types.ADDRESSFORMAT, pFormat, pTarget));
+    }
+    
+    function _addSqlPart (pName, pSqlPart, pAddBraces)
+    {
+        placeholders.push(new Placeholder(pName, PlaceholderUtils.types.SQLPART, pAddBraces ? "(" + pSqlPart + ")" : pSqlPart));
+    }
+    
+    function _addSqlPartFunction (pName, pSqlPartFunction, pTarget)
+    {
+        placeholders.push(new Placeholder(pName, PlaceholderUtils.types.SQLPARTFUNCTION, pSqlPartFunction, pTarget));
+    }
+    
+    var placeholders = [];
+    
+    _addAddressFormat("address", "{street} {buildingno}");
+    _addAddressFormat("zipCode", "{zip}");
+    _addAddressFormat("city", "{city}");
+    _addAddressFormat("district", "{district}");
+    _addAddressFormat("region", "{region}");
+    _addAddressFormat("country", "{country}");
+    _addAddressFormat("letterSalutation", "{letter_salutation}");
+    _addAddressFormat("fullAddress", "");
+    _addAddressFormat("senderOrgname", "{organisation_name}", PlaceholderUtils.targets.SENDER);
+    _addAddressFormat("senderAddress", "{street} {buildingno}", PlaceholderUtils.targets.SENDER);
+    _addAddressFormat("senderZipCity", "{country} - {zip} {city}", PlaceholderUtils.targets.SENDER);
+    _addAddressFormat("senderFullAddress", "", PlaceholderUtils.targets.SENDER);
+    
+    return placeholders;
 }
-
-
-
-
diff --git a/process/Email_lib/process.js b/process/Email_lib/process.js
index a6002c411cdfae930617ccfbdd96068180e07b59..ac0feee222fabc11b981d42c676b9d48b4bd6019 100644
--- a/process/Email_lib/process.js
+++ b/process/Email_lib/process.js
@@ -1,3 +1,4 @@
+import("system.logging");
 import("system.translate");
 import("system.text");
 import("system.db");
@@ -16,15 +17,16 @@ function EmailUtils () {}
  * In Thunderbird the mail is opened in view mode and you've to manually "edit as new"
  * 
  * @param {String|Array} pToRecipients mailaddresses of the recipients, can either be a 1D-Array with several addresses or a string with one address
- * @param {String} pContactId contactId of the sender. the standard mailadress of the contact is used as sender-address
+ * @param {String} pSenderContactId contactId of the sender. the standard mailadress of the contact is used as sender-address
  * @param {String} [pTemplateId] if a document-template shall be used, give the templateId here
+ * @param {String} [pRecipientContactId] contactId of the recipient, required to fill placeholders
  */
-EmailUtils.openMailTemplate = function (pToRecipients, pContactId, pTemplateId)
+EmailUtils.openMailTemplate = function (pToRecipients, pSenderContactId, pTemplateId, pRecipientContactId)
 {
     var email = new Email(pToRecipients);
-    email.setSender(pContactId);
+    email.setSender(pSenderContactId);
     if (pTemplateId)
-        email.setTemplate(pTemplateId);
+        email.setTemplate(pTemplateId, pRecipientContactId);
 
     email.downloadEML();
 }
@@ -71,10 +73,15 @@ function Email (pToRecipients, pSender, pSubject, pBody, pCcRecipients, pBccReci
  * loads a document template into the mail body
  * 
  * @param {String} pTemplateId the id of the template
+ * @param {String} pContactId the id of the template
  */
-Email.prototype.setTemplate = function (pTemplateId)
+Email.prototype.setTemplate = function (pTemplateId, pContactId)
 {
-    this.body = String(DocumentTemplate.loadTemplate());
+    var template = DocumentTemplate.loadTemplate(pTemplateId);
+    //TODO: also set other properties if the template is a eml
+    if (template)
+        this.body = template.getReplacedContentByContactId(pContactId);
+    logging.log(this.body)
 }
 
 /**