diff --git a/pom.xml b/pom.xml
index 757e9ef63b1b0d96b58d20e9a67d81b93553deda..ad95a1c7a9c4cb8db6751d5328442534ceefbc48 100644
--- a/pom.xml
+++ b/pom.xml
@@ -41,6 +41,10 @@
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter-data-jpa</artifactId>
     </dependency>
+    <dependency>
+      <groupId>org.springframework.boot</groupId>
+      <artifactId>spring-boot-starter-thymeleaf</artifactId>
+    </dependency>
     <dependency>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter-mail</artifactId>
diff --git a/src/main/java/org/openepics/names/service/DeviceGroupService.java b/src/main/java/org/openepics/names/service/DeviceGroupService.java
index aae236b4221508b55e3f389f5d03183d176603aa..0a334e1a5b6c14b6587cac00f490f60ea2ceb899 100644
--- a/src/main/java/org/openepics/names/service/DeviceGroupService.java
+++ b/src/main/java/org/openepics/names/service/DeviceGroupService.java
@@ -30,7 +30,6 @@ import org.openepics.names.repository.model.DeviceGroup;
 import org.openepics.names.repository.model.DeviceType;
 import org.openepics.names.rest.beans.Status;
 import org.openepics.names.rest.beans.Type;
-import org.openepics.names.rest.beans.element.StructureElement;
 import org.openepics.names.rest.beans.element.StructureElementCommand;
 import org.openepics.names.util.HolderSystemDeviceStructure;
 import org.openepics.names.util.StructureChoice;
@@ -38,6 +37,7 @@ import org.openepics.names.util.StructureCommand;
 import org.openepics.names.util.StructureElementUtil;
 import org.openepics.names.util.StructureUtil;
 import org.openepics.names.util.ValidateUtil;
+import org.openepics.names.util.notification.NotificationUtil;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Propagation;
@@ -78,16 +78,16 @@ public class DeviceGroupService {
     }
 
     @Transactional(propagation = Propagation.MANDATORY)
-    public StructureElement deleteStructure(StructureElementCommand structureElement,
-            Date requested, String requestedBy) {
+    public StructureElementNotification deleteStructure(StructureElementCommand structureElement,
+            Date requested, String requestedBy,
+            HolderSystemDeviceStructure holder) {
         // validation outside method
         // transaction
         //     do
         //         find
         //         create structure to pending, not latest, deleted, with data
-        //         possibly validate that deleted
         //     return
-        //         structure element for deleted structure
+        //         structure element for deleted structure + notification
 
         String uuid = structureElement.getUuid().toString();
 
@@ -105,11 +105,13 @@ public class DeviceGroupService {
                 requested, requestedBy, structureElement.getComment());
         deviceGroupRepository.createDeviceGroup(deviceGroup);
 
-        return StructureElementUtil.getStructureElementRequested(deviceGroup);
+        return new StructureElementNotification(
+                StructureElementUtil.getStructureElementRequested(deviceGroup),
+                NotificationUtil.prepareNotification(Type.DEVICEGROUP, StructureCommand.DELETE, null, null, deviceGroup, holder));
     }
 
     @Transactional(propagation = Propagation.MANDATORY)
-    public StructureElement approveStructure(StructureElementCommand structureElement,
+    public StructureElementNotification approveStructure(StructureElementCommand structureElement,
             Date processed, String processedBy,
             HolderSystemDeviceStructure holder) {
         // validation outside method
@@ -119,7 +121,6 @@ public class DeviceGroupService {
         //         update not latest
         //         find
         //         approve - update structure to status APPROVED, latest to true
-        //         possibly validate that approved
         //         additional
         //             find out previous to find out what is approved - create update delete
         //             approve create
@@ -136,7 +137,7 @@ public class DeviceGroupService {
         //                 keep related names - legacy names
         //                 delete sub structures
         //     return
-        //         structure element for approved structure
+        //         structure element for approved structure + notification
 
         String uuid = structureElement.getUuid().toString();
         String processedComment = structureElement.getComment();
@@ -164,13 +165,13 @@ public class DeviceGroupService {
         deviceGroup.setLatest(Boolean.TRUE);
         deviceGroupRepository.updateDeviceGroup(deviceGroup);
 
-        // additional
-        // find out previous
+        // previous
         List<DeviceGroup> previouses = iDeviceGroupRepository.findPreviousByUuidAndId(uuid, deviceGroup.getId());
         StructureCommand structureCommandCUD = StructureUtil.getStructureCommandCUD(previouses, deviceGroup);
+        DeviceGroup previous = previouses != null && !previouses.isEmpty() ? previouses.get(0) : null;
         LOGGER.log(Level.FINE, "approveStructure, structureCommandCUD:        {0}", structureCommandCUD);
 
-        // approve delete
+        // additional
         if (StructureCommand.DELETE.equals(structureCommandCUD)) {
             // not delete names - legacy names
             // delete sub structures - delete, approve
@@ -180,7 +181,7 @@ public class DeviceGroupService {
                 structureElementCommands.add(new StructureElementCommand(deviceType.getUuid(), Type.DEVICETYPE, null, null, null, null, StructuresService.DELETE_AFTER_APPROVE_STRUCTURE_CHANGE));
             }
             for (StructureElementCommand structureElementCommand : structureElementCommands) {
-                deviceTypeService.deleteStructure(structureElementCommand, processed, processedBy);
+                deviceTypeService.deleteStructure(structureElementCommand, processed, processedBy, holder);
             }
             for (StructureElementCommand structureElementCommand : structureElementCommands) {
                 deviceTypeService.approveStructure(structureElementCommand, processed, processedBy, holder);
@@ -188,7 +189,9 @@ public class DeviceGroupService {
         }
 
         LOGGER.log(Level.FINE, "approveStructure, structureElement:           {0}", structureElement);
-        return StructureElementUtil.getStructureElementProcessed(deviceGroup, holder, StructureChoice.STRUCTURE);
+        return new StructureElementNotification(
+                StructureElementUtil.getStructureElementProcessed(deviceGroup, holder, StructureChoice.STRUCTURE),
+                NotificationUtil.prepareNotification(Type.DEVICEGROUP, structureCommandCUD, StructureCommand.APPROVE, previous, deviceGroup, holder));
     }
 
 }
diff --git a/src/main/java/org/openepics/names/service/DeviceTypeService.java b/src/main/java/org/openepics/names/service/DeviceTypeService.java
index 10cab786a79884d4e23f701d4b28d20a2e84365f..ff73eba5e79c2f97ce5b540122aee505eb496291 100644
--- a/src/main/java/org/openepics/names/service/DeviceTypeService.java
+++ b/src/main/java/org/openepics/names/service/DeviceTypeService.java
@@ -27,7 +27,7 @@ import org.openepics.names.repository.DeviceTypeRepository;
 import org.openepics.names.repository.IDeviceTypeRepository;
 import org.openepics.names.repository.model.DeviceType;
 import org.openepics.names.rest.beans.Status;
-import org.openepics.names.rest.beans.element.StructureElement;
+import org.openepics.names.rest.beans.Type;
 import org.openepics.names.rest.beans.element.StructureElementCommand;
 import org.openepics.names.util.HolderSystemDeviceStructure;
 import org.openepics.names.util.StructureChoice;
@@ -35,6 +35,7 @@ import org.openepics.names.util.StructureCommand;
 import org.openepics.names.util.StructureElementUtil;
 import org.openepics.names.util.StructureUtil;
 import org.openepics.names.util.ValidateUtil;
+import org.openepics.names.util.notification.NotificationUtil;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Propagation;
@@ -70,16 +71,16 @@ public class DeviceTypeService {
     }
 
     @Transactional(propagation = Propagation.MANDATORY)
-    public StructureElement deleteStructure(StructureElementCommand structureElement,
-            Date requested, String requestedBy) {
+    public StructureElementNotification deleteStructure(StructureElementCommand structureElement,
+            Date requested, String requestedBy,
+            HolderSystemDeviceStructure holder) {
         // validation outside method
         // transaction
         //     do
         //         find
         //         create structure to pending, not latest, deleted, with data
-        //         possibly validate that deleted
         //     return
-        //         structure element for deleted structure
+        //         structure element for deleted structure + notification
 
         String uuid = structureElement.getUuid().toString();
 
@@ -97,11 +98,13 @@ public class DeviceTypeService {
                 requested, requestedBy, structureElement.getComment());
         deviceTypeRepository.createDeviceType(deviceType);
 
-        return StructureElementUtil.getStructureElementRequested(deviceType);
+        return new StructureElementNotification(
+                StructureElementUtil.getStructureElementRequested(deviceType),
+                NotificationUtil.prepareNotification(Type.DEVICETYPE, StructureCommand.DELETE, null, null, deviceType, holder));
     }
 
     @Transactional(propagation = Propagation.MANDATORY)
-    public StructureElement approveStructure(StructureElementCommand structureElement,
+    public StructureElementNotification approveStructure(StructureElementCommand structureElement,
             Date processed, String processedBy,
             HolderSystemDeviceStructure holder) {
         // validation outside method
@@ -111,7 +114,6 @@ public class DeviceTypeService {
         //         update not latest
         //         find
         //         approve - update structure to status APPROVED, latest to true
-        //         possibly validate that approved
         //         additional
         //             find out previous to find out what is approved - create update delete
         //             approve create
@@ -128,7 +130,7 @@ public class DeviceTypeService {
         //                 keep related names - legacy names
         //                 delete sub structures
         //     return
-        //         structure element for approved structure
+        //         structure element for approved structure + notification
 
         String uuid = structureElement.getUuid().toString();
         String processedComment = structureElement.getComment();
@@ -156,15 +158,13 @@ public class DeviceTypeService {
         deviceType.setLatest(Boolean.TRUE);
         deviceTypeRepository.updateDeviceType(deviceType);
 
-        // additional
-        // find out previous
+        // previous
         List<DeviceType> previouses = iDeviceTypeRepository.findPreviousByUuidAndId(uuid, deviceType.getId());
         StructureCommand structureCommandCUD = StructureUtil.getStructureCommandCUD(previouses, deviceType);
         DeviceType previous = previouses != null && !previouses.isEmpty() ? previouses.get(0) : null;
         LOGGER.log(Level.FINE, "approveStructure, structureCommandCUD:        {0}", structureCommandCUD);
 
-        // approve update
-        // approve delete
+        // additional
         if (StructureCommand.UPDATE.equals(structureCommandCUD)) {
             namesService.updateNames(previous, deviceType);
         } else if (StructureCommand.DELETE.equals(structureCommandCUD)) {
@@ -172,7 +172,9 @@ public class DeviceTypeService {
         }
 
         LOGGER.log(Level.FINE, "approveStructure, structureElement:           {0}", structureElement);
-        return StructureElementUtil.getStructureElementProcessed(deviceType, holder, StructureChoice.STRUCTURE);
+        return new StructureElementNotification(
+                StructureElementUtil.getStructureElementProcessed(deviceType, holder, StructureChoice.STRUCTURE),
+                NotificationUtil.prepareNotification(Type.DEVICETYPE, structureCommandCUD, StructureCommand.APPROVE, previous, deviceType, holder));
     }
 
 }
diff --git a/src/main/java/org/openepics/names/service/DisciplineService.java b/src/main/java/org/openepics/names/service/DisciplineService.java
index cccf5e8880a9e1326ae2ab8c4e21ca4c1783436c..a1e4762a5ef24ea4691c83c7e1bbd14187e9713b 100644
--- a/src/main/java/org/openepics/names/service/DisciplineService.java
+++ b/src/main/java/org/openepics/names/service/DisciplineService.java
@@ -30,7 +30,6 @@ import org.openepics.names.repository.model.DeviceGroup;
 import org.openepics.names.repository.model.Discipline;
 import org.openepics.names.rest.beans.Status;
 import org.openepics.names.rest.beans.Type;
-import org.openepics.names.rest.beans.element.StructureElement;
 import org.openepics.names.rest.beans.element.StructureElementCommand;
 import org.openepics.names.util.HolderSystemDeviceStructure;
 import org.openepics.names.util.StructureChoice;
@@ -38,6 +37,7 @@ import org.openepics.names.util.StructureCommand;
 import org.openepics.names.util.StructureElementUtil;
 import org.openepics.names.util.StructureUtil;
 import org.openepics.names.util.ValidateUtil;
+import org.openepics.names.util.notification.NotificationUtil;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Propagation;
@@ -81,16 +81,16 @@ public class DisciplineService {
     }
 
     @Transactional(propagation = Propagation.MANDATORY)
-    public StructureElement deleteStructure(StructureElementCommand structureElement,
-            Date requested, String requestedBy) {
+    public StructureElementNotification deleteStructure(StructureElementCommand structureElement,
+            Date requested, String requestedBy,
+            HolderSystemDeviceStructure holder) {
         // validation outside method
         // transaction
         //     do
         //         find
         //         create structure to pending, not latest, deleted, with data
-        //         possibly validate that deleted
         //     return
-        //         structure element for deleted structure
+        //         structure element for deleted structure + notification
 
         String uuid = structureElement.getUuid().toString();
 
@@ -108,11 +108,13 @@ public class DisciplineService {
                 requested, requestedBy, structureElement.getComment());
         disciplineRepository.createDiscipline(discipline);
 
-        return StructureElementUtil.getStructureElementRequested(discipline);
+        return new StructureElementNotification(
+                StructureElementUtil.getStructureElementRequested(discipline),
+                NotificationUtil.prepareNotification(Type.DISCIPLINE, StructureCommand.DELETE, null, null, discipline, holder));
     }
 
     @Transactional(propagation = Propagation.MANDATORY)
-    public StructureElement approveStructure(StructureElementCommand structureElement,
+    public StructureElementNotification approveStructure(StructureElementCommand structureElement,
             Date processed, String processedBy,
             HolderSystemDeviceStructure holder) {
         // validation outside method
@@ -122,7 +124,6 @@ public class DisciplineService {
         //         update not latest
         //         find
         //         approve - update structure to status APPROVED, latest to true
-        //         possibly validate that approved
         //         additional
         //             find out previous to find out what is approved - create update delete
         //             approve create
@@ -139,7 +140,7 @@ public class DisciplineService {
         //                 keep related names - legacy names
         //                 delete sub structures
         //     return
-        //         structure element for approved structure
+        //         structure element for approved structure + notification
 
         String uuid = structureElement.getUuid().toString();
         String processedComment = structureElement.getComment();
@@ -166,15 +167,13 @@ public class DisciplineService {
         discipline.setLatest(Boolean.TRUE);
         disciplineRepository.updateDiscipline(discipline);
 
-        // additional
-        // find out previous
+        // previous
         List<Discipline> previouses = iDisciplineRepository.findPreviousByUuidAndId(uuid, discipline.getId());
         StructureCommand structureCommandCUD = StructureUtil.getStructureCommandCUD(previouses, discipline);
         Discipline previous = previouses != null && !previouses.isEmpty() ? previouses.get(0) : null;
         LOGGER.log(Level.FINE, "approveStructure, structureCommandCUD:        {0}", structureCommandCUD);
 
-        // approve update
-        // approve delete
+        // additional
         if (StructureCommand.UPDATE.equals(structureCommandCUD)) {
             namesService.updateNames(previous, discipline);
         } else if (StructureCommand.DELETE.equals(structureCommandCUD)) {
@@ -186,7 +185,7 @@ public class DisciplineService {
                 structureElementCommands.add(new StructureElementCommand(deviceGroup.getUuid(), Type.DEVICEGROUP, null, null, null, null, StructuresService.DELETE_AFTER_APPROVE_STRUCTURE_CHANGE));
             }
             for (StructureElementCommand structureElementCommand : structureElementCommands) {
-                deviceGroupService.deleteStructure(structureElementCommand, processed, processedBy);
+                deviceGroupService.deleteStructure(structureElementCommand, processed, processedBy, holder);
             }
             for (StructureElementCommand structureElementCommand : structureElementCommands) {
                 deviceGroupService.approveStructure(structureElementCommand, processed, processedBy, holder);
@@ -194,7 +193,9 @@ public class DisciplineService {
         }
 
         LOGGER.log(Level.FINE, "approveStructure, structureElement:           {0}", structureElement);
-        return StructureElementUtil.getStructureElementProcessed(discipline, holder, StructureChoice.STRUCTURE);
+        return new StructureElementNotification(
+                StructureElementUtil.getStructureElementProcessed(discipline, holder, StructureChoice.STRUCTURE),
+                NotificationUtil.prepareNotification(Type.DISCIPLINE, structureCommandCUD, StructureCommand.APPROVE, previous, discipline, holder));
     }
 
 }
diff --git a/src/main/java/org/openepics/names/service/NamesService.java b/src/main/java/org/openepics/names/service/NamesService.java
index 0739f4d0adde20089ce189706154ce096e212561..2bfee4fe123e3f00310c32a976e90c46b20dd2c0 100644
--- a/src/main/java/org/openepics/names/service/NamesService.java
+++ b/src/main/java/org/openepics/names/service/NamesService.java
@@ -147,6 +147,7 @@ public class NamesService {
         //         for each name element
         //             create name, latest, with data
         //             add
+        //     no notify
         //     return
         //         name elements for created names
 
@@ -176,6 +177,7 @@ public class NamesService {
         //     find
         //     prepare
         //     create
+        //         approved, latest, not deleted, uuid
         // return
         //     name element for name
 
@@ -210,9 +212,6 @@ public class NamesService {
                 requested, requestedBy, nameElement.getComment());
         nameRepository.createName(name);
 
-        // possibly validate that created
-        //     approved, latest, not deleted, uuid
-
         // add
         return NameElementUtil.getNameElement(name);
     }
@@ -558,8 +557,8 @@ public class NamesService {
         //             update name to not latest
         //             find
         //             insert name to latest, not deleted, with data
-        //             possibly validate that updated
         //             add
+        //     no notify
         //     return
         //         name elements for updated names
 
@@ -746,8 +745,8 @@ public class NamesService {
         //             update name to not latest
         //             find
         //             insert name to latest, deleted
-        //             possibly validate that deleted
         //             add
+        //     no notify
         //     return
         //         name element for deleted names
 
diff --git a/src/main/java/org/openepics/names/service/NotificationService.java b/src/main/java/org/openepics/names/service/NotificationService.java
new file mode 100644
index 0000000000000000000000000000000000000000..75dcadee9ed08ca221e32d10b26db366720df9e9
--- /dev/null
+++ b/src/main/java/org/openepics/names/service/NotificationService.java
@@ -0,0 +1,261 @@
+/*
+ * Copyright (C) 2023 European Spallation Source ERIC.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package org.openepics.names.service;
+
+import java.nio.charset.StandardCharsets;
+import java.text.MessageFormat;
+import java.util.List;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import org.apache.commons.lang3.StringUtils;
+import org.openepics.names.util.NameCommand;
+import org.openepics.names.util.StructureCommand;
+import org.openepics.names.util.notification.NotificationName;
+import org.openepics.names.util.notification.NotificationStructure;
+import org.openepics.names.util.notification.NotificationUtil;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Service;
+import org.thymeleaf.TemplateEngine;
+import org.thymeleaf.context.Context;
+import org.thymeleaf.spring5.SpringTemplateEngine;
+import org.thymeleaf.templatemode.TemplateMode;
+import org.thymeleaf.templateresolver.ClassLoaderTemplateResolver;
+
+/**
+ * This class provides notification services.
+ *
+ * @author Lars Johansson
+ */
+@Service
+public class NotificationService {
+
+    private static final Logger LOGGER = Logger.getLogger(NotificationService.class.getName());
+
+    private static final String NOTIFY_NAMES_CREATED_UPDATED_DELETED =
+            "Notify names, # created: {0}, # updated: {1}, # deleted: {2}";
+    private static final String NOTIFY_STRUCTURES_CREATED_UPDATED_DELETED_APPROVED_CANCELLED_REJECTED =
+            "Notify structures, # created: {0}, # updated: {1}, # deleted: {2}"
+            + ", # approved: {3}, # cancelled: {4}, # rejected: {5}";
+
+    private static final String CHANGES_NOTIFICATION_NAMES             = "[Changes for names]";
+    private static final String CHANGES_NOTIFICATION_STRUCTURES        = "[Changes for structures]";
+    private static final String CHANGES_NOTIFICATION_STRUCTURES_ACTION = "[Changes for structures - need action]";
+
+    private static final String ADD_FOOTER  = "addfooter";
+    private static final String BACKEND_URL = "backendurl";
+    private static final String COMMA       = ",";
+
+    @Value("${naming.backend.swagger.url}")
+    String namingBackendSwaggerUrl;
+    @Value("${naming.mail.administrator}")
+    String namingMailAdministrator;
+
+    @Autowired
+    private MailService mailService;
+
+    /**
+     * Send notifications for names to affected users and administrators by email.
+     *
+     * @param notifications list of name notifications
+     * @param nameCommand name command
+     */
+    public void notifyNames(List<NotificationName> notifications, NameCommand nameCommand) {
+        if (NameCommand.CREATE.equals(nameCommand)) {
+            notifyNames(notifications, null, null);
+        } else if (NameCommand.UPDATE.equals(nameCommand)) {
+            notifyNames(null, notifications, null);
+        } else if (NameCommand.DELETE.equals(nameCommand)) {
+            notifyNames(null, null, notifications);
+        }
+    }
+
+    /**
+     * Send notifications for names to affected users and administrators by email.
+     *
+     * @param created list of notifications for created names
+     * @param updated list of notifications for updated names
+     * @param deleted list of notifications for deleted names
+     */
+    public void notifyNames(
+            List<NotificationName> created,
+            List<NotificationName> updated,
+            List<NotificationName> deleted) {
+
+        NotificationUtil.sortByNewName(created);
+        NotificationUtil.sortByNewName(updated);
+        NotificationUtil.sortByNewName(deleted);
+
+        int numberCreated = getListSize(created);
+        int numberUpdated = getListSize(updated);
+        int numberDeleted = getListSize(deleted);
+
+        LOGGER.log(Level.INFO,
+                () -> MessageFormat.format(
+                        NOTIFY_NAMES_CREATED_UPDATED_DELETED,
+                        numberCreated,
+                        numberUpdated,
+                        numberDeleted));
+
+        // email content
+        final Context ctx = new Context();
+        ctx.setVariable("numberCreated", numberCreated);
+        ctx.setVariable("numberUpdated", numberUpdated);
+        ctx.setVariable("numberDeleted", numberDeleted);
+        ctx.setVariable("created", created);
+        ctx.setVariable("updated", updated);
+        ctx.setVariable("deleted", deleted);
+        ctx.setVariable(ADD_FOOTER, true);
+        ctx.setVariable(BACKEND_URL, namingBackendSwaggerUrl);
+        TemplateEngine engine = generateTemplateEngine();
+
+        // send notification
+        String[] toEmailAddresses = !StringUtils.isEmpty(namingMailAdministrator)
+                ? namingMailAdministrator.split(COMMA)
+                : null;
+        String[] ccEmailAddresses = null;
+        String[] replyToEmailAddresses = null;
+        String subject = CHANGES_NOTIFICATION_NAMES + " - " + namingBackendSwaggerUrl;
+        mailService.sendEmail(toEmailAddresses, ccEmailAddresses, replyToEmailAddresses,
+                subject, engine.process("templates/notification_names.html", ctx), false, null, null);
+    }
+
+    /**
+     * Send notifications for structures to affected users and administrators by email.
+     *
+     * @param notifications list of structure notifications
+     * @param structureCommand structure command
+     */
+    public void notifyStructures(List<NotificationStructure> notifications, StructureCommand structureCommand) {
+        if (StructureCommand.CREATE.equals(structureCommand)) {
+            notifyStructures(notifications, null, null, null, null, null);
+        } else if (StructureCommand.UPDATE.equals(structureCommand)) {
+            notifyStructures(null, notifications, null, null, null, null);
+        } else if (StructureCommand.DELETE.equals(structureCommand)) {
+            notifyStructures(null, null, notifications, null, null, null);
+        } else if (StructureCommand.APPROVE.equals(structureCommand)) {
+            notifyStructures(null, null, null, notifications, null, null);
+        } else if (StructureCommand.CANCEL.equals(structureCommand)) {
+            notifyStructures(null, null, null, null, notifications, null);
+        } else if (StructureCommand.REJECT.equals(structureCommand)) {
+            notifyStructures(null, null, null, null, null, notifications);
+        }
+    }
+
+    /**
+     * Send notifications for structures to affected users and administrators by email.
+     *
+     * @param created list of notifications for created structures
+     * @param updated list of notifications for updated structures
+     * @param deleted list of notifications for deleted structures
+     * @param approved list of notifications for approved structures
+     * @param cancelled list of notifications for cancelled structures
+     * @param rejected list of notifications for rejected structures
+     */
+    public void notifyStructures(
+            List<NotificationStructure> created,
+            List<NotificationStructure> updated,
+            List<NotificationStructure> deleted,
+            List<NotificationStructure> approved,
+            List<NotificationStructure> cancelled,
+            List<NotificationStructure> rejected) {
+
+        NotificationUtil.sortByNewMnemonicpath(created);
+        NotificationUtil.sortByNewMnemonicpath(updated);
+        NotificationUtil.sortByNewMnemonicpath(deleted);
+        NotificationUtil.sortByNewMnemonicpath(approved);
+        NotificationUtil.sortByNewMnemonicpath(cancelled);
+        NotificationUtil.sortByNewMnemonicpath(rejected);
+
+        int numberCreated   = getListSize(created);
+        int numberUpdated   = getListSize(updated);
+        int numberDeleted   = getListSize(deleted);
+        int numberApproved  = getListSize(approved);
+        int numberCancelled = getListSize(cancelled);
+        int numberRejected  = getListSize(rejected);
+
+        LOGGER.log(Level.INFO,
+                () -> MessageFormat.format(
+                        NOTIFY_STRUCTURES_CREATED_UPDATED_DELETED_APPROVED_CANCELLED_REJECTED,
+                        numberCreated,
+                        numberUpdated,
+                        numberDeleted,
+                        numberApproved,
+                        numberCancelled,
+                        numberRejected));
+
+        // email content
+        final Context ctx = new Context();
+        ctx.setVariable("numberCreated",   numberCreated);
+        ctx.setVariable("numberUpdated",   numberUpdated);
+        ctx.setVariable("numberDeleted",   numberDeleted);
+        ctx.setVariable("numberApproved",  numberApproved);
+        ctx.setVariable("numberCancelled", numberCancelled);
+        ctx.setVariable("numberRejected",  numberRejected);
+        ctx.setVariable("created",   created);
+        ctx.setVariable("updated",   updated);
+        ctx.setVariable("deleted",   deleted);
+        ctx.setVariable("approved",  approved);
+        ctx.setVariable("cancelled", cancelled);
+        ctx.setVariable("rejected",  rejected);
+        ctx.setVariable(ADD_FOOTER, true);
+        ctx.setVariable(BACKEND_URL, namingBackendSwaggerUrl);
+        TemplateEngine engine = generateTemplateEngine();
+
+        // send notification
+        String[] toEmailAddresses = !StringUtils.isEmpty(namingMailAdministrator)
+                ? namingMailAdministrator.split(COMMA)
+                : null;
+        String[] ccEmailAddresses = null;
+        String[] replyToEmailAddresses = null;
+        String subject = numberCreated > 0 || numberUpdated > 0 || numberDeleted > 0
+                ? CHANGES_NOTIFICATION_STRUCTURES_ACTION + " - " + namingBackendSwaggerUrl
+                : CHANGES_NOTIFICATION_STRUCTURES        + " - " + namingBackendSwaggerUrl;
+        mailService.sendEmail(toEmailAddresses, ccEmailAddresses, replyToEmailAddresses,
+                subject, engine.process("templates/notification_structures.html", ctx), false, null, null);
+    }
+
+    /**
+     * Return list size, zero if empty list.
+     *
+     * @param list list
+     * @return list size
+     */
+    private int getListSize(List<?> list) {
+        return list != null ? list.size() : 0;
+    }
+
+    /**
+     * Generates the Thymeleaf template engine
+     *
+     * @return generate template engine
+     */
+    private TemplateEngine generateTemplateEngine() {
+        ClassLoaderTemplateResolver templateResolver = new ClassLoaderTemplateResolver();
+        templateResolver.setTemplateMode(TemplateMode.HTML);
+        templateResolver.setCacheable(false);
+        templateResolver.setCharacterEncoding(StandardCharsets.UTF_8.name());
+
+        TemplateEngine engine = new SpringTemplateEngine();
+        engine.setTemplateResolver(templateResolver);
+        return engine;
+    }
+
+}
diff --git a/src/main/java/org/openepics/names/service/StructureElementNotification.java b/src/main/java/org/openepics/names/service/StructureElementNotification.java
new file mode 100644
index 0000000000000000000000000000000000000000..7ce9a9a7dbe935e3303a279db3449cecf1a1f7d6
--- /dev/null
+++ b/src/main/java/org/openepics/names/service/StructureElementNotification.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2023 European Spallation Source ERIC.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package org.openepics.names.service;
+
+import org.openepics.names.rest.beans.element.StructureElement;
+import org.openepics.names.util.notification.NotificationStructure;
+
+/**
+ * Utility record to collect structure element and notification.
+ * Intended for use in service package classes only.
+ *
+ * @author Lars Johansson
+ */
+record StructureElementNotification (StructureElement structureElement, NotificationStructure notificationStructure) {}
diff --git a/src/main/java/org/openepics/names/service/StructuresService.java b/src/main/java/org/openepics/names/service/StructuresService.java
index 6b070de38e44a8970cd1b3a7c1b44a4465c2f83c..1e5a4349cbdd783bfc6745404f0d10724671f9de 100644
--- a/src/main/java/org/openepics/names/service/StructuresService.java
+++ b/src/main/java/org/openepics/names/service/StructuresService.java
@@ -56,11 +56,14 @@ import org.openepics.names.util.HolderIRepositories;
 import org.openepics.names.util.HolderRepositories;
 import org.openepics.names.util.HolderSystemDeviceStructure;
 import org.openepics.names.util.StructureChoice;
+import org.openepics.names.util.StructureCommand;
 import org.openepics.names.util.StructureElementUtil;
 import org.openepics.names.util.TextUtil;
 import org.openepics.names.util.Utilities;
 import org.openepics.names.util.ValidateStructureElementUtil;
 import org.openepics.names.util.ValidateUtil;
+import org.openepics.names.util.notification.NotificationStructure;
+import org.openepics.names.util.notification.NotificationUtil;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
@@ -101,6 +104,7 @@ public class StructuresService {
     private DisciplineService disciplineService;
     private DeviceGroupService deviceGroupService;
     private DeviceTypeService deviceTypeService;
+    private NotificationService notificationService;
 
     @Autowired
     public StructuresService(
@@ -123,7 +127,8 @@ public class StructuresService {
             SubsystemService subsystemService,
             DisciplineService disciplineService,
             DeviceGroupService deviceGroupService,
-            DeviceTypeService deviceTypeService) {
+            DeviceTypeService deviceTypeService,
+            NotificationService notificationService) {
 
         this.namingConvention = new EssNamingConvention();
         this.holderIRepositories = new HolderIRepositories(
@@ -148,6 +153,7 @@ public class StructuresService {
         this.disciplineService = disciplineService;
         this.deviceGroupService = deviceGroupService;
         this.deviceTypeService = deviceTypeService;
+        this.notificationService = notificationService;
     }
 
     @Transactional
@@ -155,22 +161,28 @@ public class StructuresService {
         // validation outside method
         // transaction
         //     do
-        //         for each structure element
+        //         for each structure element command
         //             create structure to pending, not latest, not deleted, with data
-        //             possibly validate that created
-        //             add structure element for created
+        //             handle notification
+        //             handle structure element for created structure
+        //     notify
         //     return
         //         structure elements for created structures
 
         LOGGER.log(Level.FINE, "createStructures, structureElements.size:        {0}", Utilities.getSize(structureElements));
 
+        // initiate holder of containers for system and device structure content, for performance reasons
+        HolderSystemDeviceStructure holder = new HolderSystemDeviceStructure(holderIRepositories);
+
         // do
         Date requested = new Date();
         String requestedBy = TextUtil.TEST_WHO;
+        List<NotificationStructure> notifications = Lists.newArrayList();
         final List<StructureElement> createdStructureElements = Lists.newArrayList();
         for (StructureElementCommand structureElement : structureElements) {
             // note
             //     namingConvention.equivalenceClassRepresentative return null for null input
+            //     rules for mnemonic for system group, device group
 
             UUID uuid = UUID.randomUUID();
             Type type = structureElement.getType();
@@ -182,8 +194,6 @@ public class StructuresService {
             String requestedComment = structureElement.getComment();
 
             if (Type.SYSTEMGROUP.equals(type)) {
-                // note rules for mnemonic for system group
-
                 // create
                 SystemGroup systemGroup = new SystemGroup(uuid,
                         name, mnemonic, equivalenceClassRepresentative,
@@ -191,7 +201,8 @@ public class StructuresService {
                         requested, requestedBy, requestedComment);
                 holderRepositories.getSystemGroupRepository().createSystemGroup(systemGroup);
 
-                // add
+                // handle
+                NotificationUtil.prepareAddNotification(notifications, type, StructureCommand.CREATE, null, null, systemGroup, holder);
                 createdStructureElements.add(StructureElementUtil.getStructureElementRequested(systemGroup));
             } else if (Type.SYSTEM.equals(type)) {
                 // create
@@ -201,7 +212,8 @@ public class StructuresService {
                         requested, requestedBy, requestedComment);
                 holderRepositories.getSystemRepository().createSystem(system);
 
-                // add
+                // handle
+                NotificationUtil.prepareAddNotification(notifications, type, StructureCommand.CREATE, null, null, system, holder);
                 createdStructureElements.add(StructureElementUtil.getStructureElementRequested(system));
             } else if (Type.SUBSYSTEM.equals(type)) {
                 // create
@@ -211,7 +223,8 @@ public class StructuresService {
                         requested, requestedBy, requestedComment);
                 holderRepositories.getSubsystemRepository().createSubsystem(subsystem);
 
-                // add
+                // handle
+                NotificationUtil.prepareAddNotification(notifications, type, StructureCommand.CREATE, null, null, subsystem, holder);
                 createdStructureElements.add(StructureElementUtil.getStructureElementRequested(subsystem));
             } else if (Type.DISCIPLINE.equals(type)) {
                 // create
@@ -221,11 +234,10 @@ public class StructuresService {
                         requested, requestedBy, requestedComment);
                 holderRepositories.getDisciplineRepository().createDiscipline(discipline);
 
-                // add
+                // handle
+                NotificationUtil.prepareAddNotification(notifications, type, StructureCommand.CREATE, null, null, discipline, holder);
                 createdStructureElements.add(StructureElementUtil.getStructureElementRequested(discipline));
             } else if (Type.DEVICEGROUP.equals(type)) {
-                // note rules for mnemonic for device group
-
                 // create
                 DeviceGroup deviceGroup = new DeviceGroup(uuid, structureElement.getParent(),
                         name, mnemonic, equivalenceClassRepresentative,
@@ -233,7 +245,8 @@ public class StructuresService {
                         requested, requestedBy, requestedComment);
                 holderRepositories.getDeviceGroupRepository().createDeviceGroup(deviceGroup);
 
-                // add
+                // handle
+                NotificationUtil.prepareAddNotification(notifications, type, StructureCommand.CREATE, null, null, deviceGroup, holder);
                 createdStructureElements.add(StructureElementUtil.getStructureElementRequested(deviceGroup));
             } else if (Type.DEVICETYPE.equals(type)) {
                 // create
@@ -243,11 +256,15 @@ public class StructuresService {
                         requested, requestedBy, requestedComment);
                 holderRepositories.getDeviceTypeRepository().createDeviceType(deviceType);
 
-                // add
+                // handle
+                NotificationUtil.prepareAddNotification(notifications, type, StructureCommand.CREATE, null, null, deviceType, holder);
                 createdStructureElements.add(StructureElementUtil.getStructureElementRequested(deviceType));
             }
         }
 
+        // notify
+        notificationService.notifyStructures(notifications, StructureCommand.CREATE);
+
         LOGGER.log(Level.FINE, "createStructures, createdStructureElements.size: {0}", createdStructureElements.size());
         return createdStructureElements;
     }
@@ -813,21 +830,29 @@ public class StructuresService {
         // validation outside method
         // transaction
         //     do
-        //         for each structure element
+        //         for each structure element command
         //             create structure to pending, not latest, not deleted, with data
-        //             possibly validate that updated
-        //             add structure element for updated
+        //             find out previous
+        //             handle notification
+        //             handle structure element for updated structure
+        //     notify
         //     return
         //         structure elements for updated structures
 
         LOGGER.log(Level.FINE, "updateStructures, structureElements.size:        {0}", Utilities.getSize(structureElements));
 
+        // initiate holder of containers for system and device structure content, for performance reasons
+        HolderSystemDeviceStructure holder = new HolderSystemDeviceStructure(holderIRepositories);
+
         // do
         Date requested = new Date();
         String requestedBy = TextUtil.TEST_WHO;
+        List<NotificationStructure> notifications = Lists.newArrayList();
         final List<StructureElement> updatedStructureElements = Lists.newArrayList();
         for (StructureElementCommand structureElement : structureElements) {
-            // namingConvention.equivalenceClassRepresentative return null for null input
+            // note
+            //     namingConvention.equivalenceClassRepresentative return null for null input
+            //     rules for mnemonic for system group, device group
 
             UUID uuid = structureElement.getUuid();
             Type type = structureElement.getType();
@@ -839,8 +864,6 @@ public class StructuresService {
             String requestedComment = structureElement.getComment();
 
             if (Type.SYSTEMGROUP.equals(type)) {
-                // note rules for mnemonic for system group
-
                 // create
                 SystemGroup systemGroup = new SystemGroup(uuid,
                         name, mnemonic, equivalenceClassRepresentative,
@@ -848,7 +871,12 @@ public class StructuresService {
                         requested, requestedBy, requestedComment);
                 holderRepositories.getSystemGroupRepository().createSystemGroup(systemGroup);
 
-                // add
+                // previous
+                List<SystemGroup> previouses = holderIRepositories.getSystemGroupRepository().findPreviousByUuidAndId(uuid.toString(),  systemGroup.getId());
+
+                // handle
+                NotificationUtil.prepareAddNotification(notifications, type, StructureCommand.UPDATE, null,
+                        previouses != null && !previouses.isEmpty() ? previouses.get(0) : null, systemGroup, holder);
                 updatedStructureElements.add(StructureElementUtil.getStructureElementRequested(systemGroup));
             } else if (Type.SYSTEM.equals(type)) {
                 // create
@@ -858,7 +886,12 @@ public class StructuresService {
                         requested, requestedBy, requestedComment);
                 holderRepositories.getSystemRepository().createSystem(system);
 
-                // add
+                // previous
+                List<System> previouses = holderIRepositories.getSystemRepository().findPreviousByUuidAndId(uuid.toString(),  system.getId());
+
+                // handle
+                NotificationUtil.prepareAddNotification(notifications, type, StructureCommand.UPDATE, null,
+                        previouses != null && !previouses.isEmpty() ? previouses.get(0) : null, system, holder);
                 updatedStructureElements.add(StructureElementUtil.getStructureElementRequested(system));
             } else if (Type.SUBSYSTEM.equals(type)) {
                 // create
@@ -868,7 +901,12 @@ public class StructuresService {
                         requested, requestedBy, requestedComment);
                 holderRepositories.getSubsystemRepository().createSubsystem(subsystem);
 
-                // add
+                // previous
+                List<Subsystem> previouses = holderIRepositories.getSubsystemRepository().findPreviousByUuidAndId(uuid.toString(),  subsystem.getId());
+
+                // handle
+                NotificationUtil.prepareAddNotification(notifications, type, StructureCommand.UPDATE, null,
+                        previouses != null && !previouses.isEmpty() ? previouses.get(0) : null, subsystem, holder);
                 updatedStructureElements.add(StructureElementUtil.getStructureElementRequested(subsystem));
             } else if (Type.DISCIPLINE.equals(type)) {
                 // create
@@ -878,11 +916,14 @@ public class StructuresService {
                         requested, requestedBy, requestedComment);
                 holderRepositories.getDisciplineRepository().createDiscipline(discipline);
 
-                // add
+                // previous
+                List<Discipline> previouses = holderIRepositories.getDisciplineRepository().findPreviousByUuidAndId(uuid.toString(),  discipline.getId());
+
+                // handle
+                NotificationUtil.prepareAddNotification(notifications, type, StructureCommand.UPDATE, null,
+                        previouses != null && !previouses.isEmpty() ? previouses.get(0) : null, discipline, holder);
                 updatedStructureElements.add(StructureElementUtil.getStructureElementRequested(discipline));
             } else if (Type.DEVICEGROUP.equals(type)) {
-                // note rules for mnemonic for device group
-
                 // create
                 DeviceGroup deviceGroup = new DeviceGroup(uuid, structureElement.getParent(),
                         name, mnemonic, equivalenceClassRepresentative,
@@ -890,7 +931,12 @@ public class StructuresService {
                         requested, requestedBy, requestedComment);
                 holderRepositories.getDeviceGroupRepository().createDeviceGroup(deviceGroup);
 
-                // add
+                // previous
+                List<DeviceGroup> previouses = holderIRepositories.getDeviceGroupRepository().findPreviousByUuidAndId(uuid.toString(),  deviceGroup.getId());
+
+                // handle
+                NotificationUtil.prepareAddNotification(notifications, type, StructureCommand.UPDATE, null,
+                        previouses != null && !previouses.isEmpty() ? previouses.get(0) : null, deviceGroup, holder);
                 updatedStructureElements.add(StructureElementUtil.getStructureElementRequested(deviceGroup));
             } else if (Type.DEVICETYPE.equals(type)) {
                 // create
@@ -900,11 +946,19 @@ public class StructuresService {
                         requested, requestedBy, requestedComment);
                 holderRepositories.getDeviceTypeRepository().createDeviceType(deviceType);
 
-                // add
+                // previous
+                List<DeviceType> previouses = holderIRepositories.getDeviceTypeRepository().findPreviousByUuidAndId(uuid.toString(),  deviceType.getId());
+
+                // handle
+                NotificationUtil.prepareAddNotification(notifications, type, StructureCommand.UPDATE, null,
+                        previouses != null && !previouses.isEmpty() ? previouses.get(0) : null, deviceType, holder);
                 updatedStructureElements.add(StructureElementUtil.getStructureElementRequested(deviceType));
             }
         }
 
+        // notify
+        notificationService.notifyStructures(notifications, StructureCommand.UPDATE);
+
         LOGGER.log(Level.FINE, "updateStructures, updatedStructureElements.size: {0}", updatedStructureElements.size());
         return updatedStructureElements;
     }
@@ -916,44 +970,51 @@ public class StructuresService {
         // validation outside method
         // transaction
         //     do
-        //         for each structure element
-        //             find
-        //             create structure to pending, not latest, deleted, with data
-        //             possibly validate that deleted
-        //             add structure element for deleted
+        //         for each structure element command
+        //             delete structure
+        //             handle notification
+        //             handle structure element for deleted structure
+        //     notify
         //     return
         //         structure elements for deleted structures
 
         LOGGER.log(Level.FINE, "deleteStructures, structureElements.size:        {0}", Utilities.getSize(structureElements));
 
+        // initiate holder of containers for system and device structure content, for performance reasons
+        HolderSystemDeviceStructure holder = new HolderSystemDeviceStructure(holderIRepositories);
+
         // do
         Date requested = new Date();
         String requestedBy = TextUtil.TEST_WHO;
+        List<NotificationStructure> notifications = Lists.newArrayList();
         final List<StructureElement> deletedStructureElements = Lists.newArrayList();
         for (StructureElementCommand structureElement : structureElements) {
             Type type = structureElement.getType();
+            StructureElementNotification structureElementNotification = null;
 
             if (Type.SYSTEMGROUP.equals(type)) {
-                Utilities.addToCollection(deletedStructureElements,
-                        systemGroupService.deleteStructure(structureElement, requested, requestedBy));
+                structureElementNotification = systemGroupService.deleteStructure(structureElement, requested, requestedBy, holder);
             } else if (Type.SYSTEM.equals(type)) {
-                Utilities.addToCollection(deletedStructureElements,
-                        systemService.deleteStructure(structureElement, requested, requestedBy));
+                structureElementNotification = systemService.deleteStructure(structureElement, requested, requestedBy, holder);
             } else if (Type.SUBSYSTEM.equals(type)) {
-                Utilities.addToCollection(deletedStructureElements,
-                        subsystemService.deleteStructure(structureElement, requested, requestedBy));
+                structureElementNotification = subsystemService.deleteStructure(structureElement, requested, requestedBy, holder);
             } else if (Type.DISCIPLINE.equals(type)) {
-                Utilities.addToCollection(deletedStructureElements,
-                        disciplineService.deleteStructure(structureElement, requested, requestedBy));
+                structureElementNotification = disciplineService.deleteStructure(structureElement, requested, requestedBy, holder);
             } else if (Type.DEVICEGROUP.equals(type)) {
-                Utilities.addToCollection(deletedStructureElements,
-                        deviceGroupService.deleteStructure(structureElement, requested, requestedBy));
+                structureElementNotification = deviceGroupService.deleteStructure(structureElement, requested, requestedBy, holder);
             } else if (Type.DEVICETYPE.equals(type)) {
-                Utilities.addToCollection(deletedStructureElements,
-                        deviceTypeService.deleteStructure(structureElement, requested, requestedBy));
+                structureElementNotification = deviceTypeService.deleteStructure(structureElement, requested, requestedBy, holder);
+            } else {
+                continue;
             }
+
+            Utilities.addToCollection(notifications, structureElementNotification.notificationStructure());
+            Utilities.addToCollection(deletedStructureElements, structureElementNotification.structureElement());
         }
 
+        // notify
+        notificationService.notifyStructures(notifications, StructureCommand.DELETE);
+
         LOGGER.log(Level.FINE, "deleteStructures, deletedStructureElements.size: {0}", deletedStructureElements.size());
         return deletedStructureElements;
     }
@@ -965,28 +1026,11 @@ public class StructuresService {
         // validation outside method
         // transaction
         //     do
-        //         for each structure element
+        //         for each structure element command
         //             approve structure
-        //                 find
-        //                 update not latest
-        //                 find
-        //                 approve - update structure to status APPROVED, latest to true
-        //                 possibly validate that approved
-        //                 additional
-        //                     find out previous to find out what is approved - create update delete
-        //                     approve create
-        //                         automatically create name when system structure is approved
-        //                         condition on name and structure entry
-        //                             system structure should exist (uuid), one entry that is not deleted
-        //                             name should not exist (system structure mnemonic)
-        //                         within current transaction
-        //                     approve update
-        //                         update related names
-        //                         for different mnemonic
-        //                         not for device group
-        //                     approve delete
-        //                         keep related names - legacy names
-        //                         delete sub structures
+        //             handle notification
+        //             handle structure element for approved structure
+        //     notify
         //     return
         //         structure elements for approved structures
 
@@ -1003,36 +1047,37 @@ public class StructuresService {
         //         delete sub structures
 
         // do
-        //     update
-        //         set not latest for current latest
-        //         set approved, latest for pending
         Date processed = new Date();
         String processedBy = TextUtil.TEST_WHO;
+        List<NotificationStructure> notifications = Lists.newArrayList();
         final List<StructureElement> approvedStructureElements = Lists.newArrayList();
         for (StructureElementCommand structureElement : structureElements) {
             Type type = structureElement.getType();
+            StructureElementNotification structureElementNotification = null;
 
             if (Type.SYSTEMGROUP.equals(type)) {
-                Utilities.addToCollection(approvedStructureElements,
-                        systemGroupService.approveStructure(structureElement, processed, processedBy, holder));
+                structureElementNotification = systemGroupService.approveStructure(structureElement, processed, processedBy, holder);
             } else if (Type.SYSTEM.equals(type)) {
-                Utilities.addToCollection(approvedStructureElements,
-                          systemService.approveStructure(structureElement, processed, processedBy, holder));
+                structureElementNotification = systemService.approveStructure(structureElement, processed, processedBy, holder);
             } else if (Type.SUBSYSTEM.equals(type)) {
-                Utilities.addToCollection(approvedStructureElements,
-                          subsystemService.approveStructure(structureElement, processed, processedBy, holder));
+                structureElementNotification = subsystemService.approveStructure(structureElement, processed, processedBy, holder);
             } else if (Type.DISCIPLINE.equals(type)) {
-                Utilities.addToCollection(approvedStructureElements,
-                        disciplineService.approveStructure(structureElement, processed, processedBy, holder));
+                structureElementNotification = disciplineService.approveStructure(structureElement, processed, processedBy, holder);
             } else if (Type.DEVICEGROUP.equals(type)) {
-                Utilities.addToCollection(approvedStructureElements,
-                        deviceGroupService.approveStructure(structureElement, processed, processedBy, holder));
+                structureElementNotification = deviceGroupService.approveStructure(structureElement, processed, processedBy, holder);
             } else if (Type.DEVICETYPE.equals(structureElement.getType())) {
-                Utilities.addToCollection(approvedStructureElements,
-                        deviceTypeService.approveStructure(structureElement, processed, processedBy, holder));
+                structureElementNotification = deviceTypeService.approveStructure(structureElement, processed, processedBy, holder);
+            } else {
+                continue;
             }
+
+            Utilities.addToCollection(notifications, structureElementNotification.notificationStructure());
+            Utilities.addToCollection(approvedStructureElements, structureElementNotification.structureElement());
         }
 
+        // notify
+        notificationService.notifyStructures(notifications, StructureCommand.APPROVE);
+
         LOGGER.log(Level.FINE, "approveStructures, approvedStructureElements.size: {0}", approvedStructureElements.size());
         return approvedStructureElements;
     }
@@ -1045,8 +1090,10 @@ public class StructuresService {
         //         for each structure element
         //             find
         //             cancel - update structure to status CANCELLED
-        //             possibly validate that cancelled
-        //             add
+        //             find out previous
+        //             handle notification
+        //             handle structure element for cancelled structure
+        //     notify
         //     return
         //         structure elements for cancelled structures
 
@@ -1058,6 +1105,7 @@ public class StructuresService {
         // do
         Date processed = new Date();
         String processedBy = TextUtil.TEST_WHO;
+        List<NotificationStructure> notifications = Lists.newArrayList();
         final List<StructureElement> cancelledStructureElements = Lists.newArrayList();
         for (StructureElementCommand structureElement : structureElements) {
             String uuid = structureElement.getUuid().toString();
@@ -1076,7 +1124,13 @@ public class StructuresService {
                 systemGroup.setAttributesStatusProcessed(Status.CANCELLED, processed, processedBy, processedComment);
                 holderRepositories.getSystemGroupRepository().updateSystemGroup(systemGroup);
 
-                // add
+                // previous
+                List<SystemGroup> previouses = holderIRepositories.getSystemGroupRepository().findPreviousByUuidAndId(uuid, systemGroup.getId());
+                StructureCommand structureCommandCUD = NotificationUtil.getStructureCommandCUD(previouses, systemGroup);
+
+                // handle
+                NotificationUtil.prepareAddNotification(notifications, type, structureCommandCUD, StructureCommand.CANCEL,
+                        previouses != null && !previouses.isEmpty() ? previouses.get(0) : null, systemGroup, holder);
                 cancelledStructureElements.add(StructureElementUtil.getStructureElementProcessed(systemGroup, holder, StructureChoice.STRUCTURE));
             } else if (Type.SYSTEM.equals(type)) {
                 // find
@@ -1090,7 +1144,13 @@ public class StructuresService {
                 system.setAttributesStatusProcessed(Status.CANCELLED, processed, processedBy, processedComment);
                 holderRepositories.getSystemRepository().updateSystem(system);
 
-                // add
+                // previous
+                List<System> previouses = holderIRepositories.getSystemRepository().findPreviousByUuidAndId(uuid, system.getId());
+                StructureCommand structureCommandCUD = NotificationUtil.getStructureCommandCUD(previouses, system);
+
+                // handle
+                NotificationUtil.prepareAddNotification(notifications, type, structureCommandCUD, StructureCommand.CANCEL,
+                        previouses != null && !previouses.isEmpty() ? previouses.get(0) : null, system, holder);
                 cancelledStructureElements.add(StructureElementUtil.getStructureElementProcessed(system, holder, StructureChoice.STRUCTURE));
             } else if (Type.SUBSYSTEM.equals(type)) {
                 // find
@@ -1104,7 +1164,13 @@ public class StructuresService {
                 subsystem.setAttributesStatusProcessed(Status.CANCELLED, processed, processedBy, processedComment);
                 holderRepositories.getSubsystemRepository().updateSubsystem(subsystem);
 
-                // add
+                // previous
+                List<Subsystem> previouses = holderIRepositories.getSubsystemRepository().findPreviousByUuidAndId(uuid, subsystem.getId());
+                StructureCommand structureCommandCUD = NotificationUtil.getStructureCommandCUD(previouses, subsystem);
+
+                // handle
+                NotificationUtil.prepareAddNotification(notifications, type, structureCommandCUD, StructureCommand.CANCEL,
+                        previouses != null && !previouses.isEmpty() ? previouses.get(0) : null, subsystem, holder);
                 cancelledStructureElements.add(StructureElementUtil.getStructureElementProcessed(subsystem, holder, StructureChoice.STRUCTURE));
             } else if (Type.DISCIPLINE.equals(type)) {
                 // find
@@ -1118,7 +1184,13 @@ public class StructuresService {
                 discipline.setAttributesStatusProcessed(Status.CANCELLED, processed, processedBy, processedComment);
                 holderRepositories.getDisciplineRepository().updateDiscipline(discipline);
 
-                // add
+                // previous
+                List<Discipline> previouses = holderIRepositories.getDisciplineRepository().findPreviousByUuidAndId(uuid, discipline.getId());
+                StructureCommand structureCommandCUD = NotificationUtil.getStructureCommandCUD(previouses, discipline);
+
+                // handle
+                NotificationUtil.prepareAddNotification(notifications, type, structureCommandCUD, StructureCommand.CANCEL,
+                        previouses != null && !previouses.isEmpty() ? previouses.get(0) : null, discipline, holder);
                 cancelledStructureElements.add(StructureElementUtil.getStructureElementProcessed(discipline, holder, StructureChoice.STRUCTURE));
             } else if (Type.DEVICEGROUP.equals(type)) {
                 // find
@@ -1132,7 +1204,13 @@ public class StructuresService {
                 deviceGroup.setAttributesStatusProcessed(Status.CANCELLED, processed, processedBy, processedComment);
                 holderRepositories.getDeviceGroupRepository().updateDeviceGroup(deviceGroup);
 
-                // add
+                // previous
+                List<DeviceGroup> previouses = holderIRepositories.getDeviceGroupRepository().findPreviousByUuidAndId(uuid, deviceGroup.getId());
+                StructureCommand structureCommandCUD = NotificationUtil.getStructureCommandCUD(previouses, deviceGroup);
+
+                // handle
+                NotificationUtil.prepareAddNotification(notifications, type, structureCommandCUD, StructureCommand.CANCEL,
+                        previouses != null && !previouses.isEmpty() ? previouses.get(0) : null, deviceGroup, holder);
                 cancelledStructureElements.add(StructureElementUtil.getStructureElementProcessed(deviceGroup, holder, StructureChoice.STRUCTURE));
             } else if (Type.DEVICETYPE.equals(type)) {
                 List<DeviceType> deviceTypes = holderRepositories.getDeviceTypeRepository().readDeviceTypes(Status.PENDING, null, uuid, null, null, null, null, null, null);
@@ -1145,11 +1223,20 @@ public class StructuresService {
                 deviceType.setAttributesStatusProcessed(Status.CANCELLED, processed, processedBy, processedComment);
                 holderRepositories.getDeviceTypeRepository().updateDeviceType(deviceType);
 
-                // add
+                // previous
+                List<DeviceType> previouses = holderIRepositories.getDeviceTypeRepository().findPreviousByUuidAndId(uuid, deviceType.getId());
+                StructureCommand structureCommandCUD = NotificationUtil.getStructureCommandCUD(previouses, deviceType);
+
+                // handle
+                NotificationUtil.prepareAddNotification(notifications, type, structureCommandCUD, StructureCommand.CANCEL,
+                        previouses != null && !previouses.isEmpty() ? previouses.get(0) : null, deviceType, holder);
                 cancelledStructureElements.add(StructureElementUtil.getStructureElementProcessed(deviceType, holder, StructureChoice.STRUCTURE));
             }
         }
 
+        // notify
+        notificationService.notifyStructures(notifications, StructureCommand.CANCEL);
+
         LOGGER.log(Level.FINE, "cancelStructures, cancelledStructureElements.size: {0}", cancelledStructureElements.size());
         return cancelledStructureElements;
     }
@@ -1162,8 +1249,10 @@ public class StructuresService {
         //         for each structure element
         //             find
         //             reject - update structure to status REJECTED
-        //             possibly validate that rejected
-        //             add
+        //             find out previous
+        //             handle notification
+        //             handle structure element for rejected structure
+        //     notify
         //     return
         //         structure elements for rejected structures
 
@@ -1175,6 +1264,7 @@ public class StructuresService {
         // do
         Date processed = new Date();
         String processedBy = TextUtil.TEST_WHO;
+        List<NotificationStructure> notifications = Lists.newArrayList();
         final List<StructureElement> rejectedStructureElements = Lists.newArrayList();
         for (StructureElementCommand structureElement : structureElements) {
             String uuid = structureElement.getUuid().toString();
@@ -1193,7 +1283,13 @@ public class StructuresService {
                 systemGroup.setAttributesStatusProcessed(Status.REJECTED, processed, processedBy, processedComment);
                 holderRepositories.getSystemGroupRepository().updateSystemGroup(systemGroup);
 
-                // add
+                // previous
+                List<SystemGroup> previouses = holderIRepositories.getSystemGroupRepository().findPreviousByUuidAndId(uuid, systemGroup.getId());
+                StructureCommand structureCommandCUD = NotificationUtil.getStructureCommandCUD(previouses, systemGroup);
+
+                // handle
+                NotificationUtil.prepareAddNotification(notifications, type, structureCommandCUD, StructureCommand.REJECT,
+                        previouses != null && !previouses.isEmpty() ? previouses.get(0) : null, systemGroup, holder);
                 rejectedStructureElements.add(StructureElementUtil.getStructureElementProcessed(systemGroup, holder, StructureChoice.STRUCTURE));
             } else if (Type.SYSTEM.equals(type)) {
                 // find
@@ -1207,7 +1303,13 @@ public class StructuresService {
                 system.setAttributesStatusProcessed(Status.REJECTED, processed, processedBy, processedComment);
                 holderRepositories.getSystemRepository().updateSystem(system);
 
-                // add
+                // previous
+                List<System> previouses = holderIRepositories.getSystemRepository().findPreviousByUuidAndId(uuid, system.getId());
+                StructureCommand structureCommandCUD = NotificationUtil.getStructureCommandCUD(previouses, system);
+
+                // handle
+                NotificationUtil.prepareAddNotification(notifications, type, structureCommandCUD, StructureCommand.REJECT,
+                        previouses != null && !previouses.isEmpty() ? previouses.get(0) : null, system, holder);
                 rejectedStructureElements.add(StructureElementUtil.getStructureElementProcessed(system, holder, StructureChoice.STRUCTURE));
             } else if (Type.SUBSYSTEM.equals(type)) {
                 // find
@@ -1221,7 +1323,13 @@ public class StructuresService {
                 subsystem.setAttributesStatusProcessed(Status.REJECTED, processed, processedBy, processedComment);
                 holderRepositories.getSubsystemRepository().updateSubsystem(subsystem);
 
-                // add
+                // previous
+                List<Subsystem> previouses = holderIRepositories.getSubsystemRepository().findPreviousByUuidAndId(uuid, subsystem.getId());
+                StructureCommand structureCommandCUD = NotificationUtil.getStructureCommandCUD(previouses, subsystem);
+
+                // handle
+                NotificationUtil.prepareAddNotification(notifications, type, structureCommandCUD, StructureCommand.REJECT,
+                        previouses != null && !previouses.isEmpty() ? previouses.get(0) : null, subsystem, holder);
                 rejectedStructureElements.add(StructureElementUtil.getStructureElementProcessed(subsystem, holder, StructureChoice.STRUCTURE));
             } else if (Type.DISCIPLINE.equals(type)) {
                 // find
@@ -1235,7 +1343,13 @@ public class StructuresService {
                 discipline.setAttributesStatusProcessed(Status.REJECTED, processed, processedBy, processedComment);
                 holderRepositories.getDisciplineRepository().updateDiscipline(discipline);
 
-                // add
+                // previous
+                List<Discipline> previouses = holderIRepositories.getDisciplineRepository().findPreviousByUuidAndId(uuid, discipline.getId());
+                StructureCommand structureCommandCUD = NotificationUtil.getStructureCommandCUD(previouses, discipline);
+
+                // handle
+                NotificationUtil.prepareAddNotification(notifications, type, structureCommandCUD, StructureCommand.REJECT,
+                        previouses != null && !previouses.isEmpty() ? previouses.get(0) : null, discipline, holder);
                 rejectedStructureElements.add(StructureElementUtil.getStructureElementProcessed(discipline, holder, StructureChoice.STRUCTURE));
             } else if (Type.DEVICEGROUP.equals(type)) {
                 // find
@@ -1249,7 +1363,13 @@ public class StructuresService {
                 deviceGroup.setAttributesStatusProcessed(Status.REJECTED, processed, processedBy, processedComment);
                 holderRepositories.getDeviceGroupRepository().updateDeviceGroup(deviceGroup);
 
-                // add
+                // previous
+                List<DeviceGroup> previouses = holderIRepositories.getDeviceGroupRepository().findPreviousByUuidAndId(uuid, deviceGroup.getId());
+                StructureCommand structureCommandCUD = NotificationUtil.getStructureCommandCUD(previouses, deviceGroup);
+
+                // handle
+                NotificationUtil.prepareAddNotification(notifications, type, structureCommandCUD, StructureCommand.REJECT,
+                        previouses != null && !previouses.isEmpty() ? previouses.get(0) : null, deviceGroup, holder);
                 rejectedStructureElements.add(StructureElementUtil.getStructureElementProcessed(deviceGroup, holder, StructureChoice.STRUCTURE));
             } else if (Type.DEVICETYPE.equals(type)) {
                 // find
@@ -1263,11 +1383,20 @@ public class StructuresService {
                 deviceType.setAttributesStatusProcessed(Status.REJECTED, processed, processedBy, processedComment);
                 holderRepositories.getDeviceTypeRepository().updateDeviceType(deviceType);
 
-                // add
+                // previous
+                List<DeviceType> previouses = holderIRepositories.getDeviceTypeRepository().findPreviousByUuidAndId(uuid, deviceType.getId());
+                StructureCommand structureCommandCUD = NotificationUtil.getStructureCommandCUD(previouses, deviceType);
+
+                // handle
+                NotificationUtil.prepareAddNotification(notifications, type, structureCommandCUD, StructureCommand.REJECT,
+                        previouses != null && !previouses.isEmpty() ? previouses.get(0) : null, deviceType, holder);
                 rejectedStructureElements.add(StructureElementUtil.getStructureElementProcessed(deviceType, holder, StructureChoice.STRUCTURE));
             }
         }
 
+        // notify
+        notificationService.notifyStructures(notifications, StructureCommand.REJECT);
+
         LOGGER.log(Level.FINE, "rejectStructures, rejectedStructureElements.size: {0}", rejectedStructureElements.size());
         return rejectedStructureElements;
     }
diff --git a/src/main/java/org/openepics/names/service/SubsystemService.java b/src/main/java/org/openepics/names/service/SubsystemService.java
index 77424b9ba9fc30f4431a623f5046dfae96b687f2..59dbd2ba47dd747ace82d6a893ef3305fa675df4 100644
--- a/src/main/java/org/openepics/names/service/SubsystemService.java
+++ b/src/main/java/org/openepics/names/service/SubsystemService.java
@@ -28,9 +28,9 @@ import org.openepics.names.repository.ISubsystemRepository;
 import org.openepics.names.repository.SubsystemRepository;
 import org.openepics.names.repository.model.Subsystem;
 import org.openepics.names.rest.beans.Status;
+import org.openepics.names.rest.beans.Type;
 import org.openepics.names.rest.beans.element.NameElement;
 import org.openepics.names.rest.beans.element.NameElementCommand;
-import org.openepics.names.rest.beans.element.StructureElement;
 import org.openepics.names.rest.beans.element.StructureElementCommand;
 import org.openepics.names.util.HolderSystemDeviceStructure;
 import org.openepics.names.util.StructureChoice;
@@ -38,6 +38,7 @@ import org.openepics.names.util.StructureCommand;
 import org.openepics.names.util.StructureElementUtil;
 import org.openepics.names.util.StructureUtil;
 import org.openepics.names.util.ValidateUtil;
+import org.openepics.names.util.notification.NotificationUtil;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Propagation;
@@ -73,16 +74,16 @@ public class SubsystemService {
     }
 
     @Transactional(propagation = Propagation.MANDATORY)
-    public StructureElement deleteStructure(StructureElementCommand structureElement,
-            Date requested, String requestedBy) {
+    public StructureElementNotification deleteStructure(StructureElementCommand structureElement,
+            Date requested, String requestedBy,
+            HolderSystemDeviceStructure holder) {
         // validation outside method
         // transaction
         //     do
         //         find
         //         create structure to pending, not latest, deleted, with data
-        //         possibly validate that deleted
         //     return
-        //         structure element for deleted structure
+        //         structure element for deleted structure + notification
 
         String uuid = structureElement.getUuid().toString();
 
@@ -100,11 +101,13 @@ public class SubsystemService {
                 requested, requestedBy, structureElement.getComment());
         subsystemRepository.createSubsystem(subsystem);
 
-        return StructureElementUtil.getStructureElementRequested(subsystem);
+        return new StructureElementNotification(
+                StructureElementUtil.getStructureElementRequested(subsystem),
+                NotificationUtil.prepareNotification(Type.SUBSYSTEM, StructureCommand.DELETE, null, null, subsystem, holder));
     }
 
     @Transactional(propagation = Propagation.MANDATORY)
-    public StructureElement approveStructure(StructureElementCommand structureElement,
+    public StructureElementNotification approveStructure(StructureElementCommand structureElement,
             Date processed, String processedBy,
             HolderSystemDeviceStructure holder) {
         // validation outside method
@@ -114,7 +117,6 @@ public class SubsystemService {
         //         update not latest
         //         find
         //         approve - update structure to status APPROVED, latest to true
-        //         possibly validate that approved
         //         additional
         //             find out previous to find out what is approved - create update delete
         //             approve create
@@ -131,7 +133,7 @@ public class SubsystemService {
         //                 keep related names - legacy names
         //                 delete sub structures
         //     return
-        //         structure element for approved structure
+        //         structure element for approved structure + notification
 
         String uuid = structureElement.getUuid().toString();
         String processedComment = structureElement.getComment();
@@ -159,16 +161,13 @@ public class SubsystemService {
         subsystem.setLatest(Boolean.TRUE);
         subsystemRepository.updateSubsystem(subsystem);
 
-        // additional
-        // find out previous
+        // previous
         List<Subsystem> previouses = iSubsystemRepository.findPreviousByUuidAndId(uuid, subsystem.getId());
         StructureCommand structureCommandCUD = StructureUtil.getStructureCommandCUD(previouses, subsystem);
         Subsystem previous = previouses != null && !previouses.isEmpty() ? previouses.get(0) : null;
         LOGGER.log(Level.FINE, "approveStructure, structureCommandCUD:        {0}", structureCommandCUD);
 
-        // approve create
-        // approve update
-        // approve delete
+        // additional
         if (StructureCommand.CREATE.equals(structureCommandCUD)) {
             boolean existsName = !StringUtils.isEmpty(subsystem.getMnemonic()) && namesService.existsName(StructureUtil.getMnemonicPath(subsystem, holder));
             if (!existsName) {
@@ -183,7 +182,9 @@ public class SubsystemService {
         }
 
         LOGGER.log(Level.FINE, "approveStructure, structureElement:           {0}", structureElement);
-        return StructureElementUtil.getStructureElementProcessed(subsystem, holder, StructureChoice.STRUCTURE);
+        return new StructureElementNotification(
+                StructureElementUtil.getStructureElementProcessed(subsystem, holder, StructureChoice.STRUCTURE),
+                NotificationUtil.prepareNotification(Type.SUBSYSTEM, structureCommandCUD, StructureCommand.APPROVE, previous, subsystem, holder));
     }
 
 }
diff --git a/src/main/java/org/openepics/names/service/SystemGroupService.java b/src/main/java/org/openepics/names/service/SystemGroupService.java
index 6ebabd1091e3025fec63b378d005eaa7e5bf945e..7ead3476975391713d41d2cdb51248b7e5478f25 100644
--- a/src/main/java/org/openepics/names/service/SystemGroupService.java
+++ b/src/main/java/org/openepics/names/service/SystemGroupService.java
@@ -33,7 +33,6 @@ import org.openepics.names.rest.beans.Status;
 import org.openepics.names.rest.beans.Type;
 import org.openepics.names.rest.beans.element.NameElement;
 import org.openepics.names.rest.beans.element.NameElementCommand;
-import org.openepics.names.rest.beans.element.StructureElement;
 import org.openepics.names.rest.beans.element.StructureElementCommand;
 import org.openepics.names.util.HolderSystemDeviceStructure;
 import org.openepics.names.util.StructureChoice;
@@ -41,6 +40,7 @@ import org.openepics.names.util.StructureCommand;
 import org.openepics.names.util.StructureElementUtil;
 import org.openepics.names.util.StructureUtil;
 import org.openepics.names.util.ValidateUtil;
+import org.openepics.names.util.notification.NotificationUtil;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Propagation;
@@ -84,16 +84,16 @@ public class SystemGroupService {
     }
 
     @Transactional(propagation = Propagation.MANDATORY)
-    public StructureElement deleteStructure(StructureElementCommand structureElement,
-            Date requested, String requestedBy) {
+    public StructureElementNotification deleteStructure(StructureElementCommand structureElement,
+            Date requested, String requestedBy,
+            HolderSystemDeviceStructure holder) {
         // validation outside method
         // transaction
         //     do
         //         find
         //         create structure to pending, not latest, deleted, with data
-        //         possibly validate that deleted
         //     return
-        //         structure element for deleted structure
+        //         structure element for deleted structure + notification
 
         String uuid = structureElement.getUuid().toString();
 
@@ -111,11 +111,13 @@ public class SystemGroupService {
                 requested, requestedBy, structureElement.getComment());
         systemGroupRepository.createSystemGroup(systemGroup);
 
-        return StructureElementUtil.getStructureElementRequested(systemGroup);
+        return new StructureElementNotification(
+                StructureElementUtil.getStructureElementRequested(systemGroup),
+                NotificationUtil.prepareNotification(Type.SYSTEMGROUP, StructureCommand.DELETE, null, null, systemGroup, holder));
     }
 
     @Transactional(propagation = Propagation.MANDATORY)
-    public StructureElement approveStructure(StructureElementCommand structureElement,
+    public StructureElementNotification approveStructure(StructureElementCommand structureElement,
             Date processed, String processedBy,
             HolderSystemDeviceStructure holder) {
         // validation outside method
@@ -125,7 +127,6 @@ public class SystemGroupService {
         //         update not latest
         //         find
         //         approve - update structure to status APPROVED, latest to true
-        //         possibly validate that approved
         //         additional
         //             find out previous to find out what is approved - create update delete
         //             approve create
@@ -142,7 +143,7 @@ public class SystemGroupService {
         //                 keep related names - legacy names
         //                 delete sub structures
         //     return
-        //         structure element for approved structure
+        //         structure element for approved structure + notification
 
         String uuid = structureElement.getUuid().toString();
         String processedComment = structureElement.getComment();
@@ -170,16 +171,13 @@ public class SystemGroupService {
         systemGroup.setLatest(Boolean.TRUE);
         systemGroupRepository.updateSystemGroup(systemGroup);
 
-        // additional
-        // find out previous
+        // previous
         List<SystemGroup> previouses = iSystemGroupRepository.findPreviousByUuidAndId(uuid, systemGroup.getId());
         StructureCommand structureCommandCUD = StructureUtil.getStructureCommandCUD(previouses, systemGroup);
         SystemGroup previous = previouses != null && !previouses.isEmpty() ? previouses.get(0) : null;
         LOGGER.log(Level.INFO, "approveStructure, structureCommandCUD:        {0}", structureCommandCUD);
 
-        // approve create
-        // approve update
-        // approve delete
+        // additional
         if (StructureCommand.CREATE.equals(structureCommandCUD)) {
             boolean existsName = !StringUtils.isEmpty(systemGroup.getMnemonic()) && namesService.existsName(StructureUtil.getMnemonicPath(systemGroup, holder));
             if (!existsName) {
@@ -198,14 +196,16 @@ public class SystemGroupService {
                 structureElementCommands.add(new StructureElementCommand(system.getUuid(), Type.SYSTEM, null, null, null, null, StructuresService.DELETE_AFTER_APPROVE_STRUCTURE_CHANGE));
             }
             for (StructureElementCommand structureElementCommand : structureElementCommands) {
-                systemService.deleteStructure(structureElementCommand, processed, processedBy);
+                systemService.deleteStructure(structureElementCommand, processed, processedBy, holder);
             }
             for (StructureElementCommand structureElementCommand : structureElementCommands) {
                 systemService.approveStructure(structureElementCommand, processed, processedBy, holder);
             }
         }
 
-        return StructureElementUtil.getStructureElementProcessed(systemGroup, holder, StructureChoice.STRUCTURE);
+        return new StructureElementNotification(
+                StructureElementUtil.getStructureElementProcessed(systemGroup, holder, StructureChoice.STRUCTURE),
+                NotificationUtil.prepareNotification(Type.SYSTEMGROUP, structureCommandCUD, StructureCommand.APPROVE, previous, systemGroup, holder));
     }
 
 }
diff --git a/src/main/java/org/openepics/names/service/SystemService.java b/src/main/java/org/openepics/names/service/SystemService.java
index 05ab5b078ac94dde257bd6cc07095e1346218c31..13535726e3f2ddd6ff55698c148aa24d591a28aa 100644
--- a/src/main/java/org/openepics/names/service/SystemService.java
+++ b/src/main/java/org/openepics/names/service/SystemService.java
@@ -33,7 +33,6 @@ import org.openepics.names.rest.beans.Status;
 import org.openepics.names.rest.beans.Type;
 import org.openepics.names.rest.beans.element.NameElement;
 import org.openepics.names.rest.beans.element.NameElementCommand;
-import org.openepics.names.rest.beans.element.StructureElement;
 import org.openepics.names.rest.beans.element.StructureElementCommand;
 import org.openepics.names.util.HolderSystemDeviceStructure;
 import org.openepics.names.util.StructureChoice;
@@ -41,6 +40,7 @@ import org.openepics.names.util.StructureCommand;
 import org.openepics.names.util.StructureElementUtil;
 import org.openepics.names.util.StructureUtil;
 import org.openepics.names.util.ValidateUtil;
+import org.openepics.names.util.notification.NotificationUtil;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Propagation;
@@ -84,16 +84,16 @@ public class SystemService {
     }
 
     @Transactional(propagation = Propagation.MANDATORY)
-    public StructureElement deleteStructure(StructureElementCommand structureElement,
-            Date requested, String requestedBy) {
+    public StructureElementNotification deleteStructure(StructureElementCommand structureElement,
+            Date requested, String requestedBy,
+            HolderSystemDeviceStructure holder) {
         // validation outside method
         // transaction
         //     do
         //         find
         //         create structure to pending, not latest, deleted, with data
-        //         possibly validate that deleted
         //     return
-        //         structure element for deleted structure
+        //         structure element for deleted structure + notification
 
         String uuid = structureElement.getUuid().toString();
 
@@ -111,11 +111,13 @@ public class SystemService {
                 requested, requestedBy, structureElement.getComment());
         systemRepository.createSystem(system);
 
-        return StructureElementUtil.getStructureElementRequested(system);
+        return new StructureElementNotification(
+                StructureElementUtil.getStructureElementRequested(system),
+                NotificationUtil.prepareNotification(Type.SYSTEM, StructureCommand.DELETE, null, null, system, holder));
     }
 
     @Transactional(propagation = Propagation.MANDATORY)
-    public StructureElement approveStructure(StructureElementCommand structureElement,
+    public StructureElementNotification approveStructure(StructureElementCommand structureElement,
             Date processed, String processedBy,
             HolderSystemDeviceStructure holder) {
         // validation outside method
@@ -125,7 +127,6 @@ public class SystemService {
         //         update not latest
         //         find
         //         approve - update structure to status APPROVED, latest to true
-        //         possibly validate that approved
         //         additional
         //             find out previous to find out what is approved - create update delete
         //             approve create
@@ -142,7 +143,7 @@ public class SystemService {
         //                 keep related names - legacy names
         //                 delete sub structures
         //     return
-        //         structure element for approved structure
+        //         structure element for approved structure + notification
 
         String uuid = structureElement.getUuid().toString();
         String processedComment = structureElement.getComment();
@@ -170,16 +171,13 @@ public class SystemService {
         system.setLatest(Boolean.TRUE);
         systemRepository.updateSystem(system);
 
-        // additional
-        // find out previous
+        // previous
         List<System> previouses = iSystemRepository.findPreviousByUuidAndId(uuid, system.getId());
         StructureCommand structureCommandCUD = StructureUtil.getStructureCommandCUD(previouses, system);
         System previous = previouses != null && !previouses.isEmpty() ? previouses.get(0) : null;
         LOGGER.log(Level.FINE, "approveStructure, structureCommandCUD:        {0}", structureCommandCUD);
 
-        // approve create
-        // approve update
-        // approve delete
+        // additional
         if (StructureCommand.CREATE.equals(structureCommandCUD)) {
             boolean existsName = !StringUtils.isEmpty(system.getMnemonic()) && namesService.existsName(StructureUtil.getMnemonicPath(system, holder));
             if (!existsName) {
@@ -198,7 +196,7 @@ public class SystemService {
                 structureElementCommands.add(new StructureElementCommand(subsystem.getUuid(), Type.SUBSYSTEM, null, null, null, null, StructuresService.DELETE_AFTER_APPROVE_STRUCTURE_CHANGE));
             }
             for (StructureElementCommand structureElementCommand : structureElementCommands) {
-                subsystemService.deleteStructure(structureElementCommand, processed, processedBy);
+                subsystemService.deleteStructure(structureElementCommand, processed, processedBy, holder);
             }
             for (StructureElementCommand structureElementCommand : structureElementCommands) {
                 subsystemService.approveStructure(structureElementCommand, processed, processedBy, holder);
@@ -206,7 +204,9 @@ public class SystemService {
         }
 
         LOGGER.log(Level.FINE, "approveStructure, structureElement:           {0}", structureElement);
-        return StructureElementUtil.getStructureElementProcessed(system, holder, StructureChoice.STRUCTURE);
+        return new StructureElementNotification(
+                StructureElementUtil.getStructureElementProcessed(system, holder, StructureChoice.STRUCTURE),
+                NotificationUtil.prepareNotification(Type.SYSTEM, structureCommandCUD, StructureCommand.APPROVE, previous, system, holder));
     }
 
 }
diff --git a/src/main/java/org/openepics/names/util/notification/NotificationName.java b/src/main/java/org/openepics/names/util/notification/NotificationName.java
new file mode 100644
index 0000000000000000000000000000000000000000..8a3afbf92d7a25108bde242dab43475961ff7d55
--- /dev/null
+++ b/src/main/java/org/openepics/names/util/notification/NotificationName.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2023 European Spallation Source ERIC.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package org.openepics.names.util.notification;
+
+import java.util.Date;
+import java.util.UUID;
+
+import org.openepics.names.util.NameCommand;
+
+/**
+ * Utility class to store data for user notification of changes for name in Naming.
+ *
+ * @author Lars Johansson
+ */
+public class NotificationName {
+
+    // cud - create update delete
+
+    private NameCommand nameCommandCUD;
+    private UUID uuid;
+    private String oldIndex;
+    private String oldName;
+    private String oldDescription;
+    private String newIndex;
+    private String newName;
+    private String newDescription;
+    private String comment;
+    private Date when;
+    private String who;
+
+    /**
+     * Public constructor.
+     *
+     * @param nameCommandCUD name command (create update delete)
+     * @param uuid uuid
+     * @param oldIndex old index
+     * @param oldName old name
+     * @param oldDescription old description
+     * @param newIndex new index
+     * @param newName new name
+     * @param newDescription new description
+     * @param comment comment
+     * @param when when
+     * @param who who
+     */
+    public NotificationName(NameCommand nameCommandCUD,
+            UUID uuid,
+            String oldIndex, String oldName, String oldDescription,
+            String newIndex, String newName, String newDescription,
+            String comment, Date when, String who) {
+        super();
+        this.nameCommandCUD = nameCommandCUD;
+        this.uuid = uuid;
+        this.oldIndex = oldIndex;
+        this.oldName = oldName;
+        this.oldDescription = oldDescription;
+        this.newIndex = newIndex;
+        this.newName = newName;
+        this.newDescription = newDescription;
+        this.comment = comment;
+        this.when = when;
+        this.who = who;
+    }
+
+    public NameCommand getNameCommandCUD() {
+        return nameCommandCUD;
+    }
+    public UUID getUuid() {
+        return uuid;
+    }
+    public String getOldIndex() {
+        return oldIndex;
+    }
+    public String getOldName() {
+        return oldName;
+    }
+    public String getOldDescription() {
+        return oldDescription;
+    }
+    public String getNewIndex() {
+        return newIndex;
+    }
+    public String getNewName() {
+        return newName;
+    }
+    public String getNewDescription() {
+        return newDescription;
+    }
+    public String getComment() {
+        return comment;
+    }
+    public Date getWhen() {
+        return when;
+    }
+    public String getWho() {
+        return who;
+    }
+
+}
diff --git a/src/main/java/org/openepics/names/util/notification/NotificationScheduler.java b/src/main/java/org/openepics/names/util/notification/NotificationScheduler.java
new file mode 100644
index 0000000000000000000000000000000000000000..5ccb428e8a64fc84a6afdfd7a4a55a7eb8b54e14
--- /dev/null
+++ b/src/main/java/org/openepics/names/util/notification/NotificationScheduler.java
@@ -0,0 +1,251 @@
+/*
+ * Copyright (C) 2023 European Spallation Source ERIC.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package org.openepics.names.util.notification;
+
+import java.text.MessageFormat;
+import java.util.Calendar;
+import java.util.Collections;
+import java.util.Date;
+import java.util.List;
+import java.util.Set;
+import java.util.TreeSet;
+import java.util.UUID;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import org.openepics.names.repository.INameRepository;
+import org.openepics.names.repository.model.Name;
+import org.openepics.names.service.NotificationService;
+import org.openepics.names.util.NameCommand;
+import org.openepics.names.util.ValidateUtil;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.scheduling.annotation.EnableScheduling;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Component;
+
+import com.google.common.collect.Lists;
+
+/**
+ * Scheduler class to notify users and administrators about changes for names and structures in Naming.
+ *
+ * @author Lars Johansson
+ */
+@Component
+@EnableScheduling
+public class NotificationScheduler {
+
+    private static final Logger LOGGER = Logger.getLogger(NotificationScheduler.class.getName());
+
+    private static final String NOTIFY_NAMES_FROM_TO_CREATED_UPDATED_DELETED = "Notify names, {0} - {1}, # created: {2}, # updated: {3}, # deleted: {4}";
+
+    @Autowired
+    private INameRepository iNameRepository;
+    @Autowired
+    private NotificationService notificationService;
+
+    /**
+     * Notify administrators about changes for names and structures.
+     * Intention is to inform about changes for names (create, update, delete).
+     * It is to be checked once a day, and notifications to be generated with emails.
+     * Emails is to have limited size of entries and another email is to be generated with if size is greater than limit.
+     *
+     * <br/><br/>
+     * Scheduling is handled with cron expression.
+     */
+    @Scheduled(cron = "0 0 5 * * *")
+    public void notifyAdministrators() {
+        notifyAdministratorsForNames();
+    }
+
+    /**
+     * Find out which names that are created, updated or deleted the previous day
+     * and send notification to administrators for those names.
+     */
+    private void notifyAdministratorsForNames() {
+        // prepare
+        // find names for previous day
+        // find changes for names
+        //     split into create, update, delete
+        //     prepare notification
+        // log
+        // send notification
+        //     one email with sections for create, update, delete
+
+        // prepare
+        //     dates for previous day
+        //     created
+        //     updated
+        //     deleted
+        Date from = getPreviousDayFrom();
+        Date to   = getPreviousDayTo();
+        List<NotificationName> created = Lists.newArrayList();
+        List<NotificationName> updated = Lists.newArrayList();
+        List<NotificationName> deleted = Lists.newArrayList();
+
+        // find names for previous day
+        //     uuid ascending, id ascending
+        //     distinct uuids
+        List<Name> names = iNameRepository.findByRequestedBetween(from, to);
+        Set<UUID> uuids = new TreeSet<>();
+        for (Name name : names) {
+            uuids.add(name.getUuid());
+        }
+
+        // find changes for names
+        //     (possibly one notification for uuid)
+        //     per uuid
+        //         prepare
+        //             extract names per uuid (previous day)
+        //             get names by uuid
+        //             sort by id ascending
+        //             step in list until same id
+        //         compare
+        //             extracted names to names by uuid
+        //             split into
+        //                 created
+        //                 updated
+        //                 deleted
+        for (UUID uuid : uuids) {
+            // prepare
+            List<Name> extracted = extractByUuid(names, uuid);
+            List<Name> namesByUuid = iNameRepository.findByUuid(uuid.toString());
+            sortById(extracted);
+            sortById(namesByUuid);
+
+            int index = -1;
+            for (int i=0; i<namesByUuid.size(); i++) {
+                if (extracted.get(0).getId().equals(namesByUuid.get(0).getId())) {
+                    index = i;
+                    break;
+                }
+            }
+
+            // compare
+            for (int i=0; i<extracted.size(); i++) {
+                // created - no previous
+                // updated - is not latest
+                // deleted - is latest and is deleted
+                Name name = extracted.get(i);
+                if (i == 0 && index == 0) {
+                    NotificationUtil.prepareAddNotification(created, NameCommand.CREATE, null, name);
+                } else if (Boolean.TRUE.equals(name.isLatest()) && Boolean.TRUE.equals(name.isDeleted())) {
+                    NotificationUtil.prepareAddNotification(deleted, NameCommand.DELETE, null, name);
+                } else {
+                    NotificationUtil.prepareAddNotification(updated, NameCommand.UPDATE, findPreviousByName(namesByUuid, name), name);
+                }
+            }
+        }
+
+        // log
+        LOGGER.log(Level.INFO,
+                () -> MessageFormat.format(
+                        NOTIFY_NAMES_FROM_TO_CREATED_UPDATED_DELETED,
+                        from.toString(),
+                        to.toString(),
+                        created.size(),
+                        updated.size(),
+                        deleted.size()));
+
+        // send notification
+        notificationService.notifyNames(created, updated, deleted);
+    }
+
+    /**
+     * Return date for beginning of previous day.
+     *
+     * @return date date for beginning of previous day
+     */
+    private Date getPreviousDayFrom() {
+        Calendar cal = Calendar.getInstance();
+        cal.add(Calendar.DAY_OF_YEAR, -1);
+        cal.set(Calendar.HOUR_OF_DAY, 0);
+        cal.set(Calendar.MINUTE, 0);
+        cal.set(Calendar.SECOND, 0);
+        cal.set(Calendar.MILLISECOND, 0);
+        return cal.getTime();
+    }
+
+    /**
+     * Return date for end of previous day.
+     *
+     * @return date for end of previous day
+     */
+    private Date getPreviousDayTo() {
+        Calendar cal = Calendar.getInstance();
+        cal.add(Calendar.DAY_OF_YEAR, -1);
+        cal.set(Calendar.HOUR_OF_DAY, 23);
+        cal.set(Calendar.MINUTE, 59);
+        cal.set(Calendar.SECOND, 59);
+        cal.set(Calendar.MILLISECOND, 999);
+        return cal.getTime();
+    }
+
+    /**
+     * Extract names that contain given uuid from list of names.
+     *
+     * @param list list of names
+     * @param uuid uuid
+     * @return list of names
+     */
+    private List<Name> extractByUuid(List<Name> list, UUID uuid) {
+        List<Name> extracted = Lists.newArrayList();
+        if (list != null && !list.isEmpty() && uuid != null) {
+            for (Name name : list) {
+                if (uuid.equals(name.getUuid())) {
+                    extracted.add(name);
+                }
+            }
+        }
+        return extracted;
+    }
+
+    /**
+     * Utility method to sort names according to id ascending.
+     *
+     * @param names names
+     */
+    private void sortById(List<Name> names) {
+        Collections.sort(names, (Name arg0, Name arg1) -> arg0.getId().compareTo(arg1.getId()));
+    }
+
+    /**
+     * Return name previous to given name in list.
+     *
+     * @param names list of names
+     * @param name name
+     * @return name
+     */
+    private Name findPreviousByName(List<Name> names, Name name) {
+        if (ValidateUtil.isAnyNull(names, name)) {
+            return null;
+        }
+
+        // sorted list
+        for (int i=0; i<names.size(); i++) {
+            if (name.getId().equals(names.get(i).getId())) {
+                return i > 0
+                        ? names.get(i-1)
+                        : null;
+            }
+        }
+
+        return null;
+    }
+
+}
diff --git a/src/main/java/org/openepics/names/util/notification/NotificationStructure.java b/src/main/java/org/openepics/names/util/notification/NotificationStructure.java
new file mode 100644
index 0000000000000000000000000000000000000000..ffaf105108b132077042f736392e6fcaaca1b38f
--- /dev/null
+++ b/src/main/java/org/openepics/names/util/notification/NotificationStructure.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2023 European Spallation Source ERIC.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package org.openepics.names.util.notification;
+
+import java.util.Date;
+import java.util.UUID;
+
+import org.openepics.names.rest.beans.Type;
+import org.openepics.names.util.StructureCommand;
+
+/**
+ * Utility class to store data for user notification of change for structure in Naming.
+ *
+ * @author Lars Johansson
+ */
+public class NotificationStructure {
+
+    // cud - create update delete
+    // acr - approve cancel reject
+
+    private StructureCommand structureCommandCUD;
+    private StructureCommand structureCommandACR;
+    private UUID uuid;
+    private Type type;
+    private String oldName;
+    private String oldMnemonic;
+    private String oldMnemonicpath;
+    private String oldDescription;
+    private String newName;
+    private String newMnemonic;
+    private String newMnemonicpath;
+    private String newDescription;
+    private String comment;
+    private Date when;
+    private String who;
+
+    /**
+     * Public constructor.
+     *
+     * @param structureCommandCUD structure command (create update delete)
+     * @param structureCommandACR structure command (approve cancel reject)
+     * @param type type
+     * @param uuid uuid
+     * @param oldName old name
+     * @param oldMnemonic old mnemonic
+     * @param oldMnemonicpath old mnemonic path
+     * @param oldDescription old description
+     * @param newName new name
+     * @param newMnemonic new mnemonic
+     * @param newMnemonicpath new mnemonic path
+     * @param newDescription new description
+     * @param comment comment
+     * @param when when
+     * @param who who
+     */
+    public NotificationStructure(StructureCommand structureCommandCUD, StructureCommand structureCommandACR,
+            Type type, UUID uuid,
+            String oldName, String oldMnemonic, String oldMnemonicpath, String oldDescription,
+            String newName, String newMnemonic, String newMnemonicpath, String newDescription,
+            String comment, Date when, String who) {
+        super();
+        this.structureCommandCUD = structureCommandCUD;
+        this.structureCommandACR = structureCommandACR;
+        this.type = type;
+        this.uuid = uuid;
+        this.oldName = oldName;
+        this.oldMnemonic = oldMnemonic;
+        this.oldMnemonicpath = oldMnemonicpath;
+        this.oldDescription = oldDescription;
+        this.newName = newName;
+        this.newMnemonic = newMnemonic;
+        this.newMnemonicpath = newMnemonicpath;
+        this.newDescription = newDescription;
+        this.comment = comment;
+        this.when = when;
+        this.who = who;
+    }
+
+    public StructureCommand getStructureCommandCUD() {
+        return structureCommandCUD;
+    }
+    public StructureCommand getStructureCommandACR() {
+        return structureCommandACR;
+    }
+    public Type getType() {
+        return type;
+    }
+    public UUID getUuid() {
+        return uuid;
+    }
+    public String getOldName() {
+        return oldName;
+    }
+    public String getOldMnemonic() {
+        return oldMnemonic;
+    }
+    public String getOldMnemonicpath() {
+        return oldMnemonicpath;
+    }
+    public String getOldDescription() {
+        return oldDescription;
+    }
+    public String getNewName() {
+        return newName;
+    }
+    public String getNewMnemonic() {
+        return newMnemonic;
+    }
+    public String getNewMnemonicpath() {
+        return newMnemonicpath;
+    }
+    public String getNewDescription() {
+        return newDescription;
+    }
+    public String getComment() {
+        return comment;
+    }
+    public Date getWhen() {
+        return when;
+    }
+    public String getWho() {
+        return who;
+    }
+
+}
diff --git a/src/main/java/org/openepics/names/util/notification/NotificationUtil.java b/src/main/java/org/openepics/names/util/notification/NotificationUtil.java
new file mode 100644
index 0000000000000000000000000000000000000000..4b1d95d43a18740fe7481ea3ca705dd618cc6ff4
--- /dev/null
+++ b/src/main/java/org/openepics/names/util/notification/NotificationUtil.java
@@ -0,0 +1,352 @@
+/*
+ * Copyright (C) 2023 European Spallation Source ERIC.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package org.openepics.names.util.notification;
+
+import java.util.Collections;
+import java.util.Date;
+import java.util.List;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import org.apache.commons.lang3.StringUtils;
+import org.openepics.names.repository.model.DeviceGroup;
+import org.openepics.names.repository.model.DeviceType;
+import org.openepics.names.repository.model.Discipline;
+import org.openepics.names.repository.model.Name;
+import org.openepics.names.repository.model.Structure;
+import org.openepics.names.repository.model.Subsystem;
+import org.openepics.names.repository.model.System;
+import org.openepics.names.repository.model.SystemGroup;
+import org.openepics.names.rest.beans.Type;
+import org.openepics.names.util.HolderSystemDeviceStructure;
+import org.openepics.names.util.NameCommand;
+import org.openepics.names.util.StructureCommand;
+import org.openepics.names.util.StructureUtil;
+import org.openepics.names.util.ValidateUtil;
+
+/**
+ * Utility class to assist in handling of notifications.
+ *
+ * @author Lars Johansson
+ */
+public class NotificationUtil {
+
+    private static final Logger LOGGER = Logger.getLogger(NotificationUtil.class.getName());
+
+    private static final String EMPTY = "";
+
+    /**
+     * This class is not to be instantiated.
+     */
+    private NotificationUtil() {
+        throw new IllegalStateException("Utility class");
+    }
+
+    /**
+     * Highly specialized method to find out kind of structure command (create, update, delete) for notification purposes.
+     * Intended to be used for notification purposes in approve, cancel, reject operations.
+     *
+     * @param previous list of previous structures
+     * @param structure structure
+     * @return structure command
+     */
+    public static StructureCommand getStructureCommandCUD(List<?> previous, Structure structure) {
+        if (previous == null || previous.isEmpty()) {
+            return StructureCommand.CREATE;
+        } else if (structure != null && Boolean.TRUE.equals(structure.isDeleted())) {
+            return StructureCommand.DELETE;
+        } else {
+            return StructureCommand.UPDATE;
+        }
+    }
+
+    /**
+     * Prepare notification and add to list of notifications if notification is available.
+     *
+     * @param notifications list of notifications
+     * @param nameCommand name command
+     * @param previous previous (name)
+     * @param name name
+     */
+    public static void prepareAddNotification(List<NotificationName> notifications, NameCommand nameCommand, Name previous, Name name) {
+        if (notifications == null) {
+            return;
+        }
+
+        NotificationName notification = NotificationUtil.prepareNotification(nameCommand, previous, name);
+        if (notification != null) {
+            notifications.add(notification);
+        }
+    }
+
+    /**
+     * Prepare and return notification for name with information about change.
+     * If information is not correct, <tt>null</tt> is returned.
+     *
+     * @param nameCommand name command
+     * @param previous previous (name)
+     * @param name name
+     * @return notification
+     */
+    public static NotificationName prepareNotification(NameCommand nameCommand, Name previous, Name name) {
+        LOGGER.log(Level.FINE, "prepareNotification, nameCommand: {0}", nameCommand);
+        LOGGER.log(Level.FINE, "prepareNotification, previous:    {0}", previous);
+        LOGGER.log(Level.FINE, "prepareNotification, name:        {0}", name);
+
+        if (ValidateUtil.isAnyNull(nameCommand, name)) {
+            return null;
+        }
+        if (ValidateUtil.isAnyEqual(nameCommand, NameCommand.UPDATE) && previous == null) {
+            return null;
+        }
+
+        // notify
+        //     combinations of commands and names
+        //     about information going from previous to present
+        //     to be able to notify about change
+        //     ------------------------------------------------
+        //     create                   name
+        //     update        previous + name
+        //     delete                   name
+        // support fields
+        //     name
+        //     description
+
+        String oldIndex = EMPTY;
+        String oldName = EMPTY;
+        String oldDescription = EMPTY;
+        String newIndex = StringUtils.stripToEmpty(name.getInstanceIndex());
+        String newName = StringUtils.stripToEmpty(name.getConventionName());
+        String newDescription = StringUtils.stripToEmpty(name.getDescription());
+
+        if (ValidateUtil.isAnyEqual(nameCommand, NameCommand.UPDATE)) {
+            oldIndex = StringUtils.stripToEmpty(previous.getInstanceIndex());
+            oldName = StringUtils.stripToEmpty(previous.getConventionName());
+            oldDescription = StringUtils.stripToEmpty(previous.getDescription());
+        }
+
+        return new NotificationName(
+                nameCommand,
+                name.getUuid(),
+                oldIndex,
+                oldName,
+                oldDescription,
+                newIndex,
+                newName,
+                newDescription,
+                name.getRequestedComment(),
+                name.getRequested(),
+                name.getRequestedBy());
+    }
+
+    /**
+     * Prepare notification and add to list of notifications if notification is available.
+     *
+     * @param notifications list of notifications
+     * @param type type
+     * @param structureCommandCUD structure command (create update delete)
+     * @param structureCommandACR structure command (approve cancel reject)
+     * @param previous previous (structure)
+     * @param structure structure
+     * @param holderSystemDeviceStructure holder of containers for system and device structure content
+     */
+    public static void prepareAddNotification(List<NotificationStructure> notifications,
+            Type type, StructureCommand structureCommandCUD, StructureCommand structureCommandACR,
+            Structure previous, Structure structure, HolderSystemDeviceStructure holderSystemDeviceStructure) {
+        if (notifications == null) {
+            return;
+        }
+
+        NotificationStructure notification = NotificationUtil.prepareNotification(type, structureCommandCUD, structureCommandACR, previous, structure, holderSystemDeviceStructure);
+        if (notification != null) {
+            notifications.add(notification);
+        }
+    }
+
+    /**
+     * Prepare and return notification for system group with information about change.
+     * If information is not correct, <tt>null</tt> is returned.
+     *
+     * @param type type
+     * @param structureCommandCUD structure command (create update delete)
+     * @param structureCommandACR structure command (approve cancel reject)
+     * @param previous previous (structure)
+     * @param structure structure
+     * @param holderSystemDeviceStructure holder of containers for system and device structure content
+     * @return notification
+     */
+    public static NotificationStructure prepareNotification(Type type, StructureCommand structureCommandCUD, StructureCommand structureCommandACR,
+            Structure previous, Structure structure, HolderSystemDeviceStructure holderSystemDeviceStructure) {
+        LOGGER.log(Level.FINE, "prepareNotification, type:                {0}", type);
+        LOGGER.log(Level.FINE, "prepareNotification, structureCommandCUD: {0}", structureCommandCUD);
+        LOGGER.log(Level.FINE, "prepareNotification, structureCommandACR: {0}", structureCommandACR);
+        LOGGER.log(Level.FINE, "prepareNotification, previous:            {0}", previous);
+        LOGGER.log(Level.FINE, "prepareNotification, structure:           {0}", structure);
+
+        if (ValidateUtil.isAnyNull(holderSystemDeviceStructure, type, structureCommandCUD, structure)) {
+            return null;
+        }
+        if (ValidateUtil.isAnyEqual(structureCommandCUD, StructureCommand.APPROVE, StructureCommand.CANCEL, StructureCommand.REJECT)) {
+            return null;
+        }
+        if (ValidateUtil.isAnyEqual(structureCommandACR, StructureCommand.CREATE, StructureCommand.UPDATE, StructureCommand.DELETE)) {
+            return null;
+        }
+        if (ValidateUtil.isAnyEqual(structureCommandCUD, StructureCommand.UPDATE) && previous == null) {
+            return null;
+        }
+
+        // type
+        //     system group
+        //     system
+        //     subsystem
+        //     discipline
+        //     device group
+        //     device type
+        // notify - structureCommandCUD + structureCommandACR
+        //     combinations of commands and structures
+        //     about information going from previous to present
+        //     to be able to notify about change
+        //     ------------------------------------------------
+        //     create                             structure
+        //     create + approve                   structure
+        //     create + cancel                    structure
+        //     create + reject                    structure
+        //     update                  previous + structure
+        //     update + approve        previous + structure
+        //     update + cancel         previous + structure
+        //     update + reject         previous + structure
+        //     delete                             structure
+        //     delete + approve                   structure
+        //     delete + cancel                    structure
+        //     delete + reject                    structure
+        // support fields
+        //     name
+        //     mnemonic
+        //     mnemonic path - to support hierarchy
+        //     description
+
+        String oldName = EMPTY;
+        String oldMnemonic = EMPTY;
+        String oldMnemonicpath = EMPTY;
+        String oldDescription = EMPTY;
+        String newName = EMPTY;
+        String newMnemonic = EMPTY;
+        String newMnemonicpath = EMPTY;
+        String newDescription = EMPTY;
+
+        String comment = EMPTY;
+        Date when = null;
+        String who = EMPTY;
+
+        if (structureCommandACR == null) {
+            comment = structure.getRequestedComment();
+            when = structure.getRequested();
+            who = structure.getRequestedBy();
+        } else {
+            comment = structure.getProcessedComment();
+            when = structure.getProcessed();
+            who = structure.getProcessedBy();
+        }
+
+        comment = StringUtils.stripToEmpty(comment);
+        who = StringUtils.stripToEmpty(who);
+
+        if (ValidateUtil.isAnyEqual(structureCommandCUD, StructureCommand.UPDATE)) {
+            if (Type.SYSTEMGROUP.equals(type)) {
+                oldMnemonicpath = StructureUtil.getMnemonicPath((SystemGroup) previous, holderSystemDeviceStructure);
+            } else if (Type.SYSTEM.equals(type)) {
+                oldMnemonicpath = StructureUtil.getMnemonicPath((System) previous, holderSystemDeviceStructure);
+            } else if (Type.SUBSYSTEM.equals(type)) {
+                oldMnemonicpath = StructureUtil.getMnemonicPath((Subsystem) previous, holderSystemDeviceStructure);
+            } else if (Type.DISCIPLINE.equals(type)) {
+                oldMnemonicpath = StructureUtil.getMnemonicPath((Discipline) previous, holderSystemDeviceStructure);
+            } else if (Type.DEVICEGROUP.equals(type)) {
+                oldMnemonicpath = StructureUtil.getMnemonicPath((DeviceGroup) previous, holderSystemDeviceStructure);
+            } else if (Type.DEVICETYPE.equals(type)) {
+                oldMnemonicpath = StructureUtil.getMnemonicPath((DeviceType) previous, holderSystemDeviceStructure);
+            }
+
+            oldName = StringUtils.stripToEmpty(previous.getName());
+            oldMnemonic = StringUtils.stripToEmpty(previous.getMnemonic());
+            oldMnemonicpath = StringUtils.stripToEmpty(oldMnemonicpath);
+            oldDescription = StringUtils.stripToEmpty(previous.getDescription());
+        }
+
+        if (Type.SYSTEMGROUP.equals(type)) {
+            newMnemonicpath = StructureUtil.getMnemonicPath((SystemGroup) structure, holderSystemDeviceStructure);
+        } else if (Type.SYSTEM.equals(type)) {
+            newMnemonicpath = StructureUtil.getMnemonicPath((System) structure, holderSystemDeviceStructure);
+        } else if (Type.SUBSYSTEM.equals(type)) {
+            newMnemonicpath = StructureUtil.getMnemonicPath((Subsystem) structure, holderSystemDeviceStructure);
+        } else if (Type.DISCIPLINE.equals(type)) {
+            newMnemonicpath = StructureUtil.getMnemonicPath((Discipline) structure, holderSystemDeviceStructure);
+        } else if (Type.DEVICEGROUP.equals(type)) {
+            newMnemonicpath = StructureUtil.getMnemonicPath((DeviceGroup) structure, holderSystemDeviceStructure);
+        } else if (Type.DEVICETYPE.equals(type)) {
+            newMnemonicpath = StructureUtil.getMnemonicPath((DeviceType) structure, holderSystemDeviceStructure);
+        }
+        newName = StringUtils.stripToEmpty(structure.getName());
+        newMnemonic = StringUtils.stripToEmpty(structure.getMnemonic());
+        newMnemonicpath = StringUtils.stripToEmpty(newMnemonicpath);
+        newDescription = StringUtils.stripToEmpty(structure.getDescription());
+
+        return new NotificationStructure(
+                structureCommandCUD,
+                structureCommandACR,
+                type,
+                structure.getUuid(),
+                oldName,
+                oldMnemonic,
+                oldMnemonicpath,
+                oldDescription,
+                newName,
+                newMnemonic,
+                newMnemonicpath,
+                newDescription,
+                comment,
+                when,
+                who);
+    }
+
+    /**
+     * Utility method to sort name notifications according to new name ascending.
+     *
+     * @param notifications name notifications
+     */
+    public static void sortByNewName(List<NotificationName> notifications) {
+        if (notifications != null) {
+            Collections.sort(notifications,
+                    (NotificationName arg0, NotificationName arg1) -> arg0.getNewName().compareTo(arg1.getNewName()));
+        }
+    }
+
+    /**
+     * Utility method to sort structure notifications according to new mnemonic path ascending.
+     *
+     * @param notifications structure notifications
+     */
+    public static void sortByNewMnemonicpath(List<NotificationStructure> notifications) {
+        if (notifications != null) {
+            Collections.sort(notifications,
+                    (NotificationStructure arg0, NotificationStructure arg1) -> arg0.getNewMnemonicpath().compareTo(arg1.getNewMnemonicpath()));
+        }
+    }
+
+}
diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties
index ab1cafdc96184096987e82a3b19c600dc4f81bf9..0ecb45c61bc8fadced08099a3009ea6530e09dad 100644
--- a/src/main/resources/application.properties
+++ b/src/main/resources/application.properties
@@ -12,6 +12,8 @@ logging.level.org.springframework.web=INFO
 spring.http.log-request-details=true
 
 # mail
+#     administrator mail is comma-separated list of email addresses
+naming.mail.administrator=${NAMING_MAIL_ADMINISTRATOR:}
 naming.mail.notification=${NAMING_MAIL_NOTIFICATION:false}
 naming.mail.from=${NAMING_MAIL_FROM:jboss@esss.se}
 naming.smtp.host=${NAMING_SMTP_HOST:mail.esss.lu.se}
diff --git a/src/main/resources/templates/notification_names.html b/src/main/resources/templates/notification_names.html
new file mode 100644
index 0000000000000000000000000000000000000000..db809bdb2e89553996ac73054522e9b0783b4b14
--- /dev/null
+++ b/src/main/resources/templates/notification_names.html
@@ -0,0 +1,200 @@
+<!DOCTYPE html>
+<html xmlns:th="http://www.thymeleaf.org" xmlns:a="http://www.w3.org/1999/html">
+    <head>
+        <title th:remove="all">There are changes in Naming for names</title>
+        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
+        <style>
+            th, td {
+                border-bottom: 1px solid #ddd;
+                padding-left: 20px;
+                text-align:left;
+            }
+            th {
+                background-color: #0094ca;
+                color: #FFFFFF;
+            }
+            table {
+                border-collapse: collapse;
+            }
+            thead {
+                border: 0;
+            }
+        </style>
+    </head>
+    <body>
+        <h2>There are changes in Naming for names</h2>
+        <br/>
+
+        <table>
+            <tr th:if="${numberCreated > 0}">
+                <td style="padding-left: 0px;">Number of created names: </td>
+                <td th:text="${numberCreated}"/>
+            </tr>
+            <tr th:if="${numberUpdated > 0}">
+                <td style="padding-left: 0px;">Number of updated names: </td>
+                <td th:text="${numberUpdated}"/>
+            </tr>
+            <tr th:if="${numberDeleted > 0}">
+                <td style="padding-left: 0px;">Number of deleted names: </td>
+                <td th:text="${numberDeleted}"/>
+            </tr>
+        </table>
+        <br/>
+
+        <div th:if="${numberCreated > 0}">
+            <div style="padding-bottom: 10px;">
+                <h3>Created</h3>
+            </div>
+
+            <table style="padding-bottom: 10px; width: 90%;">
+                <thead>
+                    <tr>
+                        <th>Create Update Delete</th>
+
+                        <th>Old</th>
+                        <th/>
+                        <th>New</th>
+
+                        <th>Comment</th>
+                        <th>When</th>
+                        <th>Who</th>
+                    </tr>
+                </thead>
+                <tbody>
+                    <tr th:each="row: ${created}">
+                        <td th:text="${row.getNameCommandCUD()}"/>
+
+                        <td style="font-weight: bold;">
+                          <div>
+                            <div th:text=${row.getOldIndex()}></div>
+                            <div th:text=${row.getOldName()}></div>
+                            <div th:text=${row.getOldDescription()}></div>
+                          </div>
+                        </td>
+                        <td style="text-align: center; white-space: nowrap;" th:text="' -> '"/>
+                        <td style="font-weight: bold;">
+                          <div>
+                            <div th:text=${row.getNewIndex()}></div>
+                            <div th:text=${row.getNewName()}></div>
+                            <div th:text=${row.getNewDescription()}></div>
+                          </div>
+                        </td>
+
+                        <td th:text="${row.getComment()}"/>
+                        <td th:text="${#dates.format(row.getWhen(), 'yyyy.MM.dd HH:mm')}"/>
+                        <td th:text="${row.getWho()}"/>
+                    </tr>
+                </tbody>
+            </table>
+            <br/>
+            <hr>
+        </div>
+
+        <div th:if="${numberUpdated > 0}">
+            <div style="padding-bottom: 10px;">
+                <h3>Updated</h3>
+            </div>
+
+            <table style="padding-bottom: 10px; width: 90%;">
+                <thead>
+                    <tr>
+                        <th>Create Update Delete</th>
+
+                        <th>Old</th>
+                        <th/>
+                        <th>New</th>
+
+                        <th>Comment</th>
+                        <th>When</th>
+                        <th>Who</th>
+                    </tr>
+                </thead>
+                <tbody>
+                    <tr th:each="row: ${updated}">
+                        <td th:text="${row.getNameCommandCUD()}"/>
+
+                        <td style="font-weight: bold;">
+                          <div>
+                            <div style="text-align: left;" th:text=${row.getOldIndex()}></div>
+                            <div style="text-align: left;" th:text=${row.getOldName()}></div>
+                            <div style="text-align: left;" th:text=${row.getOldDescription()}></div>
+                          </div>
+                        </td>
+                        <td style="text-align: center; white-space: nowrap;" th:text="' -> '"/>
+                        <td style="font-weight: bold;">
+                          <div>
+                            <div style="text-align: left;" th:text=${row.getNewIndex()}></div>
+                            <div style="text-align: left;" th:text=${row.getNewName()}></div>
+                            <div style="text-align: left;" th:text=${row.getNewDescription()}></div>
+                          </div>
+                        </td>
+
+                        <td th:text="${row.getComment()}"/>
+                        <td th:text="${#dates.format(row.getWhen(), 'yyyy.MM.dd HH:mm')}"/>
+                        <td th:text="${row.getWho()}"/>
+                    </tr>
+                </tbody>
+            </table>
+            <br/>
+            <hr>
+        </div>
+
+        <div th:if="${numberDeleted > 0}">
+            <div style="padding-bottom: 10px;">
+                <h3>Deleted</h3>
+            </div>
+
+            <table style="padding-bottom: 10px; width: 90%;">
+                <thead>
+                    <tr>
+                        <th>Create Update Delete</th>
+
+                        <th>Old</th>
+                        <th/>
+                        <th>New</th>
+
+                        <th>Comment</th>
+                        <th>When</th>
+                        <th>Who</th>
+                    </tr>
+                </thead>
+                <tbody>
+                    <tr th:each="row: ${deleted}">
+                        <td th:text="${row.getNameCommandCUD()}"/>
+
+                        <td style="font-weight: bold;">
+                          <div>
+                            <div style="text-align: left;" th:text=${row.getOldIndex()}></div>
+                            <div style="text-align: left;" th:text=${row.getOldName()}></div>
+                            <div style="text-align: left;" th:text=${row.getOldDescription()}></div>
+                          </div>
+                        </td>
+                        <td style="text-align: center; white-space: nowrap;" th:text="' -> '"/>
+                        <td style="font-weight: bold;">
+                          <div>
+                            <div style="text-align: left;" th:text=${row.getNewIndex()}></div>
+                            <div style="text-align: left;" th:text=${row.getNewName()}></div>
+                            <div style="text-align: left;" th:text=${row.getNewDescription()}></div>
+                          </div>
+                        </td>
+
+                        <td th:text="${row.getComment()}"/>
+                        <td th:text="${#dates.format(row.getWhen(), 'yyyy.MM.dd HH:mm')}"/>
+                        <td th:text="${row.getWho()}"/>
+                    </tr>
+                </tbody>
+            </table>
+            <br/>
+            <hr>
+        </div>
+
+        <div th:if="${addfooter}">
+            <div th:text="'Notifications show old and new values for Index, Name, Description.'"></div>
+            <br/>
+            <hr>
+
+            <div th:text="'Naming URL:'" style="display: inline;"></div>
+            <a th:href="@{${backendurl}}" th:text="${backendurl}"/></a>
+        </div>
+    </body>
+</html>
\ No newline at end of file
diff --git a/src/main/resources/templates/notification_structures.html b/src/main/resources/templates/notification_structures.html
new file mode 100644
index 0000000000000000000000000000000000000000..434b1a5aba5e3ff68acee0971e0a6512a7bafb4e
--- /dev/null
+++ b/src/main/resources/templates/notification_structures.html
@@ -0,0 +1,400 @@
+<!DOCTYPE html>
+<html xmlns:th="http://www.thymeleaf.org" xmlns:a="http://www.w3.org/1999/html">
+    <head>
+        <title th:remove="all">There are changes in Naming for structures</title>
+        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
+        <style>
+            th, td {
+                border-bottom: 1px solid #ddd;
+                padding-left: 20px;
+                text-align:left;
+            }
+            th {
+                background-color: #0094ca;
+                color: #FFFFFF;
+            }
+            table {
+                border-collapse: collapse;
+            }
+            thead {
+                border: 0;
+            }
+        </style>
+    </head>
+    <body>
+
+        <h2>There are changes in Naming for structures</h2>
+        <br/>
+
+        <table>
+            <tr th:if="${numberCreated > 0}">
+                <td style="padding-left: 0px;">Number of created structures: </td>
+                <td th:text="${numberCreated}"/>
+                <td>that need APPROVE, CANCEL, REJECT</td>
+            </tr>
+            <tr th:if="${numberUpdated > 0}">
+                <td style="padding-left: 0px;">Number of updated structures: </td>
+                <td th:text="${numberUpdated}"/>
+                <td>that need APPROVE, CANCEL, REJECT</td>
+            </tr>
+            <tr th:if="${numberDeleted > 0}">
+                <td style="padding-left: 0px;">Number of deleted structures: </td>
+                <td th:text="${numberDeleted}"/>
+                <td>that need APPROVE, CANCEL, REJECT</td>
+            </tr>
+            <tr th:if="${numberApproved > 0}">
+                <td style="padding-left: 0px;">Number of approved structures: </td>
+                <td th:text="${numberApproved}"/>
+            </tr>
+            <tr th:if="${numberCancelled > 0}">
+                <td style="padding-left: 0px;">Number of cancelled structures: </td>
+                <td th:text="${numberCancelled}"/>
+            </tr>
+            <tr th:if="${numberRejected > 0}">
+                <td style="padding-left: 0px;">Number of rejected structures: </td>
+                <td th:text="${numberRejected}"/>
+            </tr>
+        </table>
+        <br/>
+
+        <div th:if="${numberCreated > 0}">
+            <div style="padding-bottom: 10px;">
+                <h3>Created</h3>
+            </div>
+
+            <table style="padding-bottom: 10px; width: 90%">
+                <thead>
+                    <tr>
+                        <th>Type</th>
+                        <th>Create Update Delete</th>
+                        <th>Approve Cancel Reject</th>
+
+                        <th>Old</th>
+                        <th/>
+                        <th>New</th>
+
+                        <th>Comment</th>
+                        <th>When</th>
+                        <th>Who</th>
+                    </tr>
+                </thead>
+                <tbody>
+                    <tr th:each="row: ${created}">
+                        <td th:text="${row.getType()}"/>
+                        <td th:text="${row.getStructureCommandCUD()}"/>
+                        <td th:text="${row.getStructureCommandACR()}"/>
+
+                        <td style="font-weight: bold;">
+                          <div>
+                            <div th:text=${row.getOldName()}></div>
+                            <div th:text=${row.getOldMnemonic()}></div>
+                            <div th:text=${row.getOldMnemonicpath()}></div>
+                            <div th:text=${row.getOldDescription()}></div>
+                          </div>
+                        </td>
+                        <td style="text-align: center; white-space: nowrap;" th:text="' -> '"/>
+                        <td style="font-weight: bold;">
+                          <div>
+                            <div th:text=${row.getNewName()}></div>
+                            <div th:text=${row.getNewMnemonic()}></div>
+                            <div th:text=${row.getNewMnemonicpath()}></div>
+                            <div th:text=${row.getNewDescription()}></div>
+                          </div>
+                        </td>
+
+                        <td th:text="${row.getComment()}"/>
+                        <td th:text="${#dates.format(row.getWhen(), 'yyyy.MM.dd HH:mm')}"/>
+                        <td th:text="${row.getWho()}"/>
+                    </tr>
+                </tbody>
+            </table>
+            <br/>
+            <hr>
+        </div>
+
+        <div th:if="${numberUpdated > 0}">
+            <div style="padding-bottom: 10px;">
+                <h3>Updated</h3>
+            </div>
+
+            <table style="padding-bottom: 10px; width: 90%">
+                <thead>
+                    <tr>
+                        <th>Type</th>
+                        <th>Create Update Delete</th>
+                        <th>Approve Cancel Reject</th>
+
+                        <th>Old</th>
+                        <th/>
+                        <th>New</th>
+
+                        <th>Comment</th>
+                        <th>When</th>
+                        <th>Who</th>
+                    </tr>
+                </thead>
+                <tbody>
+                    <tr th:each="row: ${updated}">
+                        <td th:text="${row.getType()}"/>
+                        <td th:text="${row.getStructureCommandCUD()}"/>
+                        <td th:text="${row.getStructureCommandACR()}"/>
+
+                        <td style="font-weight: bold;">
+                          <div>
+                            <div th:text=${row.getOldName()}></div>
+                            <div th:text=${row.getOldMnemonic()}></div>
+                            <div th:text=${row.getOldMnemonicpath()}></div>
+                            <div th:text=${row.getOldDescription()}></div>
+                          </div>
+                        </td>
+                        <td style="text-align: center; white-space: nowrap;" th:text="' -> '"/>
+                        <td style="font-weight: bold;">
+                          <div>
+                            <div th:text=${row.getNewName()}></div>
+                            <div th:text=${row.getNewMnemonic()}></div>
+                            <div th:text=${row.getNewMnemonicpath()}></div>
+                            <div th:text=${row.getNewDescription()}></div>
+                          </div>
+                        </td>
+
+                        <td th:text="${row.getComment()}"/>
+                        <td th:text="${#dates.format(row.getWhen(), 'yyyy.MM.dd HH:mm')}"/>
+                        <td th:text="${row.getWho()}"/>
+                    </tr>
+                </tbody>
+            </table>
+            <br/>
+            <hr>
+        </div>
+
+        <div th:if="${numberDeleted > 0}">
+            <div style="padding-bottom: 10px;">
+                <h3>Deleted</h3>
+            </div>
+
+            <table style="padding-bottom: 10px; width: 90%">
+                <thead>
+                    <tr>
+                        <th>Type</th>
+                        <th>Create Update Delete</th>
+                        <th>Approve Cancel Reject</th>
+
+                        <th>Old</th>
+                        <th/>
+                        <th>New</th>
+
+                        <th>Comment</th>
+                        <th>When</th>
+                        <th>Who</th>
+                    </tr>
+                </thead>
+                <tbody>
+                    <tr th:each="row: ${deleted}">
+                        <td th:text="${row.getType()}"/>
+                        <td th:text="${row.getStructureCommandCUD()}"/>
+                        <td th:text="${row.getStructureCommandACR()}"/>
+
+                        <td style="font-weight: bold;">
+                          <div>
+                            <div th:text=${row.getOldName()}></div>
+                            <div th:text=${row.getOldMnemonic()}></div>
+                            <div th:text=${row.getOldMnemonicpath()}></div>
+                            <div th:text=${row.getOldDescription()}></div>
+                          </div>
+                        </td>
+                        <td style="text-align: center; white-space: nowrap;" th:text="' -> '"/>
+                        <td style="font-weight: bold;">
+                          <div>
+                            <div th:text=${row.getNewName()}></div>
+                            <div th:text=${row.getNewMnemonic()}></div>
+                            <div th:text=${row.getNewMnemonicpath()}></div>
+                            <div th:text=${row.getNewDescription()}></div>
+                          </div>
+                        </td>
+
+                        <td th:text="${row.getComment()}"/>
+                        <td th:text="${#dates.format(row.getWhen(), 'yyyy.MM.dd HH:mm')}"/>
+                        <td th:text="${row.getWho()}"/>
+                    </tr>
+                </tbody>
+            </table>
+            <br/>
+            <hr>
+        </div>
+
+        <div th:if="${numberApproved > 0}">
+            <div style="padding-bottom: 10px;">
+                <h3>Approved</h3>
+            </div>
+
+            <table style="padding-bottom: 10px; width: 90%">
+                <thead>
+                    <tr>
+                        <th>Type</th>
+                        <th>Create Update Delete</th>
+                        <th>Approve Cancel Reject</th>
+
+                        <th>Old</th>
+                        <th/>
+                        <th>New</th>
+
+                        <th>Comment</th>
+                        <th>When</th>
+                        <th>Who</th>
+                    </tr>
+                </thead>
+                <tbody>
+                    <tr th:each="row: ${approved}">
+                        <td th:text="${row.getType()}"/>
+                        <td th:text="${row.getStructureCommandCUD()}"/>
+                        <td th:text="${row.getStructureCommandACR()}"/>
+
+                        <td style="font-weight: bold;">
+                          <div>
+                            <div th:text=${row.getOldName()}></div>
+                            <div th:text=${row.getOldMnemonic()}></div>
+                            <div th:text=${row.getOldMnemonicpath()}></div>
+                            <div th:text=${row.getOldDescription()}></div>
+                          </div>
+                        </td>
+                        <td style="text-align: center; white-space: nowrap;" th:text="' -> '"/>
+                        <td style="font-weight: bold;">
+                          <div>
+                            <div th:text=${row.getNewName()}></div>
+                            <div th:text=${row.getNewMnemonic()}></div>
+                            <div th:text=${row.getNewMnemonicpath()}></div>
+                            <div th:text=${row.getNewDescription()}></div>
+                          </div>
+                        </td>
+
+                        <td th:text="${row.getComment()}"/>
+                        <td th:text="${#dates.format(row.getWhen(), 'yyyy.MM.dd HH:mm')}"/>
+                        <td th:text="${row.getWho()}"/>
+                    </tr>
+                </tbody>
+            </table>
+            <br/>
+            <hr>
+        </div>
+
+        <div th:if="${numberCancelled > 0}">
+            <div style="padding-bottom: 10px;">
+                <h3>Cancelled</h3>
+            </div>
+
+            <table style="padding-bottom: 10px; width: 90%">
+                <thead>
+                    <tr>
+                        <th>Type</th>
+                        <th>Create Update Delete</th>
+                        <th>Approve Cancel Reject</th>
+
+                        <th>Old</th>
+                        <th/>
+                        <th>New</th>
+
+                        <th>Comment</th>
+                        <th>When</th>
+                        <th>Who</th>
+                    </tr>
+                </thead>
+                <tbody>
+                    <tr th:each="row: ${cancelled}">
+                        <td th:text="${row.getType()}"/>
+                        <td th:text="${row.getStructureCommandCUD()}"/>
+                        <td th:text="${row.getStructureCommandACR()}"/>
+
+                        <td style="font-weight: bold;">
+                          <div>
+                            <div th:text=${row.getOldName()}></div>
+                            <div th:text=${row.getOldMnemonic()}></div>
+                            <div th:text=${row.getOldMnemonicpath()}></div>
+                            <div th:text=${row.getOldDescription()}></div>
+                          </div>
+                        </td>
+                        <td style="text-align: center; white-space: nowrap;" th:text="' -> '"/>
+                        <td style="font-weight: bold;">
+                          <div>
+                            <div th:text=${row.getNewName()}></div>
+                            <div th:text=${row.getNewMnemonic()}></div>
+                            <div th:text=${row.getNewMnemonicpath()}></div>
+                            <div th:text=${row.getNewDescription()}></div>
+                          </div>
+                        </td>
+
+                        <td th:text="${row.getComment()}"/>
+                        <td th:text="${#dates.format(row.getWhen(), 'yyyy.MM.dd HH:mm')}"/>
+                        <td th:text="${row.getWho()}"/>
+                    </tr>
+                </tbody>
+            </table>
+            <br/>
+            <hr>
+        </div>
+
+        <div th:if="${numberRejected > 0}">
+            <div style="padding-bottom: 10px;">
+                <h3>Rejected</h3>
+            </div>
+
+            <table style="padding-bottom: 10px; width: 90%">
+                <thead>
+                    <tr>
+                        <th>Type</th>
+                        <th>Create Update Delete</th>
+                        <th>Approve Cancel Reject</th>
+
+                        <th>Old</th>
+                        <th/>
+                        <th>New</th>
+
+                        <th>Comment</th>
+                        <th>When</th>
+                        <th>Who</th>
+                    </tr>
+                </thead>
+                <tbody>
+                    <tr th:each="row: ${rejected}">
+                        <td th:text="${row.getType()}"/>
+                        <td th:text="${row.getStructureCommandCUD()}"/>
+                        <td th:text="${row.getStructureCommandACR()}"/>
+
+                        <td style="font-weight: bold;">
+                          <div>
+                            <div th:text=${row.getOldName()}></div>
+                            <div th:text=${row.getOldMnemonic()}></div>
+                            <div th:text=${row.getOldMnemonicpath()}></div>
+                            <div th:text=${row.getOldDescription()}></div>
+                          </div>
+                        </td>
+                        <td style="text-align: center; white-space: nowrap;" th:text="' -> '"/>
+                        <td style="font-weight: bold;">
+                          <div>
+                            <div th:text=${row.getNewName()}></div>
+                            <div th:text=${row.getNewMnemonic()}></div>
+                            <div th:text=${row.getNewMnemonicpath()}></div>
+                            <div th:text=${row.getNewDescription()}></div>
+                          </div>
+                        </td>
+
+                        <td th:text="${row.getComment()}"/>
+                        <td th:text="${#dates.format(row.getWhen(), 'yyyy.MM.dd HH:mm')}"/>
+                        <td th:text="${row.getWho()}"/>
+                    </tr>
+                </tbody>
+            </table>
+            <br/>
+            <hr>
+        </div>
+
+        <div th:if="${addfooter}">
+            <div th:text="'Notifications show old and new values for Name, Mnemonic, Mnemonic path, Description.'"></div>
+            <br/>
+            <hr>
+
+            <div th:text="'Naming URL:'" style="display: inline;"></div>
+            <a th:href="@{${backendurl}}" th:text="${backendurl}"></a>
+        </div>
+
+    </body>
+</html>
\ No newline at end of file
diff --git a/src/test/resources/application.properties b/src/test/resources/application.properties
index 0498854c433309a03eac14e1d3c22b413dbb3723..bbcf7817e3c3fdbfa4a7c47caf2f11ce05b499a9 100644
--- a/src/test/resources/application.properties
+++ b/src/test/resources/application.properties
@@ -12,6 +12,8 @@ logging.level.org.springframework.web=INFO
 spring.http.log-request-details=true
 
 # mail
+#     administrator mail is comma-separated list of email addresses
+naming.mail.administrator=${NAMING_MAIL_ADMINISTRATOR:}
 naming.mail.notification=${NAMING_MAIL_NOTIFICATION:false}
 naming.mail.from=${NAMING_MAIL_FROM:jboss@esss.se}
 naming.smtp.host=${NAMING_SMTP_HOST:mail.esss.lu.se}