NobGit
public nobgit read

NobMail

Based on mailcow: dockerized

Languages

Repository composition by tracked source files.

PHP
PHP 49% JavaScript 35% HTML 9% CSS 4% Shell 2% Python 1% Lua 0% Perl 0% Ruby 0% SCSS 0%
Create file Wiki Documentation
Clone
https://nobgit.com/orgs/nobgit/nobmail.git
ssh://[email protected]:2222/orgs/nobgit/nobmail.git

Commit

[Postfix] Allow to set and override a relayhost per mailbox [Web] Replace recycle icon with trash (this one made me a bit sad) [Web] Various small fixes [Web] Allow or disallow a domain admin to change relayhost settings (default is off, as previous default)

5065667a
andryyy <[email protected]> 5 years ago
data/Dockerfiles/postfix/postfix.sh       | 29 +++++++++++++-----
 data/web/admin.php                        |  6 ++--
 data/web/css/build/008-mailcow.css        |  1 -
 data/web/css/site/edit.css                |  4 +++
 data/web/edit.php                         | 51 ++++++++++++++++++++-----------
 data/web/inc/functions.inc.php            | 13 ++++----
 data/web/inc/functions.mailbox.inc.php    |  5 +++
 data/web/inc/functions.transports.inc.php |  9 ++++--
 data/web/inc/init_db.inc.php              |  5 ++-
 data/web/js/site/admin.js                 | 19 +++++++-----
 data/web/js/site/debug.js                 |  2 +-
 data/web/js/site/mailbox.js               | 26 ++++++++--------
 data/web/js/site/quarantine.js            |  2 +-
 data/web/js/site/user.js                  |  6 ++--
 data/web/lang/lang.cs.json                |  2 +-
 data/web/lang/lang.da.json                |  2 +-
 data/web/lang/lang.de.json                |  6 +++-
 data/web/lang/lang.en.json                |  6 +++-
 data/web/lang/lang.fi.json                |  2 +-
 data/web/lang/lang.fr.json                |  2 +-
 data/web/lang/lang.ko.json                |  2 +-
 data/web/lang/lang.nl.json                |  4 +--
 data/web/lang/lang.ro.json                |  2 +-
 data/web/lang/lang.sk.json                |  2 +-
 data/web/lang/lang.sv.json                |  2 +-
 data/web/lang/lang.zh.json                |  2 +-
 data/web/user.php                         | 25 +++++++--------
 docker-compose.yml                        |  2 +-
 28 files changed, 150 insertions(+), 89 deletions(-)

Diff

diff --git a/data/Dockerfiles/postfix/postfix.sh b/data/Dockerfiles/postfix/postfix.sh
index 753f446e..fac8bbf4 100755
--- a/data/Dockerfiles/postfix/postfix.sh
+++ b/data/Dockerfiles/postfix/postfix.sh
@@ -125,16 +125,31 @@ query = SELECT GROUP_CONCAT(transport SEPARATOR '') AS transport_maps
         AND mailbox.active = '1'
     ), 'smtp_enforced_tls:', 'smtp:') AS 'transport'
     UNION ALL
-    SELECT hostname AS transport FROM relayhosts
+    SELECT COALESCE(
+      (SELECT hostname FROM relayhosts
+      LEFT OUTER JOIN mailbox ON JSON_UNQUOTE(JSON_VALUE(mailbox.attributes, '$.relayhost')) = relayhosts.id
+        WHERE relayhosts.active = '1'
+          AND (
+            mailbox.username IN (SELECT alias.goto from alias
+              JOIN mailbox ON mailbox.username = alias.goto
+                WHERE alias.active = '1'
+                  AND alias.address = '%s'
+                  AND alias.address NOT LIKE '@%%'
+            )
+          )
+      ),
+      (SELECT hostname FROM relayhosts
       LEFT OUTER JOIN domain ON domain.relayhost = relayhosts.id
         WHERE relayhosts.active = '1'
-          AND domain = '%d'
-          OR domain IN (
-            SELECT target_domain FROM alias_domain
-              WHERE alias_domain = '%d'
+          AND (domain.domain = '%d'
+            OR domain.domain IN (
+              SELECT target_domain FROM alias_domain
+                WHERE alias_domain = '%d'
+            )
           )
-  )
-  AS transport_view;
+      )
+    )
+  ) AS transport_view;
 EOF
 
 cat <<EOF > /opt/postfix/conf/sql/mysql_transport_maps.cf
diff --git a/data/web/admin.php b/data/web/admin.php
index 0d02c947..a9a85a2a 100644
--- a/data/web/admin.php
+++ b/data/web/admin.php
@@ -139,7 +139,7 @@ if (!isset($_SESSION['gal']) && $license_cache = $redis->Get('LICENSE_STATUS_CAC
                     <input type="hidden" name="unset_fido2_key" value="<?=$key_info['cid'];?>">
                     <div class="btn-group">
                     <a href="#" class="btn btn-xs btn-default" data-cid="<?=$key_info['cid'];?>" data-subject="<?=base64_encode($key_info['subject']);?>" data-toggle="modal" data-target="#fido2ChangeFn"><i class="bi bi-pencil-fill"></i> <?=$lang['fido2']['rename'];?></a>
-                    <a href="#" onClick='return confirm("<?=$lang['admin']['ays'];?>")?$(this).closest("form").submit():"";' class="btn btn-xs btn-danger"><i class="bi bi-recycle"></i> <?=$lang['admin']['remove'];?></a>
+                    <a href="#" onClick='return confirm("<?=$lang['admin']['ays'];?>")?$(this).closest("form").submit():"";' class="btn btn-xs btn-danger"><i class="bi bi-trash"></i> <?=$lang['admin']['remove'];?></a>
                     </form>
                     </div>
                   </td>
@@ -618,7 +618,7 @@ if (!isset($_SESSION['gal']) && $license_cache = $redis->Get('LICENSE_STATUS_CAC
         <div class="mass-actions-admin">
           <div class="btn-group btn-group-sm">
             <button type="button" id="toggle_multi_select_all" data-id="dkim" class="btn btn-default"><i class="bi bi-check-all"></i> <?=$lang['mailbox']['toggle_all'];?></button>
-            <button type="button" data-action="delete_selected" name="delete_selected" data-id="dkim" data-api-url="delete/dkim" class="btn btn-danger"><i class="bi bi-recycle"></i> <?=$lang['admin']['remove'];?></button>
+            <button type="button" data-action="delete_selected" name="delete_selected" data-id="dkim" data-api-url="delete/dkim" class="btn btn-danger"><i class="bi bi-trash"></i> <?=$lang['admin']['remove'];?></button>
           </div>
         </div>
 
@@ -1402,7 +1402,7 @@ if (!isset($_SESSION['gal']) && $license_cache = $redis->Get('LICENSE_STATUS_CAC
             data-api-url='edit/mailq'
             data-api-attr='{"action":"super_delete"}'
             data-toggle="tooltip" title="postsuper -d ALL"
-            href="#"><i class="bi bi-recycle"></i> <?=$lang['admin']['delete_queue'];?></a>
+            href="#"><i class="bi bi-trash"></i> <?=$lang['admin']['delete_queue'];?></a>
         </div>
       </div>
       </div>
diff --git a/data/web/css/build/008-mailcow.css b/data/web/css/build/008-mailcow.css
index cdd44817..9418da50 100644
--- a/data/web/css/build/008-mailcow.css
+++ b/data/web/css/build/008-mailcow.css
@@ -76,7 +76,6 @@
   font-size: inherit;
 }
 .icon-spin {
-  font-size: 1.0rem;
   animation-name: spin;
   animation-duration: 2000ms;
   animation-iteration-count: infinite;
diff --git a/data/web/css/site/edit.css b/data/web/css/site/edit.css
index 1242af2b..558215b4 100644
--- a/data/web/css/site/edit.css
+++ b/data/web/css/site/edit.css
@@ -40,4 +40,8 @@
 .input-group-addon-xmpp {
   background-color: #fff;
   border: 0px solid #fff;
+}
+#sender_acl_disabled {
+  display:none;
+  margin-top:10px;
 }
\ No newline at end of file
diff --git a/data/web/edit.php b/data/web/edit.php
index fa061523..f1168ded 100644
--- a/data/web/edit.php
+++ b/data/web/edit.php
@@ -281,6 +281,21 @@ if (isset($_SESSION['mailcow_cc_role'])) {
                   <input type="text" class="form-control" name="description" value="<?=htmlspecialchars($result['description']);?>">
                 </div>
               </div>
+              <div class="form-group">
+                <label class="control-label col-sm-2" for="relayhost"><?=$lang['edit']['relayhost'];?></label>
+                <div class="col-sm-10">
+                  <select data-acl="<?=$_SESSION['acl']['domain_relayhost'];?>" data-live-search="true" id="relayhost" name="relayhost" class="form-control">
+                    <?php
+                    foreach ($rlyhosts as $rlyhost) {
+                    ?>
+                    <option class="<?=($rlyhost['active'] == 1) ? '' : 'background: #ff4136; color: #fff';?>" value="<?=$rlyhost['id'];?>" <?=($result['relayhost'] == $rlyhost['id']) ? 'selected' : null;?>>ID <?=$rlyhost['id'];?>: <?=$rlyhost['hostname'];?> (<?=$rlyhost['username'];?>)</option>
+                    <?php
+                    }
+                    ?>
+                    <option value="" <?=($result['relayhost'] == "0") ? 'selected' : null;?>>None</option>
+                  </select>
+                </div>
+              </div>
               <?php
               if ($_SESSION['mailcow_cc_role'] == "admin") {
               ?>
@@ -314,21 +329,6 @@ if (isset($_SESSION['mailcow_cc_role'])) {
                   <input type="number" class="form-control" name="quota" value="<?=intval($result['max_quota_for_domain'] / 1048576);?>">
                 </div>
               </div>
-              <div class="form-group">
-                <label class="control-label col-sm-2" for="quota"><?=$lang['edit']['relayhost'];?></label>
-                <div class="col-sm-10">
-                  <select data-live-search="true" name="relayhost" class="form-control">
-                    <?php
-                    foreach ($rlyhosts as $rlyhost) {
-                    ?>
-                    <option value="<?=$rlyhost['id'];?>" <?=($result['relayhost'] == $rlyhost['id']) ? 'selected' : null;?>>ID <?=$rlyhost['id'];?>: <?=$rlyhost['hostname'];?> (<?=$rlyhost['username'];?>)</option>
-                    <?php
-                    }
-                    ?>
-                    <option value="" <?=($result['relayhost'] == "0") ? 'selected' : null;?>>None</option>
-                  </select>
-                </div>
-              </div>
               <div class="form-group">
                 <label class="control-label col-sm-2"><?=$lang['edit']['backup_mx_options'];?></label>
                 <div class="col-sm-10">
@@ -640,6 +640,7 @@ if (isset($_SESSION['mailcow_cc_role'])) {
       $quarantine_notification = mailbox('get', 'quarantine_notification', $mailbox);
       $quarantine_category = mailbox('get', 'quarantine_category', $mailbox);
       $get_tls_policy = mailbox('get', 'tls_policy', $mailbox);
+      $rlyhosts = relayhost('get');
       if (!empty($result)) {
         ?>
         <h4><?=$lang['edit']['mailbox'];?></h4>
@@ -724,10 +725,26 @@ if (isset($_SESSION['mailcow_cc_role'])) {
 
               ?>
               </select>
-              <div style="display:none" id="sender_acl_disabled"><?=$lang['edit']['sender_acl_disabled'];?></div>
+              <div id="sender_acl_disabled"><i class="bi bi-shield-exclamation"></i> <?=$lang['edit']['sender_acl_disabled'];?></div>
               <small class="help-block"><?=$lang['edit']['sender_acl_info'];?></small>
             </div>
           </div>
+          <div class="form-group">
+            <label class="control-label col-sm-2" for="relayhost"><?=$lang['edit']['relayhost'];?></label>
+            <div class="col-sm-10">
+              <select data-acl="<?=$_SESSION['acl']['mailbox_relayhost'];?>" data-live-search="true" id="relayhost" name="relayhost" class="form-control">
+                <?php
+                foreach ($rlyhosts as $rlyhost) {
+                ?>
+                    <option style="<?=($rlyhost['active'] == 1) ? '' : 'background: #ff4136; color: #fff';?>" value="<?=$rlyhost['id'];?>" <?=($result['attributes']['relayhost'] == $rlyhost['id']) ? 'selected' : null;?>>ID <?=$rlyhost['id'];?>: <?=$rlyhost['hostname'];?> (<?=$rlyhost['username'];?>)</option>
+                <?php
+                }
+                ?>
+                <option value="" <?=($result['attributes']['relayhost'] == "0") ? 'selected' : null;?>>None</option>
+              </select>
+              <small class="help-block"><?=$lang['edit']['mailbox_relayhost_info'];?></small>
+            </div>
+          </div>
           <div class="form-group">
             <label class="control-label col-sm-2"><?=$lang['user']['quarantine_notification'];?></label>
             <div class="col-sm-10">
@@ -965,7 +982,7 @@ if (isset($_SESSION['mailcow_cc_role'])) {
               <div class="btn-group" data-acl="<?=$_SESSION['acl']['pushover'];?>">
                   <a class="btn btn-sm btn-default" data-action="edit_selected" data-id="pushover" data-item="<?=htmlspecialchars($mailbox);?>" data-api-url='edit/pushover' data-api-attr='{}' href="#"><?=$lang['edit']['save'];?></a>
                   <a class="btn btn-sm btn-default" data-action="edit_selected" data-id="pushover-test" data-item="<?=htmlspecialchars($mailbox);?>" data-api-url='edit/pushover-test' data-api-attr='{}' href="#"><i class="bi bi-check-lg"></i> <?=$lang['edit']['pushover_verify'];?></a>
-                  <a id="pushover_delete" class="btn btn-sm btn-danger" data-action="edit_selected" data-id="pushover-delete" data-item="<?=htmlspecialchars($mailbox);?>" data-api-url='edit/pushover' data-api-attr='{"delete":"true"}' href="#"><i class="bi bi-recycle"></i> <?=$lang['edit']['remove'];?></a>
+                  <a id="pushover_delete" class="btn btn-sm btn-danger" data-action="edit_selected" data-id="pushover-delete" data-item="<?=htmlspecialchars($mailbox);?>" data-api-url='edit/pushover' data-api-attr='{"delete":"true"}' href="#"><i class="bi bi-trash"></i> <?=$lang['edit']['remove'];?></a>
               </div>
             </div>
           </div>
diff --git a/data/web/inc/functions.inc.php b/data/web/inc/functions.inc.php
index 33c58007..f107863b 100644
--- a/data/web/inc/functions.inc.php
+++ b/data/web/inc/functions.inc.php
@@ -940,8 +940,9 @@ function edit_user_account($_data) {
 }
 function user_get_alias_details($username) {
 	global $pdo;
-  $data['direct_aliases'] = false;
-  $data['shared_aliases'] = false;
+	global $lang;
+  $data['direct_aliases'] = array();
+  $data['shared_aliases'] = array();
   if ($_SESSION['mailcow_cc_role'] == "user") {
     $username	= $_SESSION['mailcow_cc_username'];
   }
@@ -987,22 +988,22 @@ function user_get_alias_details($username) {
     if (empty($row['ad_alias'])) {
       continue;
     }
-    $data['direct_aliases'][$row['ad_alias']]['public_comment'] = '↪ ' . $row['alias_domain'];
+    $data['direct_aliases'][$row['ad_alias']]['public_comment'] = '<span data-toggle="tooltip" title="' . $lang['add']['alias_domain'] . '">' . $row['alias_domain'] . '</span>';
     $data['alias_domains'][] = $row['alias_domain'];
   }
-  $stmt = $pdo->prepare("SELECT IFNULL(GROUP_CONCAT(`send_as` SEPARATOR ', '), '&#10008;') AS `send_as` FROM `sender_acl` WHERE `logged_in_as` = :username AND `send_as` NOT LIKE '@%';");
+  $stmt = $pdo->prepare("SELECT IFNULL(GROUP_CONCAT(`send_as` SEPARATOR ', '), '') AS `send_as` FROM `sender_acl` WHERE `logged_in_as` = :username AND `send_as` NOT LIKE '@%';");
   $stmt->execute(array(':username' => $username));
   $run = $stmt->fetchAll(PDO::FETCH_ASSOC);
   while ($row = array_shift($run)) {
     $data['aliases_also_send_as'] = $row['send_as'];
   }
-  $stmt = $pdo->prepare("SELECT CONCAT_WS(', ', IFNULL(GROUP_CONCAT(DISTINCT `send_as` SEPARATOR ', '), '&#10008;'), GROUP_CONCAT(DISTINCT CONCAT('@',`alias_domain`) SEPARATOR ', ')) AS `send_as` FROM `sender_acl` LEFT JOIN `alias_domain` ON `alias_domain`.`target_domain` =  TRIM(LEADING '@' FROM `send_as`) WHERE `logged_in_as` = :username AND `send_as` LIKE '@%';");
+  $stmt = $pdo->prepare("SELECT CONCAT_WS(', ', IFNULL(GROUP_CONCAT(DISTINCT `send_as` SEPARATOR ', '), ''), GROUP_CONCAT(DISTINCT CONCAT('@',`alias_domain`) SEPARATOR ', ')) AS `send_as` FROM `sender_acl` LEFT JOIN `alias_domain` ON `alias_domain`.`target_domain` =  TRIM(LEADING '@' FROM `send_as`) WHERE `logged_in_as` = :username AND `send_as` LIKE '@%';");
   $stmt->execute(array(':username' => $username));
   $run = $stmt->fetchAll(PDO::FETCH_ASSOC);
   while ($row = array_shift($run)) {
     $data['aliases_send_as_all'] = $row['send_as'];
   }
-  $stmt = $pdo->prepare("SELECT IFNULL(GROUP_CONCAT(`address` SEPARATOR ', '), '&#10008;') as `address` FROM `alias` WHERE `goto` REGEXP :username AND `address` LIKE '@%';");
+  $stmt = $pdo->prepare("SELECT IFNULL(GROUP_CONCAT(`address` SEPARATOR ', '), '') as `address` FROM `alias` WHERE `goto` REGEXP :username AND `address` LIKE '@%';");
   $stmt->execute(array(':username' => '(^|,)'.$username.'($|,)'));
   $run = $stmt->fetchAll(PDO::FETCH_ASSOC);
   while ($row = array_shift($run)) {
diff --git a/data/web/inc/functions.mailbox.inc.php b/data/web/inc/functions.mailbox.inc.php
index 33ddac39..8bea647b 100644
--- a/data/web/inc/functions.mailbox.inc.php
+++ b/data/web/inc/functions.mailbox.inc.php
@@ -2141,6 +2141,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
                 $xmpp                 = (isset($_data['xmpp']) && !empty($_SESSION['acl']['xmpp_domain_access']) && $_SESSION['acl']['xmpp_domain_access'] == "1") ? intval($_data['xmpp']) : $is_now['xmpp'];
                 $xmpp_prefix          = (!empty($_data['xmpp_prefix']) && !empty($_SESSION['acl']['xmpp_prefix']) && $_SESSION['acl']['xmpp_prefix'] == "1") ? $_data['xmpp_prefix'] : $is_now['xmpp_prefix'];
                 $description          = (!empty($_data['description']) && isset($_SESSION['acl']['domain_desc']) && $_SESSION['acl']['domain_desc'] == "1") ? $_data['description'] : $is_now['description'];
+                (int)$relayhost       = (isset($_data['relayhost']) && isset($_SESSION['acl']['domain_relayhost']) && $_SESSION['acl']['domain_relayhost'] == "1") ? intval($_data['relayhost']) : intval($is_now['relayhost']);
               }
               else {
                 $_SESSION['return'][] = array(
@@ -2359,6 +2360,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
               (int)$smtp_access = (isset($_data['smtp_access']) && isset($_SESSION['acl']['protocol_access']) && $_SESSION['acl']['protocol_access'] == "1") ? intval($_data['smtp_access']) : intval($is_now['attributes']['smtp_access']);
               (int)$xmpp_admin = (isset($_data['xmpp_admin']) && isset($_SESSION['acl']['xmpp_admin']) && $_SESSION['acl']['xmpp_admin'] == "1") ? intval($_data['xmpp_admin']) : intval($is_now['attributes']['xmpp_admin']);
               (int)$xmpp_access = (isset($_data['xmpp_access']) && isset($_SESSION['acl']['xmpp_mailbox_access']) && $_SESSION['acl']['xmpp_mailbox_access'] == "1") ? intval($_data['xmpp_access']) : intval($is_now['attributes']['xmpp_access']);
+              (int)$relayhost = (isset($_data['relayhost']) && isset($_SESSION['acl']['mailbox_relayhost']) && $_SESSION['acl']['mailbox_relayhost'] == "1") ? intval($_data['relayhost']) : intval($is_now['attributes']['relayhost']);
               (int)$quota_m = (isset_has_content($_data['quota'])) ? intval($_data['quota']) : ($is_now['quota'] / 1048576);
               $name       = (!empty($_data['name'])) ? ltrim(rtrim($_data['name'], '>'), '<') : $is_now['name'];
               $domain     = $is_now['domain'];
@@ -2631,6 +2633,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
                 `attributes` = JSON_SET(`attributes`, '$.pop3_access', :pop3_access),
                 `attributes` = JSON_SET(`attributes`, '$.xmpp_admin', :xmpp_admin),
                 `attributes` = JSON_SET(`attributes`, '$.xmpp_access', :xmpp_access),
+                `attributes` = JSON_SET(`attributes`, '$.relayhost', :relayhost),
                 `attributes` = JSON_SET(`attributes`, '$.smtp_access', :smtp_access)
                   WHERE `username` = :username");
             $stmt->execute(array(
@@ -2644,6 +2647,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
               ':smtp_access' => $smtp_access,
               ':xmpp_admin' => $xmpp_admin,
               ':xmpp_access' => $xmpp_access,
+              ':relayhost' => $relayhost,
               ':username' => $username
             ));
             $_SESSION['return'][] = array(
@@ -3561,6 +3565,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
           $mailboxdata['active_int'] = $row['active'];
           $mailboxdata['domain'] = $row['domain'];
           $mailboxdata['domain_xmpp'] = $row['domain_xmpp'];
+          $mailboxdata['relayhost'] = $row['relayhost'];
           $mailboxdata['name'] = $row['name'];
           $mailboxdata['domain_xmpp_prefix'] = $row['domain_xmpp_prefix'];
           $mailboxdata['local_part'] = $row['local_part'];
diff --git a/data/web/inc/functions.transports.inc.php b/data/web/inc/functions.transports.inc.php
index 7d8031aa..bc14ecb2 100644
--- a/data/web/inc/functions.transports.inc.php
+++ b/data/web/inc/functions.transports.inc.php
@@ -137,11 +137,11 @@ function relayhost($_action, $_data = null) {
       }
     break;
     case 'get':
-      if ($_SESSION['mailcow_cc_role'] != "admin") {
+      if ($_SESSION['mailcow_cc_role'] != "admin" && $_SESSION['mailcow_cc_role'] != "domainadmin") {
         return false;
       }
       $relayhosts = array();
-      $stmt = $pdo->query("SELECT `id`, `hostname`, `username` FROM `relayhosts`");
+      $stmt = $pdo->query("SELECT `id`, `hostname`, `username`, `active` FROM `relayhosts`");
       $relayhosts = $stmt->fetchAll(PDO::FETCH_ASSOC);
       return $relayhosts;
     break;
@@ -166,6 +166,11 @@ function relayhost($_action, $_data = null) {
         $used_by_domains = $stmt->fetch(PDO::FETCH_ASSOC)['used_by_domains'];
         $used_by_domains = (empty($used_by_domains)) ? '' : $used_by_domains;
         $relayhostdata['used_by_domains'] = $used_by_domains;
+        $stmt = $pdo->prepare("SELECT GROUP_CONCAT(`username` SEPARATOR ', ') AS `used_by_mailboxes` FROM `mailbox` WHERE JSON_VALUE(`attributes`, '$.relayhost') = :id");
+        $stmt->execute(array(':id' => $_data));
+        $used_by_mailboxes = $stmt->fetch(PDO::FETCH_ASSOC)['used_by_mailboxes'];
+        $used_by_mailboxes = (empty($used_by_mailboxes)) ? '' : $used_by_mailboxes;
+        $relayhostdata['used_by_mailboxes'] = $used_by_mailboxes;
       }
       return $relayhostdata;
     break;
diff --git a/data/web/inc/init_db.inc.php b/data/web/inc/init_db.inc.php
index 1f423ac5..85e20a7a 100644
--- a/data/web/inc/init_db.inc.php
+++ b/data/web/inc/init_db.inc.php
@@ -3,7 +3,7 @@ function init_db_schema() {
   try {
     global $pdo;
 
-    $db_version = "21052021_0900";
+    $db_version = "25052021_0900";
 
     $stmt = $pdo->query("SHOW TABLES LIKE 'versions'");
     $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC));
@@ -572,6 +572,8 @@ function init_db_schema() {
           "protocol_access" => "TINYINT(1) NOT NULL DEFAULT '1'",
           "smtp_ip_access" => "TINYINT(1) NOT NULL DEFAULT '1'",
           "alias_domains" => "TINYINT(1) NOT NULL DEFAULT '0'",
+          "mailbox_relayhost" => "TINYINT(1) NOT NULL DEFAULT '1'",
+          "domain_relayhost" => "TINYINT(1) NOT NULL DEFAULT '1'",
           "xmpp_prefix" => "TINYINT(1) NOT NULL DEFAULT '0'",
           "xmpp_domain_access" => "TINYINT(1) NOT NULL DEFAULT '0'",
           "xmpp_mailbox_access" => "TINYINT(1) NOT NULL DEFAULT '0'",
@@ -1188,6 +1190,7 @@ function init_db_schema() {
     $pdo->query("UPDATE `pushover` SET `attributes` =  JSON_SET(`attributes`, '$.only_x_prio', \"0\") WHERE JSON_VALUE(`attributes`, '$.only_x_prio') IS NULL;");
     // mailbox
     $pdo->query("UPDATE `mailbox` SET `attributes` = '{}' WHERE `attributes` = '' OR `attributes` IS NULL;");
+    $pdo->query("UPDATE `mailbox` SET `attributes` =  JSON_SET(`attributes`, '$.relayhost', \"0\") WHERE JSON_VALUE(`attributes`, '$.relayhost') IS NULL;");
     $pdo->query("UPDATE `mailbox` SET `attributes` =  JSON_SET(`attributes`, '$.xmpp_access', \"1\") WHERE JSON_VALUE(`attributes`, '$.xmpp_access') IS NULL;");
     $pdo->query("UPDATE `mailbox` SET `attributes` =  JSON_SET(`attributes`, '$.xmpp_admin', \"0\") WHERE JSON_VALUE(`attributes`, '$.xmpp_admin') IS NULL;");
     $pdo->query("UPDATE `mailbox` SET `attributes` =  JSON_SET(`attributes`, '$.force_pw_update', \"0\") WHERE JSON_VALUE(`attributes`, '$.force_pw_update') IS NULL;");
diff --git a/data/web/js/site/admin.js b/data/web/js/site/admin.js
index ffaf5dff..f7dbec8c 100644
--- a/data/web/js/site/admin.js
+++ b/data/web/js/site/admin.js
@@ -187,7 +187,7 @@ jQuery(function($){
         {"name":"id","type":"text","title":"ID","style":{"width":"50px"}},
         {"name":"hostname","type":"text","title":lang.host,"style":{"width":"250px"}},
         {"name":"username","title":lang.username,"breakpoints":"xs sm"},
-        {"name":"used_by_domains","title":lang.in_use_by,"style":{"width":"110px"}, "type": "text","breakpoints":"xs sm"},
+        {"name":"in_use_by","title":lang.in_use_by,"style":{"width":"110px"}, "type": "text","breakpoints":"xs sm"},
         {"name":"active","filterable": false,"style":{"maxWidth":"80px","width":"80px"},"title":lang.active,"formatter": function(value){return 1==value?'<i class="bi bi-check-lg"></i>':0==value&&'<i class="bi bi-x-lg"></i>';}},
         {"name":"action","filterable": false,"sortable": false,"style":{"text-align":"right","maxWidth":"220px","width":"220px"},"type":"html","title":lang.action,"breakpoints":"xs sm md"}
       ],
@@ -209,7 +209,7 @@ jQuery(function($){
     });
   }
   function draw_transport_maps() {
-    ft_relayhoststable = FooTable.init('#transportstable', {
+    ft_transportstable = FooTable.init('#transportstable', {
       "columns": [
         {"name":"chkbox","title":"","style":{"maxWidth":"60px","width":"60px"},"filterable": false,"sortable": false,"type":"html"},
         {"name":"id","type":"text","title":"ID","style":{"width":"50px"}},
@@ -279,8 +279,11 @@ jQuery(function($){
         item.action = '<div class="btn-group">' +
           '<a href="#" data-toggle="modal" data-target="#testTransportModal" data-transport-id="' + encodeURI(item.id) + '" data-transport-type="sender-dependent" class="btn btn-xs btn-default"><i class="bi bi-caret-right-fill"></i> Test</a>' +
           '<a href="/edit/relayhost/' + encodeURI(item.id) + '" class="btn btn-xs btn-default"><i class="bi bi-pencil-fill"></i> ' + lang.edit + '</a>' +
-          '<a href="#" data-action="delete_selected" data-id="single-rlyhost" data-api-url="delete/relayhost" data-item="' + encodeURI(item.id) + '" class="btn btn-xs btn-danger"><i class="bi bi-recycle"></i> ' + lang.remove + '</a>' +
+          '<a href="#" data-action="delete_selected" data-id="single-rlyhost" data-api-url="delete/relayhost" data-item="' + encodeURI(item.id) + '" class="btn btn-xs btn-danger"><i class="bi bi-trash"></i> ' + lang.remove + '</a>' +
           '</div>';
+        if (item.used_by_mailboxes == '') { item.in_use_by = item.used_by_domains; }
+        else if (item.used_by_domains == '') { item.in_use_by = item.used_by_mailboxes; }
+        else { item.in_use_by = item.used_by_mailboxes + '<hr style="margin:5px 0px 5px 0px;">' + item.used_by_domains; }
         item.chkbox = '<input type="checkbox" data-id="rlyhosts" name="multi_select" value="' + item.id + '" />';
       });
     } else if (table == 'transportstable') {
@@ -291,7 +294,7 @@ jQuery(function($){
         item.action = '<div class="btn-group">' +
           '<a href="#" data-toggle="modal" data-target="#testTransportModal" data-transport-id="' + encodeURI(item.id) + '" data-transport-type="transport-map" class="btn btn-xs btn-default"><i class="bi bi-caret-right-fill"></i> Test</a>' +
           '<a href="/edit/transport/' + encodeURI(item.id) + '" class="btn btn-xs btn-default"><i class="bi bi-pencil-fill"></i> ' + lang.edit + '</a>' +
-          '<a href="#" data-action="delete_selected" data-id="single-transport" data-api-url="delete/transport" data-item="' + encodeURI(item.id) + '" class="btn btn-xs btn-danger"><i class="bi bi-recycle"></i> ' + lang.remove + '</a>' +
+          '<a href="#" data-action="delete_selected" data-id="single-transport" data-api-url="delete/transport" data-item="' + encodeURI(item.id) + '" class="btn btn-xs btn-danger"><i class="bi bi-trash"></i> ' + lang.remove + '</a>' +
           '</div>';
         item.chkbox = '<input type="checkbox" data-id="transports" name="multi_select" value="' + item.id + '" />';
       });
@@ -309,7 +312,7 @@ jQuery(function($){
     } else if (table == 'forwardinghoststable') {
       $.each(data, function (i, item) {
         item.action = '<div class="btn-group">' +
-          '<a href="#" data-action="delete_selected" data-id="single-fwdhost" data-api-url="delete/fwdhost" data-item="' + encodeURI(item.host) + '" class="btn btn-xs btn-danger"><i class="bi bi-recycle"></i> ' + lang.remove + '</a>' +
+          '<a href="#" data-action="delete_selected" data-id="single-fwdhost" data-api-url="delete/fwdhost" data-item="' + encodeURI(item.host) + '" class="btn btn-xs btn-danger"><i class="bi bi-trash"></i> ' + lang.remove + '</a>' +
           '</div>';
         item.chkbox = '<input type="checkbox" data-id="fwdhosts" name="multi_select" value="' + item.host + '" />';
       });
@@ -317,7 +320,7 @@ jQuery(function($){
       $.each(data, function (i, item) {
         item.action = '<div class="btn-group">' +
           '<a href="/edit.php?oauth2client=' + encodeURI(item.id) + '" class="btn btn-xs btn-default"><i class="bi bi-pencil-fill"></i> ' + lang.edit + '</a>' +
-          '<a href="#" data-action="delete_selected" data-id="single-oauth2-client" data-api-url="delete/oauth2-client" data-item="' + encodeURI(item.id) + '" class="btn btn-xs btn-danger"><i class="bi bi-recycle"></i> ' + lang.remove + '</a>' +
+          '<a href="#" data-action="delete_selected" data-id="single-oauth2-client" data-api-url="delete/oauth2-client" data-item="' + encodeURI(item.id) + '" class="btn btn-xs btn-danger"><i class="bi bi-trash"></i> ' + lang.remove + '</a>' +
           '</div>';
         item.scope = "profile";
         item.grant_types = 'refresh_token password authorization_code';
@@ -330,7 +333,7 @@ jQuery(function($){
         item.chkbox = '<input type="checkbox" data-id="domain_admins" name="multi_select" value="' + item.username + '" />';
         item.action = '<div class="btn-group">' +
           '<a href="/edit/domainadmin/' + encodeURI(item.username) + '" class="btn btn-xs btn-default"><i class="bi bi-pencil-fill"></i> ' + lang.edit + '</a>' +
-          '<a href="#" data-action="delete_selected" data-id="single-domain-admin" data-api-url="delete/domain-admin" data-item="' + encodeURI(item.username) + '" class="btn btn-xs btn-danger"><i class="bi bi-recycle"></i> ' + lang.remove + '</a>' +
+          '<a href="#" data-action="delete_selected" data-id="single-domain-admin" data-api-url="delete/domain-admin" data-item="' + encodeURI(item.username) + '" class="btn btn-xs btn-danger"><i class="bi bi-trash"></i> ' + lang.remove + '</a>' +
           '<a href="/index.php?duallogin=' + encodeURIComponent(item.username) + '" class="btn btn-xs btn-success"><i class="bi bi-person-fill"></i> Login</a>' +
           '</div>';
       });
@@ -344,7 +347,7 @@ jQuery(function($){
         item.chkbox = '<input type="checkbox" data-id="admins" name="multi_select" value="' + item.username + '" />';
         item.action = '<div class="btn-group">' +
           '<a href="/edit/admin/' + encodeURI(item.username) + '" class="btn btn-xs btn-default"><i class="bi bi-pencil-fill"></i> ' + lang.edit + '</a>' +
-          '<a href="#" data-action="delete_selected" data-id="single-admin" data-api-url="delete/admin" data-item="' + encodeURI(item.username) + '" class="btn btn-xs btn-danger"><i class="bi bi-recycle"></i> ' + lang.remove + '</a>' +
+          '<a href="#" data-action="delete_selected" data-id="single-admin" data-api-url="delete/admin" data-item="' + encodeURI(item.username) + '" class="btn btn-xs btn-danger"><i class="bi bi-trash"></i> ' + lang.remove + '</a>' +
           '</div>';
       });
     }
diff --git a/data/web/js/site/debug.js b/data/web/js/site/debug.js
index 246209ad..fa9ffbb5 100644
--- a/data/web/js/site/debug.js
+++ b/data/web/js/site/debug.js
@@ -710,7 +710,7 @@ jQuery(function($){
         }
         item.indicator = '<span style="border-right:6px solid #' + intToRGB(hashCode(item.rl_hash)) + ';padding-left:5px;">&nbsp;</span>';
         if (item.rl_hash != 'err') {
-          item.action = '<a href="#" data-action="delete_selected" data-id="single-hash" data-api-url="delete/rlhash" data-item="' + encodeURI(item.rl_hash) + '" class="btn btn-xs btn-danger"><i class="bi bi-recycle"></i> ' + lang.reset_limit + '</a>';
+          item.action = '<a href="#" data-action="delete_selected" data-id="single-hash" data-api-url="delete/rlhash" data-item="' + encodeURI(item.rl_hash) + '" class="btn btn-xs btn-danger"><i class="bi bi-trash"></i> ' + lang.reset_limit + '</a>';
         }
       });
     }
diff --git a/data/web/js/site/mailbox.js b/data/web/js/site/mailbox.js
index b7896f8d..14a217bc 100644
--- a/data/web/js/site/mailbox.js
+++ b/data/web/js/site/mailbox.js
@@ -128,7 +128,7 @@ $(document).ready(function() {
   // Log modal
   $('#dnsInfoModal').on('show.bs.modal', function(e) {
     var domain = $(e.relatedTarget).data('domain');
-    $('.dns-modal-body').html('<center><i class="bi bi-arrow-repeat icon-spin" style="font-size:18pt;margin:50px"></i></center>');
+    $('.dns-modal-body').html('<center><i class="bi bi-arrow-repeat icon-spin"></i></center>');
     $.ajax({
       url: '/inc/ajax/dns_diagnostics.php',
       data: { domain: domain },
@@ -286,7 +286,7 @@ jQuery(function($){
             item.action = '<div class="btn-group">';
             if (role == "admin") {
               item.action += '<a href="/edit/domain/' + encodeURIComponent(item.domain_name) + '" class="btn btn-xs btn-default"><i class="bi bi-pencil-fill"></i> ' + lang.edit + '</a>' +
-                '<a href="#" data-action="delete_selected" data-id="single-domain" data-api-url="delete/domain" data-item="' + encodeURIComponent(item.domain_name) + '" class="btn btn-xs btn-danger"><i class="bi bi-recycle"></i> ' + lang.remove + '</a>';
+                '<a href="#" data-action="delete_selected" data-id="single-domain" data-api-url="delete/domain" data-item="' + encodeURIComponent(item.domain_name) + '" class="btn btn-xs btn-danger"><i class="bi bi-trash"></i> ' + lang.remove + '</a>';
             }
             else {
               item.action += '<a href="/edit/domain/' + encodeURIComponent(item.domain_name) + '" class="btn btn-xs btn-default"><i class="bi bi-pencil-fill"></i> ' + lang.edit + '</a>';
@@ -432,7 +432,7 @@ jQuery(function($){
             if (acl_data.login_as === 1) {
             item.action = '<div class="btn-group">' +
               '<a href="/edit/mailbox/' + encodeURIComponent(item.username) + '" class="btn btn-xs btn-default"><i class="bi bi-pencil-fill"></i> ' + lang.edit + '</a>' +
-              '<a href="#" data-action="delete_selected" data-id="single-mailbox" data-api-url="delete/mailbox" data-item="' + encodeURIComponent(item.username) + '" class="btn btn-xs btn-danger"><i class="bi bi-recycle"></i> ' + lang.remove + '</a>' +
+              '<a href="#" data-action="delete_selected" data-id="single-mailbox" data-api-url="delete/mailbox" data-item="' + encodeURIComponent(item.username) + '" class="btn btn-xs btn-danger"><i class="bi bi-trash"></i> ' + lang.remove + '</a>' +
               '<a href="/index.php?duallogin=' + encodeURIComponent(item.username) + '" class="login_as btn btn-xs btn-success"><i class="bi bi-person-fill"></i> Login</a>';
               if (ALLOW_ADMIN_EMAIL_LOGIN) {
                 item.action += '<a href="/sogo-auth.php?login=' + encodeURIComponent(item.username) + '" class="login_as btn btn-xs btn-primary" target="_blank"><i class="bi bi-envelope-fill"></i> SOGo</a>';
@@ -442,7 +442,7 @@ jQuery(function($){
             else {
             item.action = '<div class="btn-group">' +
               '<a href="/edit/mailbox/' + encodeURIComponent(item.username) + '" class="btn btn-xs btn-default"><i class="bi bi-pencil-fill"></i> ' + lang.edit + '</a>' +
-              '<a href="#" data-action="delete_selected" data-id="single-mailbox" data-api-url="delete/mailbox" data-item="' + encodeURIComponent(item.username) + '" class="btn btn-xs btn-danger"><i class="bi bi-recycle"></i> ' + lang.remove + '</a>' +
+              '<a href="#" data-action="delete_selected" data-id="single-mailbox" data-api-url="delete/mailbox" data-item="' + encodeURIComponent(item.username) + '" class="btn btn-xs btn-danger"><i class="bi bi-trash"></i> ' + lang.remove + '</a>' +
               '</div>';
             }
             item.in_use = '<div class="progress">' +
@@ -519,7 +519,7 @@ jQuery(function($){
             }
             item.action = '<div class="btn-group">' +
               '<a href="/edit/resource/' + encodeURIComponent(item.name) + '" class="btn btn-xs btn-default"><i class="bi bi-pencil-fill"></i> ' + lang.edit + '</a>' +
-              '<a href="#" data-action="delete_selected" data-id="single-resource" data-api-url="delete/resource" data-item="' + item.name + '" class="btn btn-xs btn-danger"><i class="bi bi-recycle"></i> ' + lang.remove + '</a>' +
+              '<a href="#" data-action="delete_selected" data-id="single-resource" data-api-url="delete/resource" data-item="' + item.name + '" class="btn btn-xs btn-danger"><i class="bi bi-trash"></i> ' + lang.remove + '</a>' +
               '</div>';
             item.chkbox = '<input type="checkbox" data-id="resource" name="multi_select" value="' + encodeURIComponent(item.name) + '" />';
             item.name = escapeHtml(item.name);
@@ -585,7 +585,7 @@ jQuery(function($){
           $.each(data, function (i, item) {
             item.action = '<div class="btn-group">' +
               '<a href="/edit/bcc/' + item.id + '" class="btn btn-xs btn-default"><i class="bi bi-pencil-fill"></i> ' + lang.edit + '</a>' +
-              '<a href="#" data-action="delete_selected" data-id="single-bcc" data-api-url="delete/bcc" data-item="' + item.id + '" class="btn btn-xs btn-danger"><i class="bi bi-recycle"></i> ' + lang.remove + '</a>' +
+              '<a href="#" data-action="delete_selected" data-id="single-bcc" data-api-url="delete/bcc" data-item="' + item.id + '" class="btn btn-xs btn-danger"><i class="bi bi-trash"></i> ' + lang.remove + '</a>' +
               '</div>';
             item.chkbox = '<input type="checkbox" data-id="bcc" name="multi_select" value="' + item.id + '" />';
             item.local_dest = escapeHtml(item.local_dest);
@@ -655,7 +655,7 @@ jQuery(function($){
               item.recipient_map_new = escapeHtml(item.recipient_map_new);
               item.action = '<div class="btn-group">' +
                 '<a href="/edit/recipient_map/' + item.id + '" class="btn btn-xs btn-default"><i class="bi bi-pencil-fill"></i> ' + lang.edit + '</a>' +
-                '<a href="#" data-action="delete_selected" data-id="single-recipient_map" data-api-url="delete/recipient_map" data-item="' + item.id + '" class="btn btn-xs btn-danger"><i class="bi bi-recycle"></i> ' + lang.remove + '</a>' +
+                '<a href="#" data-action="delete_selected" data-id="single-recipient_map" data-api-url="delete/recipient_map" data-item="' + item.id + '" class="btn btn-xs btn-danger"><i class="bi bi-trash"></i> ' + lang.remove + '</a>' +
                 '</div>';
               item.chkbox = '<input type="checkbox" data-id="recipient_map" name="multi_select" value="' + item.id + '" />';
             });
@@ -725,7 +725,7 @@ jQuery(function($){
               }
               item.action = '<div class="btn-group">' +
                 '<a href="/edit/tls_policy_map/' + item.id + '" class="btn btn-xs btn-default"><i class="bi bi-pencil-fill"></i> ' + lang.edit + '</a>' +
-                '<a href="#" data-action="delete_selected" data-id="single-tls-policy-map" data-api-url="delete/tls-policy-map" data-item="' + item.id + '" class="btn btn-xs btn-danger"><i class="bi bi-recycle"></i> ' + lang.remove + '</a>' +
+                '<a href="#" data-action="delete_selected" data-id="single-tls-policy-map" data-api-url="delete/tls-policy-map" data-item="' + item.id + '" class="btn btn-xs btn-danger"><i class="bi bi-trash"></i> ' + lang.remove + '</a>' +
                 '</div>';
               item.chkbox = '<input type="checkbox" data-id="tls-policy-map" name="multi_select" value="' + item.id + '" />';
             });
@@ -790,7 +790,7 @@ jQuery(function($){
           $.each(data, function (i, item) {
             item.action = '<div class="btn-group">' +
               '<a href="/edit/alias/' + encodeURIComponent(item.id) + '" class="btn btn-xs btn-default"><i class="bi bi-pencil-fill"></i> ' + lang.edit + '</a>' +
-              '<a href="#" data-action="delete_selected" data-id="single-alias" data-api-url="delete/alias" data-item="' + encodeURIComponent(item.id) + '" class="btn btn-xs btn-danger"><i class="bi bi-recycle"></i> ' + lang.remove + '</a>' +
+              '<a href="#" data-action="delete_selected" data-id="single-alias" data-api-url="delete/alias" data-item="' + encodeURIComponent(item.id) + '" class="btn btn-xs btn-danger"><i class="bi bi-trash"></i> ' + lang.remove + '</a>' +
               '</div>';
             item.chkbox = '<input type="checkbox" data-id="alias" name="multi_select" value="' + encodeURIComponent(item.id) + '" />';
             item.goto = escapeHtml(item.goto.replace(/,/g, " "));
@@ -813,7 +813,7 @@ jQuery(function($){
               item.address = escapeHtml(item.address);
             }
             if (item.goto == "null@localhost") {
-              item.goto = '⤷ <i class="bi bi-recycle" style="font-size:12px"></i>';
+              item.goto = '⤷ <i class="bi bi-trash" style="font-size:12px"></i>';
             }
             else if (item.goto == "spam@localhost") {
               item.goto = '<span class="label label-danger">Learn as spam</span>';
@@ -884,7 +884,7 @@ jQuery(function($){
           $.each(data, function (i, item) {
             item.action = '<div class="btn-group">' +
               '<a href="/edit/aliasdomain/' + encodeURIComponent(item.alias_domain) + '" class="btn btn-xs btn-default"><i class="bi bi-pencil-fill"></i> ' + lang.edit + '</a>' +
-              '<a href="#" data-action="delete_selected" data-id="single-alias-domain" data-api-url="delete/alias-domain" data-item="' + encodeURIComponent(item.alias_domain) + '" class="btn btn-xs btn-danger"><i class="bi bi-recycle"></i> ' + lang.remove + '</a>' +
+              '<a href="#" data-action="delete_selected" data-id="single-alias-domain" data-api-url="delete/alias-domain" data-item="' + encodeURIComponent(item.alias_domain) + '" class="btn btn-xs btn-danger"><i class="bi bi-trash"></i> ' + lang.remove + '</a>' +
               '<a href="#dnsInfoModal" class="btn btn-xs btn-info" data-toggle="modal" data-domain="' + encodeURIComponent(item.alias_domain) + '"><i class="bi bi-question-lg"></i> DNS</a></div>' +
               '</div>';
             item.chkbox = '<input type="checkbox" data-id="alias-domain" name="multi_select" value="' + encodeURIComponent(item.alias_domain) + '" />';
@@ -964,7 +964,7 @@ jQuery(function($){
             item.server_w_port = escapeHtml(item.user1) + '@' + item.host1 + ':' + item.port1;
             item.action = '<div class="btn-group">' +
               '<a href="/edit/syncjob/' + item.id + '" class="btn btn-xs btn-default"><i class="bi bi-pencil-fill"></i> ' + lang.edit + '</a>' +
-              '<a href="#" data-action="delete_selected" data-id="single-syncjob" data-api-url="delete/syncjob" data-item="' + item.id + '" class="btn btn-xs btn-danger"><i class="bi bi-recycle"></i> ' + lang.remove + '</a>' +
+              '<a href="#" data-action="delete_selected" data-id="single-syncjob" data-api-url="delete/syncjob" data-item="' + item.id + '" class="btn btn-xs btn-danger"><i class="bi bi-trash"></i> ' + lang.remove + '</a>' +
               '</div>';
             item.chkbox = '<input type="checkbox" data-id="syncjob" name="multi_select" value="' + item.id + '" />';
             if (item.is_running == 1) {
@@ -1042,7 +1042,7 @@ jQuery(function($){
             item.filter_type = '<div class="label label-default">' + item.filter_type.charAt(0).toUpperCase() + item.filter_type.slice(1).toLowerCase() + '</div>'
             item.action = '<div class="btn-group">' +
               '<a href="/edit/filter/' + item.id + '" class="btn btn-xs btn-default"><i class="bi bi-pencil-fill"></i> ' + lang.edit + '</a>' +
-              '<a href="#" data-action="delete_selected" data-id="single-filter" data-api-url="delete/filter" data-item="' + encodeURIComponent(item.id) + '" class="btn btn-xs btn-danger"><i class="bi bi-recycle"></i> ' + lang.remove + '</a>' +
+              '<a href="#" data-action="delete_selected" data-id="single-filter" data-api-url="delete/filter" data-item="' + encodeURIComponent(item.id) + '" class="btn btn-xs btn-danger"><i class="bi bi-trash"></i> ' + lang.remove + '</a>' +
               '</div>';
             item.chkbox = '<input type="checkbox" data-id="filter_item" name="multi_select" value="' + item.id + '" />'
           });
diff --git a/data/web/js/site/quarantine.js b/data/web/js/site/quarantine.js
index e98fb820..748f43d6 100644
--- a/data/web/js/site/quarantine.js
+++ b/data/web/js/site/quarantine.js
@@ -97,7 +97,7 @@ jQuery(function($){
             if (acl_data.login_as === 1) {
             item.action = '<div class="btn-group">' +
               '<a href="#" data-item="' + encodeURI(item.id) + '" class="btn btn-xs btn-info show_qid_info"><i class="bi bi-box-arrow-up-right"></i> ' + lang.show_item + '</a>' +
-              '<a href="#" data-action="delete_selected" data-id="del-single-qitem" data-api-url="delete/qitem" data-item="' + encodeURI(item.id) + '" class="btn btn-xs btn-danger"><i class="bi bi-recycle"></i> ' + lang.remove + '</a>' +
+              '<a href="#" data-action="delete_selected" data-id="del-single-qitem" data-api-url="delete/qitem" data-item="' + encodeURI(item.id) + '" class="btn btn-xs btn-danger"><i class="bi bi-trash"></i> ' + lang.remove + '</a>' +
               '</div>';
             }
             else {
diff --git a/data/web/js/site/user.js b/data/web/js/site/user.js
index 6bfd982d..c2ff3897 100644
--- a/data/web/js/site/user.js
+++ b/data/web/js/site/user.js
@@ -94,7 +94,7 @@ jQuery(function($){
           $.each(data, function (i, item) {
             if (acl_data.spam_alias === 1) {
               item.action = '<div class="btn-group">' +
-                '<a href="#" data-action="delete_selected" data-id="single-tla" data-api-url="delete/time_limited_alias" data-item="' + encodeURIComponent(item.address) + '" class="btn btn-xs btn-danger"><i class="bi bi-recycle"></i> ' + lang.remove + '</a>' +
+                '<a href="#" data-action="delete_selected" data-id="single-tla" data-api-url="delete/time_limited_alias" data-item="' + encodeURIComponent(item.address) + '" class="btn btn-xs btn-danger"><i class="bi bi-trash"></i> ' + lang.remove + '</a>' +
                 '</div>';
               item.chkbox = '<input type="checkbox" data-id="tla" name="multi_select" value="' + encodeURIComponent(item.address) + '" />';
               item.address = escapeHtml(item.address);
@@ -155,7 +155,7 @@ jQuery(function($){
             if (acl_data.syncjobs === 1) {
               item.action = '<div class="btn-group">' +
                 '<a href="/edit/syncjob/' + item.id + '" class="btn btn-xs btn-default"><i class="bi bi-pencil-fill"></i> ' + lang.edit + '</a>' +
-                '<a href="#" data-action="delete_selected" data-id="single-syncjob" data-api-url="delete/syncjob" data-item="' + item.id + '" class="btn btn-xs btn-danger"><i class="bi bi-recycle"></i> ' + lang.remove + '</a>' +
+                '<a href="#" data-action="delete_selected" data-id="single-syncjob" data-api-url="delete/syncjob" data-item="' + item.id + '" class="btn btn-xs btn-danger"><i class="bi bi-trash"></i> ' + lang.remove + '</a>' +
                 '</div>';
               item.chkbox = '<input type="checkbox" data-id="syncjob" name="multi_select" value="' + item.id + '" />';
             }
@@ -209,7 +209,7 @@ jQuery(function($){
             if (acl_data.app_passwds === 1) {
               item.action = '<div class="btn-group">' +
                 '<a href="/edit/app-passwd/' + item.id + '" class="btn btn-xs btn-default"><i class="bi bi-pencil-fill"></i> ' + lang.edit + '</a>' +
-                '<a href="#" data-action="delete_selected" data-id="single-apppasswd" data-api-url="delete/app-passwd" data-item="' + item.id + '" class="btn btn-xs btn-danger"><i class="bi bi-recycle"></i> ' + lang.remove + '</a>' +
+                '<a href="#" data-action="delete_selected" data-id="single-apppasswd" data-api-url="delete/app-passwd" data-item="' + item.id + '" class="btn btn-xs btn-danger"><i class="bi bi-trash"></i> ' + lang.remove + '</a>' +
                 '</div>';
               item.chkbox = '<input type="checkbox" data-id="apppasswd" name="multi_select" value="' + item.id + '" />';
             }
diff --git a/data/web/lang/lang.cs.json b/data/web/lang/lang.cs.json
index c11937f2..7c807155 100644
--- a/data/web/lang/lang.cs.json
+++ b/data/web/lang/lang.cs.json
@@ -531,7 +531,7 @@
         "save": "Uložit změny",
         "scope": "Rozsah",
         "sender_acl": "Povolit odesílání jako",
-        "sender_acl_disabled": "↳ <span class=\"label label-danger\">Kontrola odesílatele vypnuta</span>",
+        "sender_acl_disabled": "<span class=\"label label-danger\">Kontrola odesílatele vypnuta</span>",
         "sender_acl_info": "Má-li uživatel schránky A dovoleno odesílat jako uživatel schránky B, nezobrazuje se adresa odesílatele B v seznamu \"Od\" v SOGo automaticky.<br>\r\n  Uživatel schránky A musí nejdříve v SOGo vytvořit pověření, jež umožní uživateli B vybrat svou adresu jako odesílatele. Tento mechanismus neplatí pro aliasy.",
         "sieve_desc": "Krátký popis",
         "sieve_type": "Typ filtru",
diff --git a/data/web/lang/lang.da.json b/data/web/lang/lang.da.json
index 797b9dd4..3b467a35 100644
--- a/data/web/lang/lang.da.json
+++ b/data/web/lang/lang.da.json
@@ -552,7 +552,7 @@
         "save": "Gem ændringer",
         "scope": "Anvendelsesområde",
         "sender_acl": "Tillad at sende som",
-        "sender_acl_disabled": "↳ <span class=\"label label-danger\">Afsenderkontrol er deaktiveret</span>",
+        "sender_acl_disabled": "<span class=\"label label-danger\">Afsenderkontrol er deaktiveret</span>",
         "sender_acl_info": "Hvis postkassebruger A har tilladelse til at sende som postkassebruger B, vises afsenderadressen ikke automatisk som valgbar \"from\" felt i SOGo.<br>\r\n  Postkassebruger B skal oprette en delegation i SOGo for at tillade postkassebruger A at vælge deres adresse som afsender. For at delegere en postkasse i SOGo skal du bruge menuen (tre prikker) til højre for dit postkassens navn øverst til venstre, mens du er i postvisningen. Denne adfærd gælder ikke for aliasadresser.",
         "sieve_desc": "Kort beskrivelse",
         "sieve_type": "Filtertype",
diff --git a/data/web/lang/lang.de.json b/data/web/lang/lang.de.json
index f3785cbe..0a16bc31 100644
--- a/data/web/lang/lang.de.json
+++ b/data/web/lang/lang.de.json
@@ -27,6 +27,8 @@
         "tls_policy": "Verschlüsselungsrichtlinie",
         "unlimited_quota": "Unendliche Quota für Mailboxen",
         "domain_desc": "Domainbeschreibung ändern",
+        "mailbox_relayhost": "Relayhost für eine Mailbox setzen",
+        "domain_relayhost": "Relayhost für eine Domain setzen",
         "xmpp_admin": "Benutzer zum XMPP-Administrator ernennen",
         "xmpp_domain_access": "XMPP-Zugang einer Domain konfigurieren",
         "xmpp_mailbox_access": "XMPP-Zugang eines Benutzers einstellen",
@@ -554,6 +556,7 @@
         "inactive": "Inaktiv",
         "kind": "Art",
         "mailbox": "Mailbox bearbeiten",
+        "mailbox_relayhost_info": "Wird auf eine Mailbox und direkte Alias-Adressen angewendet. Überschreibt die Einstellung einer Domain.",
         "mailbox_quota_def": "Standard-Quota einer Mailbox",
         "max_aliases": "Max. Aliasse",
         "max_mailboxes": "Max. Mailboxanzahl",
@@ -594,7 +597,7 @@
         "save": "Änderungen speichern",
         "scope": "Scope",
         "sender_acl": "Darf Nachrichten versenden als",
-        "sender_acl_disabled": "↳ <span class=\"label label-danger\">Absenderprüfung deaktiviert</span>",
+        "sender_acl_disabled": "<span class=\"label label-danger\">Absenderprüfung deaktiviert</span>",
         "sender_acl_info": "Wird einem Mailbox-Benutzer A der Versand als Mailbox-Benutzer B gestattet, so erscheint der Absender <b>nicht</b> automatisch in SOGo zur Auswahl.<br>\r\n  In SOGo muss zusätzlich eine Delegation eingerichtet werden. Dieses Verhalten trifft nicht auf Alias-Adressen zu.",
         "sieve_desc": "Kurze Beschreibung",
         "sieve_type": "Filtertyp",
@@ -1020,6 +1023,7 @@
         "eas_reset_help": "In vielen Fällen kann ein ActiveSync-Profil durch das Zurücksetzen des Caches repariert werden.<br><b>Vorsicht:</b> Alle Elemente werden erneut heruntergeladen!",
         "eas_reset_now": "Jetzt zurücksetzen",
         "edit": "Bearbeiten",
+        "empty": "Keine Einträge vorhanden",
         "email": "E-Mail",
         "email_and_dav": "E-Mail, Kalender und Adressbücher",
         "encryption": "Verschlüsselung",
diff --git a/data/web/lang/lang.en.json b/data/web/lang/lang.en.json
index 08ce6fe8..780d6b3a 100644
--- a/data/web/lang/lang.en.json
+++ b/data/web/lang/lang.en.json
@@ -27,6 +27,8 @@
         "tls_policy": "TLS policy",
         "unlimited_quota": "Unlimited quota for mailboxes",
         "domain_desc": "Change domain description",
+        "mailbox_relayhost": "Change relayhost for a mailbox",
+        "domain_relayhost": "Change relayhost for a domain",
         "xmpp_admin": "Promote XMPP user to administrator",
         "xmpp_domain_access": "Configure XMPP domain access",
         "xmpp_mailbox_access": "Configure XMPP user access",
@@ -553,6 +555,7 @@
         "kind": "Kind",
         "mailbox": "Edit mailbox",
         "mailbox_quota_def": "Default mailbox quota",
+        "mailbox_relayhost_info": "Applied to the mailbox and direct aliases only, does override a domain relayhost.",
         "max_aliases": "Max. aliases",
         "max_mailboxes": "Max. possible mailboxes",
         "max_quota": "Max. quota per mailbox (MiB)",
@@ -592,7 +595,7 @@
         "save": "Save changes",
         "scope": "Scope",
         "sender_acl": "Allow to send as",
-        "sender_acl_disabled": "↳ <span class=\"label label-danger\">Sender check is disabled</span>",
+        "sender_acl_disabled": "<span class=\"label label-danger\">Sender check is disabled</span>",
         "sender_acl_info": "If mailbox user A is allowed to send as mailbox user B, the sender address is not automatically displayed as selectable \"from\" field in SOGo.<br>\r\n  Mailbox user B needs to create a delegation in SOGo to allow mailbox user A to select their address as sender. To delegate a mailbox in SOGo, use the menu (three dots) to the right of your mailbox name in the upper left while in mail view. This behaviour does not apply to alias addresses.",
         "sieve_desc": "Short description",
         "sieve_type": "Filter type",
@@ -1018,6 +1021,7 @@
         "eas_reset_help": "In many cases a device cache reset will help to recover a broken ActiveSync profile.<br><b>Attention:</b> All elements will be redownloaded!",
         "eas_reset_now": "Reset now",
         "edit": "Edit",
+        "empty": "No results",
         "email": "Email",
         "email_and_dav": "Email, calendars and contacts",
         "encryption": "Encryption",
diff --git a/data/web/lang/lang.fi.json b/data/web/lang/lang.fi.json
index 25bfe4dc..3410ccc7 100644
--- a/data/web/lang/lang.fi.json
+++ b/data/web/lang/lang.fi.json
@@ -468,7 +468,7 @@
         "save": "Tallenna muutokset",
         "scope": "Laajuus",
         "sender_acl": "Salli lähettää nimellä",
-        "sender_acl_disabled": "↳ <span class=\"label label-danger\">Lähettäjän tarkistus on poistettu käytöstä</span>",
+        "sender_acl_disabled": "<span class=\"label label-danger\">Lähettäjän tarkistus on poistettu käytöstä</span>",
         "sender_acl_info": "Jos postilaatikon käyttäjän A sallitaan lähettävän postilaatikon käyttäjäksi B, lähettäjän osoitetta ei näytetä automaattisesti valittavana \"alkaen\" -kentässä SOGossa.<br>\r\nSähkö postilaatikon käyttäjän A on luotava valtuutus SOGoon, jotta sähkö postilaatikon käyttäjä b voi valita osoitteen lähettäjäksi. Tämä käyttäytyminen ei koske alias-osoitteita",
         "sieve_desc": "Lyhyt kuvaus",
         "sieve_type": "Suodattimen tyyppi",
diff --git a/data/web/lang/lang.fr.json b/data/web/lang/lang.fr.json
index e8177ae8..07cd326c 100644
--- a/data/web/lang/lang.fr.json
+++ b/data/web/lang/lang.fr.json
@@ -578,7 +578,7 @@
 		"save": "Enregistrer les modifications",
 		"scope": "Portée",
 		"sender_acl": "Permettre d’envoyer comme",
-		"sender_acl_disabled": "↳ <span class=\"label label-danger\">Le contrôle de l’expéditeur est désactivé</span>",
+		"sender_acl_disabled": "<span class=\"label label-danger\">Le contrôle de l’expéditeur est désactivé</span>",
 		"sender_acl_info": "Si l’utilisateur de la boîte A est autorisé à envoyer en tant qu’utilisateur de la boîte B, l’adresse de l’expéditeur n’est pas automatiquement affichée comme sélectionnable du champ \"from\" dans SOGo.<br>\r\n  L’utilisateur B de la boîte doit créer une délégation dans Sogo pour permettre à l’utilisateur A de la boîte de sélectionner son adresse comme expéditeur. Pour déléguer une boîte dans Sogo, utilisez le menu (trois points) à droite du nom de votre boîte dans le coin supérieur gauche dans la vue de courrier. Ce comportement ne s’applique pas aux adresses alias.",
 		"sieve_desc": "Description courte",
 		"sieve_type": "Type de filtre",
diff --git a/data/web/lang/lang.ko.json b/data/web/lang/lang.ko.json
index 7e484fc9..cdd34b1b 100644
--- a/data/web/lang/lang.ko.json
+++ b/data/web/lang/lang.ko.json
@@ -534,7 +534,7 @@
         "save": "Save changes",
         "scope": "Scope",
         "sender_acl": "Allow to send as",
-        "sender_acl_disabled": "↳ <span class=\"label label-danger\">Sender check is disabled</span>",
+        "sender_acl_disabled": "<span class=\"label label-danger\">Sender check is disabled</span>",
         "sender_acl_info": "If mailbox user A is allowed to send as mailbox user B, the sender address is not automatically displayed as selectable \"from\" field in SOGo.<br>\r\n  Mailbox user B needs to create a delegation in SOGo to allow mailbox user A to select their address as sender. To delegate a mailbox in SOGo, use the menu (three dots) to the right of your mailbox name in the upper left while in mail view. This behaviour does not apply to alias addresses.",
         "sieve_desc": "Short description",
         "sieve_type": "Filter type",
diff --git a/data/web/lang/lang.nl.json b/data/web/lang/lang.nl.json
index 24487f91..03c55e9a 100644
--- a/data/web/lang/lang.nl.json
+++ b/data/web/lang/lang.nl.json
@@ -578,7 +578,7 @@
         "save": "Wijzigingen opslaan",
         "scope": "Scope",
         "sender_acl": "Sta toe om te verzenden als",
-        "sender_acl_disabled": "↳ <span class=\"label label-danger\">Verzendcontrole is uitgeschakeld</span>",
+        "sender_acl_disabled": "<span class=\"label label-danger\">Verzendcontrole is uitgeschakeld</span>",
         "sender_acl_info": "Wanneer mailboxgebruiker A toegestaan is te verzenden namens mailboxgebruiker B, zal het verzendadres niet automatisch worden weergegeven in het \"van\"-veld in SOGo. Mailboxgebruiker A dient hiervoor een aparte vermelding te maken in SOGo. Om een mailbox te delegeren in SOGo kan het menu (drie punten) aan de rechterkant van de naam van het mailbox linksboven worden gebruikt in de mailweergave. Dit is niet van toepassing op aliasadressen.",
         "sieve_desc": "Korte beschrijving",
         "sieve_type": "Filtertype",
@@ -1047,7 +1047,7 @@
         "running": "Wordt uitgevoerd",
         "save": "Sla wijzigingen op",
         "save_changes": "Wijzigingen opslaan",
-        "sender_acl_disabled": "↳ <span class=\"label label-danger\">Verzendcontrole is uitgeschakeld</span>",
+        "sender_acl_disabled": "<span class=\"label label-danger\">Verzendcontrole is uitgeschakeld</span>",
         "shared_aliases": "Gedeelde aliasadressen",
         "shared_aliases_desc": "Een gedeeld aliasadres wordt niet beïnvloed door gebruikersspecifieke instellingen. Een aangepast spamfilter kan eventueel worden ingesteld door een administrator.",
         "show_sieve_filters": "Toon actieve filters",
diff --git a/data/web/lang/lang.ro.json b/data/web/lang/lang.ro.json
index e2218f8a..58ca140e 100644
--- a/data/web/lang/lang.ro.json
+++ b/data/web/lang/lang.ro.json
@@ -587,7 +587,7 @@
         "save": "Salvează modificările",
         "scope": "Scop",
         "sender_acl": "Permite trimiterea ca",
-        "sender_acl_disabled": "↳ <span class=\"label label-danger\">Verificarea expeditorului este dezactivată</span>",
+        "sender_acl_disabled": "<span class=\"label label-danger\">Verificarea expeditorului este dezactivată</span>",
         "sender_acl_info": "Dacă utilizatorului A de căsuță poștală îi este permis să trimită ca utilizatorul B, adresa expeditorului nu este afișată automat ca fiind câmp selectabil \"de la\" în SOGo.<br>\r\n  Utilizatorul căsuței poștale B trebuie să creeze o delegație în SOGo pentru a permite utilizatorul cutiei poștale A să selecteze adresa ca expeditor. Pentru a delega o cutie poștală în SOGo, utilizați meniul (trei puncte) din dreapta numelui căsuței poștale în stânga sus, în timp ce vă aflați în vizualizarea e-mailului. Acest comportament nu se aplică adreselor alias.",
         "sieve_desc": "Descriere scurtă",
         "sieve_type": "Tip filtru",
diff --git a/data/web/lang/lang.sk.json b/data/web/lang/lang.sk.json
index 39df5723..f6926ebb 100644
--- a/data/web/lang/lang.sk.json
+++ b/data/web/lang/lang.sk.json
@@ -590,7 +590,7 @@
         "save": "Uložiť zmeny",
         "scope": "Rozsah",
         "sender_acl": "Povoliť odosielanie ako",
-        "sender_acl_disabled": "↳ <span class=\"label label-danger\">Kontrola odosielateľa vypnutá</span>",
+        "sender_acl_disabled": "<span class=\"label label-danger\">Kontrola odosielateľa vypnutá</span>",
         "sender_acl_info": "Ak poštový používateľ A má povolenie poslať ako poštový používateľ B, adresa odosielateľa nieje automaticky viditeľná ako voliteľné \"from\" pole v SOGo.<br>\r\n Poštový používateľ B potrebuje vytvoriť delegáciu v SOGo, aby bol schopný poštový používateľ A vybrať svoju adresu ako odosielateľ. Na delegovanie poštovej adresy v SOGo, použite menu (tri bodky) napravo vášho poštového mena v hornom ľavom rohu, v prehľade správ. Toto neplatí pre alias adresy.",
         "sieve_desc": "Krátky popis",
         "sieve_type": "Typ filtru",
diff --git a/data/web/lang/lang.sv.json b/data/web/lang/lang.sv.json
index 135f6c0f..3c139d79 100644
--- a/data/web/lang/lang.sv.json
+++ b/data/web/lang/lang.sv.json
@@ -592,7 +592,7 @@
         "save": "Spara ändringar",
         "scope": "Omfattning",
         "sender_acl": "Tillåt att skicka som",
-        "sender_acl_disabled": "↳ <span class=\"label label-danger\">Avsändarkontroll är avaktiverad</span>",
+        "sender_acl_disabled": "<span class=\"label label-danger\">Avsändarkontroll är avaktiverad</span>",
         "sender_acl_info": "Om användaren A tillåts skicka som användaren B visas inte avsändaradressen automatiskt i \"från\" fältet i SOGo.<br>\r\n  Användaren B måste skapa en delegation i SOGo för att låta användaren A välja Användaren B's adress som avsändare. För att delegera en postlåda i SOGo, gå till menyn (tre punkter) till höger om ditt namn uppe till vänster i postvyn. Detta gäller inte för aliasadresser.",
         "sieve_desc": "Kort beskrivning",
         "sieve_type": "Filtertyp",
diff --git a/data/web/lang/lang.zh.json b/data/web/lang/lang.zh.json
index 6adfb52c..8993cb9c 100644
--- a/data/web/lang/lang.zh.json
+++ b/data/web/lang/lang.zh.json
@@ -544,7 +544,7 @@
         "save": "保存更改",
         "scope": "范围",
         "sender_acl": "允许发送为",
-        "sender_acl_disabled": "↳ <span class=\"label label-danger\">发件人检查已关闭</span>",
+        "sender_acl_disabled": "<span class=\"label label-danger\">发件人检查已关闭</span>",
         "sender_acl_info": "如果允许邮箱用户A作为邮箱用户B发送邮件,发件人的地址不会在SOGo中\"来自\"区域自动地作为下拉可选项显示。<br>\r\n 邮箱用户B需要添加授权以允许邮箱用户A选择B的地址作为发件人;授权方法为,在SOGo中点击左上方邮箱地址右边的菜单按钮(三个点)并授权。",
         "sieve_desc": "简短描述",
         "sieve_type": "过滤器类型",
diff --git a/data/web/user.php b/data/web/user.php
index 7006aaaa..61a04be2 100644
--- a/data/web/user.php
+++ b/data/web/user.php
@@ -94,7 +94,7 @@ if (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == 'doma
                 <input type="hidden" name="unset_fido2_key" value="<?=$key_info['cid'];?>" />
                 <div class="btn-group">
                 <a href="#" class="btn btn-xs btn-default" data-cid="<?=$key_info['cid'];?>" data-subject="<?=base64_encode($key_info['subject']);?>" data-toggle="modal" data-target="#fido2ChangeFn"><i class="bi bi-pencil-fill"></i> <?=strtolower($lang['fido2']['rename']);?></a>
-                <a href="#" onClick='return confirm("<?=$lang['admin']['ays'];?>")?$(this).closest("form").submit():"";' class="btn btn-xs btn-danger"><i class="bi bi-recycle"></i> <?=strtolower($lang['admin']['remove']);?></a>
+                <a href="#" onClick='return confirm("<?=$lang['admin']['ays'];?>")?$(this).closest("form").submit():"";' class="btn btn-xs btn-danger"><i class="bi bi-trash"></i> <?=strtolower($lang['admin']['remove']);?></a>
                 </form>
                 </div>
               </td>
@@ -229,32 +229,33 @@ elseif (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == '
         }
         ?>
         <div class="row">
-          <div class="col-md-3 col-xs-5 text-right"><?=$lang['user']['direct_aliases'];?>:
+          <div class="col-md-3 col-xs-5 text-right"><i class="bi bi-pin-angle"></i> <?=$lang['user']['direct_aliases'];?>:
             <p class="small"><?=$lang['user']['direct_aliases_desc'];?></p>
           </div>
           <div class="col-md-9 col-xs-7">
           <?php
-          if ($user_get_alias_details['direct_aliases'] === false) {
-            echo '&#10008;';
+          if (empty($user_get_alias_details['direct_aliases'])) {
+            echo '<i class="bi bi-x-lg"></i>';
           }
           else {
             foreach (array_filter($user_get_alias_details['direct_aliases']) as $direct_alias => $direct_alias_meta) {
               (!empty($direct_alias_meta['public_comment'])) ?
-                printf('%s &mdash; <span class="bg-info">%s</span><br>', $direct_alias, $direct_alias_meta['public_comment']) :
+                printf('%s &mdash; <i class="bi bi-chat-left"></i> %s<br>', $direct_alias, $direct_alias_meta['public_comment']) :
                 printf('%s<br>', $direct_alias);
             }
           }
           ?>
           </div>
         </div>
+        <br>
         <div class="row">
-          <div class="col-md-3 col-xs-5 text-right"><?=$lang['user']['shared_aliases'];?>:
+          <div class="col-md-3 col-xs-5 text-right"><i class="bi bi-share"></i> <?=$lang['user']['shared_aliases'];?>:
             <p class="small"><?=$lang['user']['shared_aliases_desc'];?></p>
           </div>
           <div class="col-md-9 col-xs-7">
           <?php
-          if ($user_get_alias_details['shared_aliases'] === false) {
-            echo '&#10008;';
+          if (empty($user_get_alias_details['shared_aliases'])) {
+            echo '<i class="bi bi-x-lg"></i>';
           }
           else {
             foreach (array_filter($user_get_alias_details['shared_aliases']) as $shared_alias => $shared_alias_meta) {
@@ -271,19 +272,19 @@ elseif (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == '
         <div class="row">
           <div class="col-md-3 col-xs-5 text-right"><?=$lang['user']['aliases_also_send_as'];?>:</div>
           <div class="col-md-9 col-xs-7">
-          <p><?=($user_get_alias_details['aliases_also_send_as'] == '*') ? $lang['user']['sender_acl_disabled'] : $user_get_alias_details['aliases_also_send_as'];?></p>
+          <p><?=($user_get_alias_details['aliases_also_send_as'] == '*') ? $lang['user']['sender_acl_disabled'] : (empty($user_get_alias_details['aliases_also_send_as'])) ? '<i class="bi bi-x-lg"></i>' : '' ;?></p>
           </div>
         </div>
         <div class="row">
           <div class="col-md-3 col-xs-5 text-right"><?=$lang['user']['aliases_send_as_all'];?>:</div>
           <div class="col-md-9 col-xs-7">
-          <p><?=$user_get_alias_details['aliases_send_as_all'];?></p>
+          <p><?=(empty($user_get_alias_details['aliases_send_as_all'])) ? '<i class="bi bi-x-lg"></i>' : '' ;?></p>
           </div>
         </div>
         <div class="row">
           <div class="col-md-3 col-xs-5 text-right"><?=$lang['user']['is_catch_all'];?>:</div>
           <div class="col-md-9 col-xs-7">
-          <p><?=$user_get_alias_details['is_catch_all'];?></p>
+          <p><?=(empty($user_get_alias_details['is_catch_all'])) ? '<i class="bi bi-x-lg"></i>' : '' ;?></p>
           </div>
         </div>
         <hr>
@@ -648,7 +649,7 @@ elseif (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == '
           <div class="btn-group" data-acl="<?=$_SESSION['acl']['pushover'];?>">
               <a class="btn btn-sm btn-default" data-action="edit_selected" data-id="pushover" data-item="<?=htmlspecialchars($username);?>" data-api-url='edit/pushover' data-api-attr='{}' href="#"><?=$lang['user']['save'];?></a>
               <a class="btn btn-sm btn-default" data-action="edit_selected" data-id="pushover-test" data-item="<?=htmlspecialchars($username);?>" data-api-url='edit/pushover-test' data-api-attr='{}' href="#"><i class="bi bi-check-all"></i> <?=$lang['user']['pushover_verify'];?></a>
-              <a id="pushover_delete" class="btn btn-sm btn-danger" data-action="edit_selected" data-id="pushover-delete" data-item="<?=htmlspecialchars($username);?>" data-api-url='edit/pushover' data-api-attr='{"delete":"true"}' href="#"><i class="bi bi-recycle"></i> <?=$lang['user']['remove'];?></a>
+              <a id="pushover_delete" class="btn btn-sm btn-danger" data-action="edit_selected" data-id="pushover-delete" data-item="<?=htmlspecialchars($username);?>" data-api-url='edit/pushover' data-api-attr='{"delete":"true"}' href="#"><i class="bi bi-trash"></i> <?=$lang['user']['remove'];?></a>
           </div>
         </div>
       </div>
diff --git a/docker-compose.yml b/docker-compose.yml
index 44733122..2e984e0a 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -290,7 +290,7 @@ services:
             - dovecot
 
     postfix-mailcow:
-      image: mailcow/postfix:1.61
+      image: mailcow/postfix:1.62
       depends_on:
         - mysql-mailcow
       volumes: