diff --git a/src/Configuration/LocalPlatformSettings.cs b/src/Configuration/LocalPlatformSettings.cs
index 8b3aadcb..e7d540cc 100644
--- a/src/Configuration/LocalPlatformSettings.cs
+++ b/src/Configuration/LocalPlatformSettings.cs
@@ -10,7 +10,7 @@ public class LocalPlatformSettings
string _localTestDataPath = null;
///
- /// The endpoint for the bridge
+ /// The path to the local storage folder
///
public string LocalTestingStorageBasePath { get; set; }
@@ -21,12 +21,16 @@ public class LocalPlatformSettings
public string BlobStorageFolder { get; set; } = "blobs/";
+ public string NotificationsStorageFolder { get; set; } = "notifications/";
+
///
/// Folder where static test data like profile, authorization, and register data is available for local testing.
///
- public string LocalTestingStaticTestDataPath {
+ public string LocalTestingStaticTestDataPath
+ {
get => _localTestDataPath;
- set {
+ set
+ {
if (!value.EndsWith(Path.DirectorySeparatorChar) &&
!value.EndsWith(Path.AltDirectorySeparatorChar))
{
@@ -47,7 +51,7 @@ public string LocalTestingStaticTestDataPath {
///
public string LocalAppMode { get; set; }
- public string DocumentDbFolder { get; set; } = "documentdb/";
+ public string DocumentDbFolder { get; set; } = "documentdb/";
public string InstanceCollectionFolder { get; set; } = "instances/";
diff --git a/src/LocalTest.csproj b/src/LocalTest.csproj
index eae18855..0932a0e4 100644
--- a/src/LocalTest.csproj
+++ b/src/LocalTest.csproj
@@ -4,6 +4,9 @@
net6.0
56f36ce2-b44b-415e-a8a5-f399a76e35b9
enable
+
+
+ $(DefineConstants);LOCALTEST
@@ -12,6 +15,7 @@
+
diff --git a/src/Notifications/API/Controllers/EmailNotificationOrdersController.cs b/src/Notifications/API/Controllers/EmailNotificationOrdersController.cs
new file mode 100644
index 00000000..aa055931
--- /dev/null
+++ b/src/Notifications/API/Controllers/EmailNotificationOrdersController.cs
@@ -0,0 +1,98 @@
+#nullable enable
+#if !LOCALTEST
+using Altinn.Notifications.Configuration;
+#endif
+using Altinn.Notifications.Core.Models;
+using Altinn.Notifications.Core.Models.Orders;
+using Altinn.Notifications.Core.Services.Interfaces;
+using Altinn.Notifications.Extensions;
+using Altinn.Notifications.Mappers;
+using Altinn.Notifications.Models;
+using Altinn.Notifications.Validators;
+
+using FluentValidation;
+
+#if !LOCALTEST
+using Microsoft.AspNetCore.Authorization;
+#endif
+using Microsoft.AspNetCore.Mvc;
+
+#if !LOCALTEST
+using Swashbuckle.AspNetCore.Annotations;
+using Swashbuckle.AspNetCore.Filters;
+#endif
+
+namespace Altinn.Notifications.Controllers;
+
+///
+/// Controller for all operations related to email notification orders
+///
+[Route("notifications/api/v1/orders/email")]
+[ApiController]
+#if !LOCALTEST
+[Authorize(Policy = AuthorizationConstants.POLICY_CREATE_SCOPE_OR_PLATFORM_ACCESS)]
+[SwaggerResponse(401, "Caller is unauthorized")]
+[SwaggerResponse(403, "Caller is not authorized to access the requested resource")]
+# endif
+public class EmailNotificationOrdersController : ControllerBase
+{
+ private readonly IValidator _validator;
+ private readonly IEmailNotificationOrderService _orderService;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public EmailNotificationOrdersController(IValidator validator, IEmailNotificationOrderService orderService)
+ {
+ _validator = validator;
+ _orderService = orderService;
+ }
+
+ ///
+ /// Add an email notification order.
+ ///
+ ///
+ /// The API will accept the request after som basic validation of the request.
+ /// The system will also attempt to verify that it will be possible to fulfill the order.
+ ///
+ /// The id of the registered notification order
+ [HttpPost]
+ [Consumes("application/json")]
+ [Produces("application/json")]
+#if !LOCALTEST
+ [SwaggerResponse(202, "The notification order was accepted", typeof(OrderIdExt))]
+ [SwaggerResponse(400, "The notification order is invalid", typeof(ValidationProblemDetails))]
+ [SwaggerResponseHeader(202, "Location", "string", "Link to access the newly created notification order.")]
+#endif
+ public async Task> Post(EmailNotificationOrderRequestExt emailNotificationOrderRequest)
+ {
+ var validationResult = _validator.Validate(emailNotificationOrderRequest);
+ if (!validationResult.IsValid)
+ {
+ validationResult.AddToModelState(ModelState);
+ return ValidationProblem(ModelState);
+ }
+
+#if LOCALTEST
+ string creator = "localtest";
+#else
+ string? creator = HttpContext.GetOrg();
+
+ if (creator == null)
+ {
+ return Forbid();
+ }
+#endif
+
+ var orderRequest = emailNotificationOrderRequest.MapToOrderRequest(creator);
+ (NotificationOrder? registeredOrder, ServiceError? error) = await _orderService.RegisterEmailNotificationOrder(orderRequest);
+
+ if (error != null)
+ {
+ return StatusCode(error.ErrorCode, error.ErrorMessage);
+ }
+
+ string selfLink = registeredOrder!.GetSelfLink();
+ return Accepted(selfLink, new OrderIdExt(registeredOrder!.Id));
+ }
+}
\ No newline at end of file
diff --git a/src/Notifications/API/Extensions/ResourceLinkExtensions.cs b/src/Notifications/API/Extensions/ResourceLinkExtensions.cs
new file mode 100644
index 00000000..a8ce73fa
--- /dev/null
+++ b/src/Notifications/API/Extensions/ResourceLinkExtensions.cs
@@ -0,0 +1,81 @@
+#nullable enable
+using Altinn.Notifications.Core.Models.Orders;
+using Altinn.Notifications.Models;
+
+namespace Altinn.Notifications.Extensions;
+
+///
+/// Extension class for ResourceLinks
+///
+public static class ResourceLinkExtensions
+{
+ private static string? _baseUri;
+
+ ///
+ /// Initializes the ResourceLinkExtensions with the base URI from settings.
+ ///
+ ///
+ /// Should be called during startup to ensure base url is set
+ ///
+ public static void Initialize(string baseUri)
+ {
+ _baseUri = baseUri;
+ }
+
+ ///
+ /// Sets the resource links on an external notification order
+ ///
+ /// Exception if class has not been initialized in Program.cs
+ public static void SetResourceLinks(this NotificationOrderExt order)
+ {
+ if (_baseUri == null)
+ {
+ throw new InvalidOperationException("ResourceLinkExtensions has not been initialized with the base URI.");
+ }
+
+ string self = _baseUri + "/notifications/api/v1/orders/" + order.Id;
+
+ order.Links = new()
+ {
+ Self = self,
+ Status = self + "/status",
+ Notifications = self + "/notifications"
+ };
+ }
+
+ ///
+ /// Gets the self link for the provided notification order
+ ///
+ /// Exception if class has not been initialized in Program.cs
+ public static void NotificationSummaryResourceLinks(this NotificationOrderWithStatusExt order)
+ {
+ if (_baseUri == null)
+ {
+ throw new InvalidOperationException("ResourceLinkExtensions has not been initialized with the base URI.");
+ }
+
+ string baseUri = $"{_baseUri}/notifications/api/v1/orders/{order!.Id}/notifications/";
+
+ if (order.NotificationsStatusSummary?.Email != null)
+ {
+ order.NotificationsStatusSummary.Email.Links = new()
+ {
+ Self = baseUri + "email"
+ };
+ }
+ }
+
+ ///
+ /// Gets the self link for the provided notification order
+ ///
+ /// Exception if class has not been initialized in Program.cs
+ public static string GetSelfLink(this NotificationOrder order)
+ {
+ if (_baseUri == null)
+ {
+ throw new InvalidOperationException("ResourceLinkExtensions has not been initialized with the base URI.");
+ }
+
+ return _baseUri + "/notifications/api/v1/orders/" + order!.Id;
+ }
+}
diff --git a/src/Notifications/API/Mappers/OrderMapper.cs b/src/Notifications/API/Mappers/OrderMapper.cs
new file mode 100644
index 00000000..c0738e15
--- /dev/null
+++ b/src/Notifications/API/Mappers/OrderMapper.cs
@@ -0,0 +1,168 @@
+#nullable enable
+using Altinn.Notifications.Core.Enums;
+using Altinn.Notifications.Core.Models;
+using Altinn.Notifications.Core.Models.Address;
+using Altinn.Notifications.Core.Models.NotificationTemplate;
+using Altinn.Notifications.Core.Models.Orders;
+using Altinn.Notifications.Extensions;
+using Altinn.Notifications.Models;
+
+namespace Altinn.Notifications.Mappers;
+
+///
+/// Mapper for
+///
+public static class OrderMapper
+{
+ ///
+ /// Maps a to a
+ ///
+ public static NotificationOrderRequest MapToOrderRequest(this EmailNotificationOrderRequestExt extRequest, string creator)
+ {
+ var emailTemplate = new EmailTemplate(null, extRequest.Subject, extRequest.Body, (EmailContentType)extRequest.ContentType);
+
+ var recipients = new List();
+
+ recipients.AddRange(
+ extRequest.Recipients.Select(r => new Recipient(string.Empty, new List() { new EmailAddressPoint(r.EmailAddress!) })));
+
+ return new NotificationOrderRequest(
+ extRequest.SendersReference,
+ creator,
+ new List() { emailTemplate },
+ extRequest.RequestedSendTime,
+ NotificationChannel.Email,
+ recipients);
+ }
+
+ ///
+ /// Maps a to a
+ ///
+ public static NotificationOrderExt MapToNotificationOrderExt(this NotificationOrder order)
+ {
+ var orderExt = new NotificationOrderExt();
+
+ orderExt.MapBaseNotificationOrder(order);
+ orderExt.Recipients = order.Recipients.MapToRecipientExt();
+
+ foreach (var template in order.Templates)
+ {
+ switch (template.Type)
+ {
+ case NotificationTemplateType.Email:
+ var emailTemplate = template! as EmailTemplate;
+
+ orderExt.EmailTemplate = new()
+ {
+ Body = emailTemplate!.Body,
+ FromAddress = emailTemplate.FromAddress,
+ ContentType = (EmailContentTypeExt)emailTemplate.ContentType,
+ Subject = emailTemplate.Subject
+ };
+
+ break;
+ default:
+ break;
+ }
+ }
+
+ orderExt.SetResourceLinks();
+ return orderExt;
+ }
+
+ ///
+ /// Maps a to a
+ ///
+ public static NotificationOrderWithStatusExt MapToNotificationOrderWithStatusExt(this NotificationOrderWithStatus order)
+ {
+ var orderExt = new NotificationOrderWithStatusExt();
+ orderExt.MapBaseNotificationOrder(order);
+
+ orderExt.ProcessingStatus = new()
+ {
+ LastUpdate = order.ProcessingStatus.LastUpdate,
+ Status = order.ProcessingStatus.Status.ToString(),
+ StatusDescription = order.ProcessingStatus.StatusDescription
+ };
+
+ if (order.NotificationStatuses.Any())
+ {
+ orderExt.NotificationsStatusSummary = new();
+ foreach (var entry in order.NotificationStatuses)
+ {
+ NotificationTemplateType notificationType = entry.Key;
+ NotificationStatus status = entry.Value;
+
+ switch (notificationType)
+ {
+ case NotificationTemplateType.Email:
+ orderExt.NotificationsStatusSummary.Email = new()
+ {
+ Generated = status.Generated,
+ Succeeded = status.Succeeded
+ };
+ break;
+ }
+ }
+
+ orderExt.NotificationSummaryResourceLinks();
+ }
+
+ return orderExt;
+ }
+
+ ///
+ /// Maps a list of to a
+ ///
+ public static NotificationOrderListExt MapToNotificationOrderListExt(this List orders)
+ {
+ NotificationOrderListExt ordersExt = new()
+ {
+ Count = orders.Count
+ };
+
+ foreach (NotificationOrder order in orders)
+ {
+ ordersExt.Orders.Add(order.MapToNotificationOrderExt());
+ }
+
+ return ordersExt;
+ }
+
+ ///
+ /// Maps a List of to a List of
+ ///
+ internal static List MapToRecipientExt(this List recipients)
+ {
+ var recipientExt = new List();
+
+ recipientExt.AddRange(
+ recipients.Select(r => new RecipientExt
+ {
+ EmailAddress = GetEmailFromAddressList(r.AddressInfo)
+ }));
+
+ return recipientExt;
+ }
+
+ private static IBaseNotificationOrderExt MapBaseNotificationOrder(this IBaseNotificationOrderExt orderExt, IBaseNotificationOrder order)
+ {
+ orderExt.Id = order.Id.ToString();
+ orderExt.SendersReference = order.SendersReference;
+ orderExt.Created = order.Created;
+ orderExt.Creator = order.Creator.ShortName;
+ orderExt.NotificationChannel = (NotificationChannelExt)order.NotificationChannel;
+ orderExt.RequestedSendTime = order.RequestedSendTime;
+
+ return orderExt;
+ }
+
+ private static string? GetEmailFromAddressList(List addressPoints)
+ {
+ var emailAddressPoint = addressPoints
+ .Find(a => a.AddressType.Equals(AddressType.Email))
+ as EmailAddressPoint;
+
+ return emailAddressPoint?.EmailAddress;
+ }
+}
diff --git a/src/Notifications/API/Models/EmailContentTypeExt.cs b/src/Notifications/API/Models/EmailContentTypeExt.cs
new file mode 100644
index 00000000..22921275
--- /dev/null
+++ b/src/Notifications/API/Models/EmailContentTypeExt.cs
@@ -0,0 +1,18 @@
+#nullable enable
+namespace Altinn.Notifications.Models;
+
+///
+/// Enum describing available content types for an email.
+///
+public enum EmailContentTypeExt
+{
+ ///
+ /// The email format is plain text.
+ ///
+ Plain,
+
+ ///
+ /// The email contains HTML elements
+ ///
+ Html
+}
diff --git a/src/Notifications/API/Models/EmailNotificationOrderRequestExt.cs b/src/Notifications/API/Models/EmailNotificationOrderRequestExt.cs
new file mode 100644
index 00000000..20153a7e
--- /dev/null
+++ b/src/Notifications/API/Models/EmailNotificationOrderRequestExt.cs
@@ -0,0 +1,58 @@
+#nullable enable
+using System.Text.Json;
+using System.Text.Json.Serialization;
+
+namespace Altinn.Notifications.Models;
+
+///
+/// Class representing an email notiication order request
+///
+///
+/// External representaion to be used in the API.
+///
+public class EmailNotificationOrderRequestExt
+{
+ ///
+ /// Gets or sets the subject of the email
+ ///
+ [JsonPropertyName("subject")]
+ public string Subject { get; set; } = string.Empty;
+
+ ///
+ /// Gets or sets the body of the email
+ ///
+ [JsonPropertyName("body")]
+ public string Body { get; set; } = string.Empty;
+
+ ///
+ /// Gets or sets the content type of the email
+ ///
+ [JsonPropertyName("contentType")]
+ public EmailContentTypeExt ContentType { get; set; } = EmailContentTypeExt.Plain;
+
+ ///
+ /// Gets or sets the send time of the email. Defaults to UtcNow.
+ ///
+ [JsonPropertyName("requestedSendTime")]
+ public DateTime RequestedSendTime { get; set; } = DateTime.UtcNow;
+
+ ///
+ /// Gets or sets the senders reference on the notification
+ ///
+ [JsonPropertyName("sendersReference")]
+ public string? SendersReference { get; set; }
+
+ ///
+ /// Gets or sets the list of recipients
+ ///
+ [JsonPropertyName("recipients")]
+ public List Recipients { get; set; } = new List();
+
+ ///
+ /// Json serialized the
+ ///
+ public string Serialize()
+ {
+ return JsonSerializer.Serialize(this);
+ }
+}
diff --git a/src/Notifications/API/Models/EmailNotificationSummaryExt.cs b/src/Notifications/API/Models/EmailNotificationSummaryExt.cs
new file mode 100644
index 00000000..99db078a
--- /dev/null
+++ b/src/Notifications/API/Models/EmailNotificationSummaryExt.cs
@@ -0,0 +1,44 @@
+#nullable enable
+using System.Text.Json.Serialization;
+
+namespace Altinn.Notifications.Core.Models.Notification
+{
+ ///
+ /// A class representing an email notification summary
+ ///
+ ///
+ /// External representaion to be used in the API.
+ ///
+ public class EmailNotificationSummaryExt
+ {
+ ///
+ /// The order id
+ ///
+ [JsonPropertyName("orderId")]
+ public Guid OrderId { get; set; }
+
+ ///
+ /// The senders reference
+ ///
+ [JsonPropertyName("sendersReference")]
+ public string? SendersReference { get; set; }
+
+ ///
+ /// The number of generated email notifications
+ ///
+ [JsonPropertyName("generated")]
+ public int Generated { get; set; }
+
+ ///
+ /// The number of email notifications that were sent successfully
+ ///
+ [JsonPropertyName("succeeded")]
+ public int Succeeded { get; set; }
+
+ ///
+ /// A list of notifications with send result
+ ///
+ [JsonPropertyName("notifications")]
+ public List Notifications { get; set; } = new List();
+ }
+}
diff --git a/src/Notifications/API/Models/EmailNotificationWithResultExt.cs b/src/Notifications/API/Models/EmailNotificationWithResultExt.cs
new file mode 100644
index 00000000..2e664978
--- /dev/null
+++ b/src/Notifications/API/Models/EmailNotificationWithResultExt.cs
@@ -0,0 +1,37 @@
+#nullable enable
+using System.Text.Json.Serialization;
+
+using Altinn.Notifications.Models;
+
+namespace Altinn.Notifications.Core.Models.Notification
+{
+ ///
+ /// EmailNotificationWithResultExt class
+ ///
+ public class EmailNotificationWithResultExt
+ {
+ ///
+ /// The notification id
+ ///
+ [JsonPropertyName("id")]
+ public Guid Id { get; set; }
+
+ ///
+ /// Boolean indicating if the sending of the notification was successful
+ ///
+ [JsonPropertyName("succeeded")]
+ public bool Succeeded { get; set; }
+
+ ///
+ /// The recipient of the notification
+ ///
+ [JsonPropertyName("recipient")]
+ public RecipientExt Recipient { get; set; } = new();
+
+ ///
+ /// The result status of the notification
+ ///
+ [JsonPropertyName("sendStatus")]
+ public StatusExt SendStatus { get; set; } = new();
+ }
+}
diff --git a/src/Notifications/API/Models/EmailTemplateExt.cs b/src/Notifications/API/Models/EmailTemplateExt.cs
new file mode 100644
index 00000000..8e2c40c4
--- /dev/null
+++ b/src/Notifications/API/Models/EmailTemplateExt.cs
@@ -0,0 +1,34 @@
+#nullable enable
+using System.Text.Json.Serialization;
+
+namespace Altinn.Notifications.Models;
+
+///
+/// Template for an email notification
+///
+public class EmailTemplateExt
+{
+ ///
+ /// Gets the from adress of emails created by the template
+ ///
+ [JsonPropertyName("fromAddress")]
+ public string FromAddress { get; set; } = string.Empty;
+
+ ///
+ /// Gets the subject of emails created by the template
+ ///
+ [JsonPropertyName("subject")]
+ public string Subject { get; set; } = string.Empty;
+
+ ///
+ /// Gets the body of emails created by the template
+ ///
+ [JsonPropertyName("body")]
+ public string Body { get; set; } = string.Empty;
+
+ ///
+ /// Gets the content type of emails created by the template
+ ///
+ [JsonPropertyName("contentType")]
+ public EmailContentTypeExt ContentType { get; set; } = EmailContentTypeExt.Plain;
+}
diff --git a/src/Notifications/API/Models/IBaseNotificationOrderExt.cs b/src/Notifications/API/Models/IBaseNotificationOrderExt.cs
new file mode 100644
index 00000000..4d7f2065
--- /dev/null
+++ b/src/Notifications/API/Models/IBaseNotificationOrderExt.cs
@@ -0,0 +1,41 @@
+#nullable enable
+namespace Altinn.Notifications.Models;
+
+///
+/// A class representing the base properties of a registered notification order.
+///
+///
+/// External representaion to be used in the API.
+///
+public interface IBaseNotificationOrderExt
+{
+ ///
+ /// Gets or sets the id of the notification order
+ ///
+ public string Id { get; set; }
+
+ ///
+ /// Gets or sets the short name of the creator of the notification order
+ ///
+ public string Creator { get; set; }
+
+ ///
+ /// Gets or sets the senders reference of the notification
+ ///
+ public string? SendersReference { get; set; }
+
+ ///
+ /// Gets or sets the requested send time of the notification
+ ///
+ public DateTime RequestedSendTime { get; set; }
+
+ ///
+ /// Gets or sets the date and time of when the notification order was created
+ ///
+ public DateTime Created { get; set; }
+
+ ///
+ /// Gets or sets the preferred notification channel of the notification order
+ ///
+ public NotificationChannelExt NotificationChannel { get; set; }
+}
diff --git a/src/Notifications/API/Models/NotificationChannelExt.cs b/src/Notifications/API/Models/NotificationChannelExt.cs
new file mode 100644
index 00000000..11bea3a2
--- /dev/null
+++ b/src/Notifications/API/Models/NotificationChannelExt.cs
@@ -0,0 +1,13 @@
+#nullable enable
+namespace Altinn.Notifications.Models;
+
+///
+/// Enum describing available notification channels.
+///
+public enum NotificationChannelExt
+{
+ ///
+ /// The selected channel for the notification is email.
+ ///
+ Email
+}
diff --git a/src/Notifications/API/Models/NotificationOrderExt.cs b/src/Notifications/API/Models/NotificationOrderExt.cs
new file mode 100644
index 00000000..90aa71c8
--- /dev/null
+++ b/src/Notifications/API/Models/NotificationOrderExt.cs
@@ -0,0 +1,56 @@
+#nullable enable
+using System.Text.Json.Serialization;
+
+namespace Altinn.Notifications.Models;
+
+///
+/// A class representing a registered notification order.
+///
+///
+/// External representaion to be used in the API.
+///
+public class NotificationOrderExt : IBaseNotificationOrderExt
+{
+ /// >
+ [JsonPropertyName("id")]
+ public string Id { get; set; } = string.Empty;
+
+ /// >
+ [JsonPropertyName("creator")]
+ public string Creator { get; set; } = string.Empty;
+
+ /// >
+ [JsonPropertyName("sendersReference")]
+ public string? SendersReference { get; set; }
+
+ /// >
+ [JsonPropertyName("requestedSendTime")]
+ public DateTime RequestedSendTime { get; set; }
+
+ /// >
+ [JsonPropertyName("created")]
+ public DateTime Created { get; set; }
+
+ /// >
+ [JsonPropertyName("notificationChannel")]
+ [JsonConverter(typeof(JsonStringEnumConverter))]
+ public NotificationChannelExt NotificationChannel { get; set; }
+
+ ///
+ /// Gets or sets the list of recipients
+ ///
+ [JsonPropertyName("recipients")]
+ public List Recipients { get; set; } = new List();
+
+ ///
+ /// Gets or sets the emailTemplate
+ ///
+ [JsonPropertyName("emailTemplate")]
+ public EmailTemplateExt? EmailTemplate { get; set; }
+
+ ///
+ /// Gets or sets the link of the order
+ ///
+ [JsonPropertyName("links")]
+ public OrderResourceLinksExt Links { get; set; } = new OrderResourceLinksExt();
+}
diff --git a/src/Notifications/API/Models/NotificationOrderListExt.cs b/src/Notifications/API/Models/NotificationOrderListExt.cs
new file mode 100644
index 00000000..e21362ae
--- /dev/null
+++ b/src/Notifications/API/Models/NotificationOrderListExt.cs
@@ -0,0 +1,25 @@
+#nullable enable
+using System.Text.Json.Serialization;
+
+namespace Altinn.Notifications.Models;
+
+///
+/// A class representing a list of notification order.
+///
+///
+/// External representaion to be used in the API.
+///
+public class NotificationOrderListExt
+{
+ ///
+ /// Gets or sets the number of orders in the list
+ ///
+ [JsonPropertyName("count")]
+ public int Count { get; set; }
+
+ ///
+ /// Gets or sets the list of notification orders
+ ///
+ [JsonPropertyName("orders")]
+ public List Orders { get; set; } = new List();
+}
diff --git a/src/Notifications/API/Models/NotificationOrderWithStatusExt.cs b/src/Notifications/API/Models/NotificationOrderWithStatusExt.cs
new file mode 100644
index 00000000..856bd3b2
--- /dev/null
+++ b/src/Notifications/API/Models/NotificationOrderWithStatusExt.cs
@@ -0,0 +1,50 @@
+#nullable enable
+using System.Text.Json.Serialization;
+
+namespace Altinn.Notifications.Models;
+
+///
+/// A class representing a registered notification order with status information.
+///
+///
+/// External representation to be used in the API.
+///
+public class NotificationOrderWithStatusExt : IBaseNotificationOrderExt
+{
+ /// >
+ [JsonPropertyName("id")]
+ public string Id { get; set; } = string.Empty;
+
+ /// >
+ [JsonPropertyName("sendersReference")]
+ public string? SendersReference { get; set; }
+
+ /// >
+ [JsonPropertyName("requestedSendTime")]
+ public DateTime RequestedSendTime { get; set; }
+
+ /// >
+ [JsonPropertyName("creator")]
+ public string Creator { get; set; } = string.Empty;
+
+ /// >
+ [JsonPropertyName("created")]
+ public DateTime Created { get; set; }
+
+ /// >
+ [JsonPropertyName("notificationChannel")]
+ [JsonConverter(typeof(JsonStringEnumConverter))]
+ public NotificationChannelExt NotificationChannel { get; set; }
+
+ ///
+ /// Gets or sets the processing status of the notication order
+ ///
+ [JsonPropertyName("processingStatus")]
+ public StatusExt ProcessingStatus { get; set; } = new();
+
+ ///
+ /// Gets or sets the summary of the notifiications statuses
+ ///
+ [JsonPropertyName("notificationsStatusSummary")]
+ public NotificationsStatusSummaryExt? NotificationsStatusSummary { get; set; }
+}
diff --git a/src/Notifications/API/Models/NotificationResourceLinksExt.cs b/src/Notifications/API/Models/NotificationResourceLinksExt.cs
new file mode 100644
index 00000000..72376c2d
--- /dev/null
+++ b/src/Notifications/API/Models/NotificationResourceLinksExt.cs
@@ -0,0 +1,19 @@
+#nullable enable
+using System.Text.Json.Serialization;
+
+namespace Altinn.Notifications.Models;
+
+///
+/// A class representing a set of resource links of a notification
+///
+///
+/// External representaion to be used in the API.
+///
+public class NotificationResourceLinksExt
+{
+ ///
+ /// Gets or sets the self link
+ ///
+ [JsonPropertyName("self")]
+ public string Self { get; set; } = string.Empty;
+}
diff --git a/src/Notifications/API/Models/NotificationStatusExt.cs b/src/Notifications/API/Models/NotificationStatusExt.cs
new file mode 100644
index 00000000..bb2bb54d
--- /dev/null
+++ b/src/Notifications/API/Models/NotificationStatusExt.cs
@@ -0,0 +1,38 @@
+#nullable enable
+using System.Text.Json.Serialization;
+
+namespace Altinn.Notifications.Models;
+
+///
+/// An abstrct class representing a status overview of a notification channels
+///
+public abstract class NotificationStatusExt
+{
+ ///
+ /// Gets or sets the self link of the notification status object
+ ///
+ [JsonPropertyName("links")]
+ public NotificationResourceLinksExt Links { get; set; } = new();
+
+ ///
+ /// Gets or sets the number of generated notifications
+ ///
+ [JsonPropertyName("generated")]
+ public int Generated { get; set; }
+
+ ///
+ /// Gets or sets the number of succeeeded notifications
+ ///
+ [JsonPropertyName("succeeded")]
+ public int Succeeded { get; set; }
+}
+
+///
+/// A class representing a status overview for email notifications
+///
+///
+/// External representaion to be used in the API.
+///
+public class EmailNotificationStatusExt : NotificationStatusExt
+{
+}
diff --git a/src/Notifications/API/Models/NotificationsStatusSummaryExt.cs b/src/Notifications/API/Models/NotificationsStatusSummaryExt.cs
new file mode 100644
index 00000000..c306a9f5
--- /dev/null
+++ b/src/Notifications/API/Models/NotificationsStatusSummaryExt.cs
@@ -0,0 +1,19 @@
+#nullable enable
+using System.Text.Json.Serialization;
+
+namespace Altinn.Notifications.Models;
+
+///
+/// A class representing a summary of status overviews of all notification channels
+///
+///
+/// External representaion to be used in the API.
+///
+public class NotificationsStatusSummaryExt
+{
+ ///
+ /// Gets or sets the status of the email notifications
+ ///
+ [JsonPropertyName("email")]
+ public EmailNotificationStatusExt? Email { get; set; }
+}
diff --git a/src/Notifications/API/Models/OrderIdExt.cs b/src/Notifications/API/Models/OrderIdExt.cs
new file mode 100644
index 00000000..551e574e
--- /dev/null
+++ b/src/Notifications/API/Models/OrderIdExt.cs
@@ -0,0 +1,27 @@
+#nullable enable
+using System.Text.Json.Serialization;
+
+namespace Altinn.Notifications.Models;
+
+///
+/// A class representing a container for an order id.
+///
+///
+/// External representaion to be used in the API.
+///
+public class OrderIdExt
+{
+ ///
+ /// The order id
+ ///
+ [JsonPropertyName("orderId")]
+ public Guid OrderId { get; set; }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public OrderIdExt(Guid orderId)
+ {
+ OrderId = orderId;
+ }
+}
diff --git a/src/Notifications/API/Models/OrderResourceLinksExt.cs b/src/Notifications/API/Models/OrderResourceLinksExt.cs
new file mode 100644
index 00000000..843f44ec
--- /dev/null
+++ b/src/Notifications/API/Models/OrderResourceLinksExt.cs
@@ -0,0 +1,31 @@
+#nullable enable
+using System.Text.Json.Serialization;
+
+namespace Altinn.Notifications.Models;
+
+///
+/// A class representing a set of resource links of a notification order.
+///
+///
+/// External representaion to be used in the API.
+///
+public class OrderResourceLinksExt
+{
+ ///
+ /// Gets or sets the self link
+ ///
+ [JsonPropertyName("self")]
+ public string Self { get; set; } = string.Empty;
+
+ ///
+ /// Gets or sets the status link
+ ///
+ [JsonPropertyName("status")]
+ public string Status { get; set; } = string.Empty;
+
+ ///
+ /// Gets or sets the notifications link
+ ///
+ [JsonPropertyName("notifications")]
+ public string Notifications { get; set; } = string.Empty;
+}
diff --git a/src/Notifications/API/Models/RecipientExt.cs b/src/Notifications/API/Models/RecipientExt.cs
new file mode 100644
index 00000000..2f7a7923
--- /dev/null
+++ b/src/Notifications/API/Models/RecipientExt.cs
@@ -0,0 +1,19 @@
+#nullable enable
+using System.Text.Json.Serialization;
+
+namespace Altinn.Notifications.Models;
+
+///
+/// Class representing a notification recipient
+///
+///
+/// External representaion to be used in the API.
+///
+public class RecipientExt
+{
+ ///
+ /// Gets or sets the email address of the recipient
+ ///
+ [JsonPropertyName("emailAddress")]
+ public string? EmailAddress { get; set; }
+}
diff --git a/src/Notifications/API/Models/StatusExt.cs b/src/Notifications/API/Models/StatusExt.cs
new file mode 100644
index 00000000..4b7165ca
--- /dev/null
+++ b/src/Notifications/API/Models/StatusExt.cs
@@ -0,0 +1,31 @@
+#nullable enable
+using System.Text.Json.Serialization;
+
+namespace Altinn.Notifications.Models;
+
+///
+/// A class representing a status summary
+///
+///
+/// External representaion to be used in the API.
+///
+public class StatusExt
+{
+ ///
+ /// Gets or sets the status
+ ///
+ [JsonPropertyName("status")]
+ public string Status { get; set; } = string.Empty;
+
+ ///
+ /// Gets or sets the description
+ ///
+ [JsonPropertyName("description")]
+ public string? StatusDescription { get; set; }
+
+ ///
+ /// Gets or sets the date time of when the status was last updated
+ ///
+ [JsonPropertyName("lastUpdate")]
+ public DateTime LastUpdate { get; set; }
+}
diff --git a/src/Notifications/API/Validators/EmailNotificationOrderRequestValidator.cs b/src/Notifications/API/Validators/EmailNotificationOrderRequestValidator.cs
new file mode 100644
index 00000000..d5165dce
--- /dev/null
+++ b/src/Notifications/API/Validators/EmailNotificationOrderRequestValidator.cs
@@ -0,0 +1,54 @@
+#nullable enable
+using System.Text.RegularExpressions;
+
+using Altinn.Notifications.Models;
+
+using FluentValidation;
+
+namespace Altinn.Notifications.Validators;
+
+///
+/// Class containing validation logic for the model
+///
+public class EmailNotificationOrderRequestValidator : AbstractValidator
+{
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public EmailNotificationOrderRequestValidator()
+ {
+ RuleFor(order => order.Recipients)
+ .NotEmpty()
+ .WithMessage("One or more recipient is required.")
+ .Must(recipients => recipients.TrueForAll(a => IsValidEmail(a.EmailAddress)))
+ .WithMessage("A valid email address must be provided for all recipients.");
+
+ RuleFor(order => order.RequestedSendTime)
+ .Must(sendTime => sendTime >= DateTime.UtcNow.AddMinutes(-5))
+ .WithMessage("Send time must be in the future. Leave blank to send immediately.");
+
+ RuleFor(order => order.Body).NotEmpty();
+ RuleFor(order => order.Subject).NotEmpty();
+ }
+
+ ///
+ /// Validated as email address based on the Altinn 2 regex
+ ///
+ /// The string to validate as an email address
+ /// A boolean indicating that the email is valid or not
+ internal static bool IsValidEmail(string? email)
+ {
+ if (string.IsNullOrEmpty(email))
+ {
+ return false;
+ }
+
+ string emailRegexPattern = @"(("[^"]+")|(([a-zA-Z0-9!#$%&'*+\-=?\^_`{|}~])+(\.([a-zA-Z0-9!#$%&'*+\-=?\^_`{|}~])+)*))@((((([a-zA-Z0-9æøåÆØÅ]([a-zA-Z0-9\-æøåÆØÅ]{0,61})[a-zA-Z0-9æøåÆØÅ]\.)|[a-zA-Z0-9æøåÆØÅ]\.){1,9})([a-zA-Z]{2,14}))|((\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})))";
+
+ Regex regex = new(emailRegexPattern, RegexOptions.None, TimeSpan.FromSeconds(1));
+
+ Match match = regex.Match(email);
+
+ return match.Success;
+ }
+}
diff --git a/src/Notifications/API/Validators/ValidationResultExtensions.cs b/src/Notifications/API/Validators/ValidationResultExtensions.cs
new file mode 100644
index 00000000..18ffe907
--- /dev/null
+++ b/src/Notifications/API/Validators/ValidationResultExtensions.cs
@@ -0,0 +1,23 @@
+#nullable enable
+using FluentValidation.Results;
+
+using Microsoft.AspNetCore.Mvc.ModelBinding;
+
+namespace Altinn.Notifications.Validators;
+
+///
+/// Extension class for
+///
+public static class ValidationResultExtensions
+{
+ ///
+ /// Adds the validation result to the model state
+ ///
+ public static void AddToModelState(this ValidationResult result, ModelStateDictionary modelState)
+ {
+ foreach (var error in result.Errors)
+ {
+ modelState.AddModelError(error.PropertyName, error.ErrorMessage);
+ }
+ }
+}
diff --git a/src/Notifications/Core/Configuration/NotificationOrderConfig.cs b/src/Notifications/Core/Configuration/NotificationOrderConfig.cs
new file mode 100644
index 00000000..571b9cf1
--- /dev/null
+++ b/src/Notifications/Core/Configuration/NotificationOrderConfig.cs
@@ -0,0 +1,13 @@
+#nullable enable
+namespace Altinn.Notifications.Core.Configuration;
+
+///
+/// Configuration class for notification orders
+///
+public class NotificationOrderConfig
+{
+ ///
+ /// Default from address for email notifications
+ ///
+ public string DefaultEmailFromAddress { get; set; } = string.Empty;
+}
diff --git a/src/Notifications/Core/Enums/AddressType.cs b/src/Notifications/Core/Enums/AddressType.cs
new file mode 100644
index 00000000..caf5ca9a
--- /dev/null
+++ b/src/Notifications/Core/Enums/AddressType.cs
@@ -0,0 +1,12 @@
+#nullable enable
+namespace Altinn.Notifications.Core.Enums;
+
+#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
+///
+/// Enum describing available address types
+///
+public enum AddressType
+{
+ Email
+}
+#pragma warning restore CS1591 // Missing XML comment for publicly visible type or member
diff --git a/src/Notifications/Core/Enums/EmailContentType.cs b/src/Notifications/Core/Enums/EmailContentType.cs
new file mode 100644
index 00000000..991ae849
--- /dev/null
+++ b/src/Notifications/Core/Enums/EmailContentType.cs
@@ -0,0 +1,18 @@
+#nullable enable
+namespace Altinn.Notifications.Core.Enums;
+
+///
+/// Enum describing available email content types
+///
+public enum EmailContentType
+{
+ ///
+ /// The email format is plain text.
+ ///
+ Plain,
+
+ ///
+ /// The email contains HTML elements
+ ///
+ Html
+}
diff --git a/src/Notifications/Core/Enums/EmailNotificationResultType.cs b/src/Notifications/Core/Enums/EmailNotificationResultType.cs
new file mode 100644
index 00000000..28e0562d
--- /dev/null
+++ b/src/Notifications/Core/Enums/EmailNotificationResultType.cs
@@ -0,0 +1,43 @@
+#nullable enable
+namespace Altinn.Notifications.Core.Enums;
+
+///
+/// Enum describing email notification result types
+///
+public enum EmailNotificationResultType
+{
+ ///
+ /// Default result for new notifications
+ ///
+ New,
+
+ ///
+ /// Email notification being sent
+ ///
+ Sending,
+
+ ///
+ /// Email notification sent
+ ///
+ Succeeded,
+
+ ///
+ /// Email delivered to recipient
+ ///
+ Delivered,
+
+ ///
+ /// Failed, unknown reason
+ ///
+ Failed,
+
+ ///
+ /// Recipient to address was not identified
+ ///
+ Failed_RecipientNotIdentified,
+
+ ///
+ /// Invalid format for email address
+ ///
+ Failed_InvalidEmailFormat
+}
diff --git a/src/Notifications/Core/Enums/IResultType.cs b/src/Notifications/Core/Enums/IResultType.cs
new file mode 100644
index 00000000..d8dea4d1
--- /dev/null
+++ b/src/Notifications/Core/Enums/IResultType.cs
@@ -0,0 +1,9 @@
+#nullable enable
+namespace Altinn.Notifications.Core.Enums;
+
+///
+/// Base class for send result of a notification
+///
+public interface IResultType
+{
+}
diff --git a/src/Notifications/Core/Enums/NotificationChannel.cs b/src/Notifications/Core/Enums/NotificationChannel.cs
new file mode 100644
index 00000000..4e23d060
--- /dev/null
+++ b/src/Notifications/Core/Enums/NotificationChannel.cs
@@ -0,0 +1,13 @@
+#nullable enable
+namespace Altinn.Notifications.Core.Enums;
+
+///
+/// Enum describing available notification channels.
+///
+public enum NotificationChannel
+{
+ ///
+ /// The selected channel for the notification is email.
+ ///
+ Email
+}
diff --git a/src/Notifications/Core/Enums/NotificationTemplateType.cs b/src/Notifications/Core/Enums/NotificationTemplateType.cs
new file mode 100644
index 00000000..a7e02909
--- /dev/null
+++ b/src/Notifications/Core/Enums/NotificationTemplateType.cs
@@ -0,0 +1,12 @@
+#nullable enable
+namespace Altinn.Notifications.Core.Enums;
+
+#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
+///
+/// Enum describing available notification template types
+///
+public enum NotificationTemplateType
+{
+ Email
+}
+#pragma warning restore CS1591 // Missing XML comment for publicly visible type or member
diff --git a/src/Notifications/Core/Enums/OrderProcessingStatus.cs b/src/Notifications/Core/Enums/OrderProcessingStatus.cs
new file mode 100644
index 00000000..20c22fa9
--- /dev/null
+++ b/src/Notifications/Core/Enums/OrderProcessingStatus.cs
@@ -0,0 +1,14 @@
+#nullable enable
+namespace Altinn.Notifications.Core.Enums;
+
+#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
+///
+/// Enum describing the various processing states of a notification order
+///
+public enum OrderProcessingStatus
+{
+ Registered,
+ Processing,
+ Completed
+}
+#pragma warning restore CS1591 // Missing XML comment for publicly visible type or member
diff --git a/src/Notifications/Core/Models/Address/EmailAddressPoint.cs b/src/Notifications/Core/Models/Address/EmailAddressPoint.cs
new file mode 100644
index 00000000..aa2b3031
--- /dev/null
+++ b/src/Notifications/Core/Models/Address/EmailAddressPoint.cs
@@ -0,0 +1,35 @@
+#nullable enable
+using Altinn.Notifications.Core.Enums;
+
+namespace Altinn.Notifications.Core.Models.Address;
+
+///
+/// A class represeting an address point
+///
+public class EmailAddressPoint : IAddressPoint
+{
+ ///
+ public AddressType AddressType { get; internal set; }
+
+ ///
+ /// Gets the email address
+ ///
+ public string EmailAddress { get; internal set; }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public EmailAddressPoint(string emailAddress)
+ {
+ AddressType = AddressType.Email;
+ EmailAddress = emailAddress;
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ internal EmailAddressPoint()
+ {
+ EmailAddress = string.Empty;
+ }
+}
diff --git a/src/Notifications/Core/Models/Address/IAddressPoint.cs b/src/Notifications/Core/Models/Address/IAddressPoint.cs
new file mode 100644
index 00000000..e15ccc17
--- /dev/null
+++ b/src/Notifications/Core/Models/Address/IAddressPoint.cs
@@ -0,0 +1,19 @@
+#nullable enable
+using System.Text.Json.Serialization;
+
+using Altinn.Notifications.Core.Enums;
+
+namespace Altinn.Notifications.Core.Models.Address;
+
+///
+/// Interface describing an address point
+///
+[JsonDerivedType(typeof(EmailAddressPoint), "email")]
+[JsonPolymorphic(TypeDiscriminatorPropertyName = "$")]
+public interface IAddressPoint
+{
+ ///
+ /// Gets or sets the address type for the address point
+ ///
+ public AddressType AddressType { get; }
+}
diff --git a/src/Notifications/Core/Models/Creator.cs b/src/Notifications/Core/Models/Creator.cs
new file mode 100644
index 00000000..ef0e334a
--- /dev/null
+++ b/src/Notifications/Core/Models/Creator.cs
@@ -0,0 +1,21 @@
+#nullable enable
+namespace Altinn.Notifications.Core.Models;
+
+///
+/// A class representing a notification creator
+///
+public class Creator
+{
+ ///
+ /// Gets the short name of the creator
+ ///
+ public string ShortName { get; internal set; }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public Creator(string shortName)
+ {
+ ShortName = shortName;
+ }
+}
diff --git a/src/Notifications/Core/Models/Email.cs b/src/Notifications/Core/Models/Email.cs
new file mode 100644
index 00000000..00a3ecce
--- /dev/null
+++ b/src/Notifications/Core/Models/Email.cs
@@ -0,0 +1,71 @@
+#nullable enable
+using System.Text.Json;
+using System.Text.Json.Serialization;
+
+using Altinn.Notifications.Core.Enums;
+
+namespace Altinn.Notifications.Core.Models;
+
+///
+/// Class representing an email
+///
+public class Email
+{
+ ///
+ /// Gets or sets the id of the email.
+ ///
+ public Guid NotificationId { get; set; }
+
+ ///
+ /// Gets or sets the subject of the email.
+ ///
+ public string Subject { get; set; }
+
+ ///
+ /// Gets or sets the body of the email.
+ ///
+ public string Body { get; set; }
+
+ ///
+ /// Gets or sets the to fromAdress of the email.
+ ///
+ public string FromAddress { get; set; }
+
+ ///
+ /// Gets or sets the to adress of the email.
+ ///
+ public string ToAddress { get; set; }
+
+ ///
+ /// Gets or sets the content type of the email.
+ ///
+ public EmailContentType ContentType { get; set; }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public Email(Guid notificationId, string subject, string body, string fromAddress, string toAddress, EmailContentType contentType)
+ {
+ NotificationId = notificationId;
+ Subject = subject;
+ Body = body;
+ FromAddress = fromAddress;
+ ToAddress = toAddress;
+ ContentType = contentType;
+ }
+
+ ///
+ /// Json serializes the
+ ///
+ public string Serialize()
+ {
+ return JsonSerializer.Serialize(
+ this,
+ new JsonSerializerOptions
+ {
+ DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
+ PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
+ Converters = { new JsonStringEnumConverter() }
+ });
+ }
+}
diff --git a/src/Notifications/Core/Models/Notification/EmailNotification.cs b/src/Notifications/Core/Models/Notification/EmailNotification.cs
new file mode 100644
index 00000000..3348ab48
--- /dev/null
+++ b/src/Notifications/Core/Models/Notification/EmailNotification.cs
@@ -0,0 +1,57 @@
+#nullable enable
+using Altinn.Notifications.Core.Enums;
+
+namespace Altinn.Notifications.Core.Models.Notification;
+
+///
+/// Class describing an email notification and extends the
+///
+public class EmailNotification : INotification
+{
+ ///
+ public Guid Id { get; internal set; }
+
+ ///
+ public Guid OrderId { get; internal set; }
+
+ ///
+ public DateTime RequestedSendTime { get; internal set; }
+
+ ///
+ public NotificationChannel NotificationChannel { get; } = NotificationChannel.Email;
+
+ ///
+ /// Get the id of the recipient of the email notification
+ ///
+ public string? RecipientId { get; internal set; }
+
+ ///
+ /// Get or sets the to address of the email notification
+ ///
+ public string ToAddress { get; internal set; } = string.Empty;
+
+ ///
+ /// Get or sets the send result of the notification
+ ///
+ public NotificationResult SendResult { get; internal set; } = new(EmailNotificationResultType.New, DateTime.UtcNow);
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public EmailNotification(Guid orderId, DateTime sendTime)
+ {
+ Id = Guid.NewGuid();
+ OrderId = orderId;
+ RequestedSendTime = sendTime;
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ internal EmailNotification()
+ {
+ Id = Guid.Empty;
+ OrderId = Guid.Empty;
+ RequestedSendTime = DateTime.MinValue;
+ }
+}
diff --git a/src/Notifications/Core/Models/Notification/EmailNotificationSummary.cs b/src/Notifications/Core/Models/Notification/EmailNotificationSummary.cs
new file mode 100644
index 00000000..28d97540
--- /dev/null
+++ b/src/Notifications/Core/Models/Notification/EmailNotificationSummary.cs
@@ -0,0 +1,32 @@
+#nullable enable
+namespace Altinn.Notifications.Core.Models.Notification
+{
+ ///
+ /// An implementation of for email notifications"/>
+ ///
+ public class EmailNotificationSummary : INotificationSummary
+ {
+ ///
+ public Guid OrderId { get; set; }
+
+ ///
+ public string? SendersReference { get; set; }
+
+ ///
+ public int Generated { get; internal set; }
+
+ ///
+ public int Succeeded { get; internal set; }
+
+ ///
+ public List Notifications { get; set; } = new List();
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public EmailNotificationSummary(Guid orderId)
+ {
+ OrderId = orderId;
+ }
+ }
+}
diff --git a/src/Notifications/Core/Models/Notification/EmailNotificationWithResult.cs b/src/Notifications/Core/Models/Notification/EmailNotificationWithResult.cs
new file mode 100644
index 00000000..a8c7e851
--- /dev/null
+++ b/src/Notifications/Core/Models/Notification/EmailNotificationWithResult.cs
@@ -0,0 +1,46 @@
+#nullable enable
+using Altinn.Notifications.Core.Enums;
+using Altinn.Notifications.Core.Models.Recipients;
+
+namespace Altinn.Notifications.Core.Models.Notification
+{
+ ///
+ /// An implementation of for email notifications"/>
+ /// Using the as recipient type and the as result type
+ ///
+ public class EmailNotificationWithResult : INotificationWithResult
+ {
+ ///
+ public Guid Id { get; }
+
+ ///
+ public bool Succeeded { get; internal set; }
+
+ ///
+ public EmailRecipient Recipient { get; }
+
+ ///
+ public NotificationResult ResultStatus { get; }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public EmailNotificationWithResult(Guid id, EmailRecipient recipient, NotificationResult result)
+ {
+ Id = id;
+ Recipient = recipient;
+ ResultStatus = result;
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ internal EmailNotificationWithResult(Guid id, bool succeeded, EmailRecipient recipient, NotificationResult result)
+ {
+ Id = id;
+ Succeeded = succeeded;
+ Recipient = recipient;
+ ResultStatus = result;
+ }
+ }
+}
diff --git a/src/Notifications/Core/Models/Notification/INotification.cs b/src/Notifications/Core/Models/Notification/INotification.cs
new file mode 100644
index 00000000..4bee1b10
--- /dev/null
+++ b/src/Notifications/Core/Models/Notification/INotification.cs
@@ -0,0 +1,38 @@
+#nullable enable
+using System;
+
+using Altinn.Notifications.Core.Enums;
+
+namespace Altinn.Notifications.Core.Models.Notification;
+
+///
+/// Interface describing a base notification.
+///
+public interface INotification
+ where TEnum : struct, Enum
+{
+ ///
+ /// Gets the id of the notification.
+ ///
+ public Guid Id { get; }
+
+ ///
+ /// Gets the order id of the notification.
+ ///
+ public Guid OrderId { get; }
+
+ ///
+ /// Gets the requested send time of the notification.
+ ///
+ public DateTime RequestedSendTime { get; }
+
+ ///
+ /// Gets the notifiction channel for the notification.
+ ///
+ public NotificationChannel NotificationChannel { get; }
+
+ ///
+ /// Gets the send result of the notification.
+ ///
+ public NotificationResult SendResult { get; }
+}
diff --git a/src/Notifications/Core/Models/Notification/INotificationSummary.cs b/src/Notifications/Core/Models/Notification/INotificationSummary.cs
new file mode 100644
index 00000000..911453b2
--- /dev/null
+++ b/src/Notifications/Core/Models/Notification/INotificationSummary.cs
@@ -0,0 +1,34 @@
+#nullable enable
+namespace Altinn.Notifications.Core.Models.Notification;
+
+///
+/// An interface representing a summary of the notifications related to an order
+///
+public interface INotificationSummary
+ where TClass : class
+{
+ ///
+ /// Gets the notification order id
+ ///
+ public Guid OrderId { get; }
+
+ ///
+ /// Gets the senders reference of the notification order
+ ///
+ public string? SendersReference { get; }
+
+ ///
+ /// Gets the number of generated notifications
+ ///
+ public int Generated { get; }
+
+ ///
+ /// Gets the number of succeeeded notifications
+ ///
+ public int Succeeded { get; }
+
+ ///
+ /// Gets the list of notifications with send result
+ ///
+ public List Notifications { get; }
+}
diff --git a/src/Notifications/Core/Models/Notification/INotificationWithResult.cs b/src/Notifications/Core/Models/Notification/INotificationWithResult.cs
new file mode 100644
index 00000000..aa95da12
--- /dev/null
+++ b/src/Notifications/Core/Models/Notification/INotificationWithResult.cs
@@ -0,0 +1,32 @@
+#nullable enable
+namespace Altinn.Notifications.Core.Models.Notification;
+
+///
+/// Interface representing a notification object with send result data
+///
+/// The class representing the recipient
+/// The enum used to describe the send result
+public interface INotificationWithResult
+ where TClass : class
+ where TEnum : struct, Enum
+{
+ ///
+ /// Gets the notification id
+ ///
+ public Guid Id { get; }
+
+ ///
+ /// Gets a boolean indicating if the sending was successful
+ ///
+ public bool Succeeded { get; }
+
+ ///
+ /// Sets the recipient with contact points
+ ///
+ public TClass Recipient { get; }
+
+ ///
+ /// Gets the send result
+ ///
+ public NotificationResult ResultStatus { get; }
+}
diff --git a/src/Notifications/Core/Models/Notification/NotificationResult.cs b/src/Notifications/Core/Models/Notification/NotificationResult.cs
new file mode 100644
index 00000000..1f85bb3a
--- /dev/null
+++ b/src/Notifications/Core/Models/Notification/NotificationResult.cs
@@ -0,0 +1,41 @@
+#nullable enable
+namespace Altinn.Notifications.Core.Models.Notification;
+
+///
+/// A class represednting a notification result
+///
+public class NotificationResult
+ where TEnum : struct, Enum
+{
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public NotificationResult(TEnum result, DateTime resultTime)
+ {
+ ResultTime = resultTime;
+ Result = result;
+ }
+
+ ///
+ /// Sets the result description
+ ///
+ public void SetResultDescription(string? description)
+ {
+ ResultDescription = description;
+ }
+
+ ///
+ /// Gets the date and time for when the last result was set.
+ ///
+ public DateTime ResultTime { get; }
+
+ ///
+ /// Gets the send result of the notification
+ ///
+ public TEnum Result { get; }
+
+ ///
+ /// Gets the description of the send result
+ ///
+ public string? ResultDescription { get; private set; }
+}
diff --git a/src/Notifications/Core/Models/Notification/SendOperationResult.cs b/src/Notifications/Core/Models/Notification/SendOperationResult.cs
new file mode 100644
index 00000000..2031dcaa
--- /dev/null
+++ b/src/Notifications/Core/Models/Notification/SendOperationResult.cs
@@ -0,0 +1,85 @@
+#nullable enable
+using System.Text.Json;
+using System.Text.Json.Serialization;
+using Altinn.Notifications.Core.Enums;
+using Altinn.Notifications.Core.Models.Orders;
+
+namespace Altinn.Notifications.Core.Models.Notification;
+
+///
+/// A class representing a send operation update object
+///
+public class SendOperationResult
+{
+ ///
+ /// The notification id
+ ///
+ public Guid NotificationId { get; set; }
+
+ ///
+ /// The send operation id
+ ///
+ public string OperationId { get; set; } = string.Empty;
+
+ ///
+ /// The email send result
+ ///
+ public EmailNotificationResultType? SendResult { get; set; }
+
+ ///
+ /// Json serializes the
+ ///
+ public string Serialize()
+ {
+ return JsonSerializer.Serialize(
+ this,
+ new JsonSerializerOptions
+ {
+ DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
+ PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
+ Converters = { new JsonStringEnumConverter() }
+ });
+ }
+
+ ///
+ /// Deserialize a json string into the
+ ///
+ public static SendOperationResult? Deserialize(string serializedString)
+ {
+ return JsonSerializer.Deserialize(
+ serializedString,
+ new JsonSerializerOptions()
+ {
+ PropertyNameCaseInsensitive = true,
+ Converters = { new JsonStringEnumConverter() }
+ });
+ }
+
+ ///
+ /// Try to parse a json string into a
+ ///
+ public static bool TryParse(string input, out SendOperationResult value)
+ {
+ SendOperationResult? parsedOutput;
+ value = new SendOperationResult();
+
+ if (string.IsNullOrEmpty(input))
+ {
+ return false;
+ }
+
+ try
+ {
+ parsedOutput = Deserialize(input!);
+
+ value = parsedOutput!;
+ return value.NotificationId != Guid.Empty;
+ }
+ catch
+ {
+ // try parse, we simply return false if fails
+ }
+
+ return false;
+ }
+}
diff --git a/src/Notifications/Core/Models/NotificationTemplate/EmailTemplate.cs b/src/Notifications/Core/Models/NotificationTemplate/EmailTemplate.cs
new file mode 100644
index 00000000..b3b627d5
--- /dev/null
+++ b/src/Notifications/Core/Models/NotificationTemplate/EmailTemplate.cs
@@ -0,0 +1,52 @@
+#nullable enable
+using Altinn.Notifications.Core.Enums;
+
+namespace Altinn.Notifications.Core.Models.NotificationTemplate;
+
+///
+/// Template for an email notification
+///
+public class EmailTemplate : INotificationTemplate
+{
+ ///
+ public NotificationTemplateType Type { get; internal set; }
+
+ ///
+ /// Gets the from adress of emails created by the template
+ ///
+ public string FromAddress { get; internal set; } = string.Empty;
+
+ ///
+ /// Gets the subject of emails created by the template
+ ///
+ public string Subject { get; internal set; } = string.Empty;
+
+ ///
+ /// Gets the body of emails created by the template
+ ///
+ public string Body { get; internal set; } = string.Empty;
+
+ ///
+ /// Gets the content type of emails created by the template
+ ///
+ public EmailContentType ContentType { get; internal set; }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public EmailTemplate(string? fromAddress, string subject, string body, EmailContentType contentType)
+ {
+ FromAddress = fromAddress ?? string.Empty;
+ Subject = subject;
+ Body = body;
+ ContentType = contentType;
+ Type = NotificationTemplateType.Email;
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ internal EmailTemplate()
+ {
+ }
+}
diff --git a/src/Notifications/Core/Models/NotificationTemplate/INotificationTemplate.cs b/src/Notifications/Core/Models/NotificationTemplate/INotificationTemplate.cs
new file mode 100644
index 00000000..106efd09
--- /dev/null
+++ b/src/Notifications/Core/Models/NotificationTemplate/INotificationTemplate.cs
@@ -0,0 +1,19 @@
+#nullable enable
+using System.Text.Json.Serialization;
+
+using Altinn.Notifications.Core.Enums;
+
+namespace Altinn.Notifications.Core.Models.NotificationTemplate;
+
+///
+/// Base class for a notification template
+///
+[JsonDerivedType(typeof(EmailTemplate), "email")]
+[JsonPolymorphic(TypeDiscriminatorPropertyName = "$")]
+public interface INotificationTemplate
+{
+ ///
+ /// Gets the type for the template
+ ///
+ public NotificationTemplateType Type { get; }
+}
diff --git a/src/Notifications/Core/Models/Orders/IBaseNotificationOrder.cs b/src/Notifications/Core/Models/Orders/IBaseNotificationOrder.cs
new file mode 100644
index 00000000..64d891c6
--- /dev/null
+++ b/src/Notifications/Core/Models/Orders/IBaseNotificationOrder.cs
@@ -0,0 +1,40 @@
+#nullable enable
+using Altinn.Notifications.Core.Enums;
+
+namespace Altinn.Notifications.Core.Models.Orders;
+
+///
+/// Class representing the base properties of a notification order
+///
+public interface IBaseNotificationOrder
+{
+ ///
+ /// Gets the id of the notification order
+ ///
+ public Guid Id { get; }
+
+ ///
+ /// Gets the senders reference of a notification
+ ///
+ public string? SendersReference { get; }
+
+ ///
+ /// Gets the requested send time for the notification(s)
+ ///
+ public DateTime RequestedSendTime { get; }
+
+ ///
+ /// Gets the preferred notification channel
+ ///
+ public NotificationChannel NotificationChannel { get; }
+
+ ///
+ /// Gets the creator of the notification
+ ///
+ public Creator Creator { get; }
+
+ ///
+ /// Gets the date and time for when the notification order was created
+ ///
+ public DateTime Created { get; }
+}
diff --git a/src/Notifications/Core/Models/Orders/NotificationOrder.cs b/src/Notifications/Core/Models/Orders/NotificationOrder.cs
new file mode 100644
index 00000000..96dfee79
--- /dev/null
+++ b/src/Notifications/Core/Models/Orders/NotificationOrder.cs
@@ -0,0 +1,122 @@
+#nullable enable
+using System.Text.Json;
+using System.Text.Json.Serialization;
+
+using Altinn.Notifications.Core.Enums;
+using Altinn.Notifications.Core.Models.NotificationTemplate;
+
+namespace Altinn.Notifications.Core.Models.Orders;
+
+///
+/// Class representing a notification order
+///
+public class NotificationOrder : IBaseNotificationOrder
+{
+ /// >
+ public Guid Id { get; internal set; } = Guid.Empty;
+
+ /// >
+ public string? SendersReference { get; internal set; }
+
+ /// >
+ public DateTime RequestedSendTime { get; internal set; }
+
+ /// >
+ public NotificationChannel NotificationChannel { get; internal set; }
+
+ /// >
+ public Creator Creator { get; internal set; }
+
+ /// >
+ public DateTime Created { get; internal set; }
+
+ ///
+ /// Gets the templates to create notifications based of
+ ///
+ public List Templates { get; internal set; } = new List();
+
+ ///
+ /// Gets a list of recipients
+ ///
+ public List Recipients { get; internal set; } = new List();
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public NotificationOrder(Guid id, string? sendersReference, List templates, DateTime requestedSendTime, NotificationChannel notificationChannel, Creator creator, DateTime created, List recipients)
+ {
+ Id = id;
+ SendersReference = sendersReference;
+ Templates = templates;
+ RequestedSendTime = requestedSendTime;
+ NotificationChannel = notificationChannel;
+ Creator = creator;
+ Created = created;
+ Recipients = recipients;
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ internal NotificationOrder()
+ {
+ Creator = new Creator(string.Empty);
+ }
+
+ ///
+ /// Json serializes the
+ ///
+ public string Serialize()
+ {
+ return JsonSerializer.Serialize(
+ this,
+ new JsonSerializerOptions
+ {
+ DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
+ PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
+ Converters = { new JsonStringEnumConverter() }
+ });
+ }
+
+ ///
+ /// Deserialize a json string into the
+ ///
+ public static NotificationOrder? Deserialize(string serializedString)
+ {
+ return JsonSerializer.Deserialize(
+ serializedString,
+ new JsonSerializerOptions()
+ {
+ PropertyNameCaseInsensitive = true,
+ Converters = { new JsonStringEnumConverter() }
+ });
+ }
+
+ ///
+ /// Try to parse a json string into a
+ ///
+ public static bool TryParse(string input, out NotificationOrder value)
+ {
+ NotificationOrder? parsedOutput;
+ value = new NotificationOrder();
+
+ if (string.IsNullOrEmpty(input))
+ {
+ return false;
+ }
+
+ try
+ {
+ parsedOutput = Deserialize(input!);
+
+ value = parsedOutput!;
+ return value.Id != Guid.Empty;
+ }
+ catch
+ {
+ // try parse, we simply return false if fails
+ }
+
+ return false;
+ }
+}
diff --git a/src/Notifications/Core/Models/Orders/NotificationOrderRequest.cs b/src/Notifications/Core/Models/Orders/NotificationOrderRequest.cs
new file mode 100644
index 00000000..9f794c3b
--- /dev/null
+++ b/src/Notifications/Core/Models/Orders/NotificationOrderRequest.cs
@@ -0,0 +1,64 @@
+#nullable enable
+using Altinn.Notifications.Core.Enums;
+using Altinn.Notifications.Core.Models.NotificationTemplate;
+
+namespace Altinn.Notifications.Core.Models.Orders;
+
+///
+/// Class representing a notification order request
+///
+public class NotificationOrderRequest
+{
+ ///
+ /// Gets the senders reference of a notification
+ ///
+ public string? SendersReference { get; internal set; }
+
+ ///
+ /// Gets the templates to create notifications based of
+ ///
+ public List Templates { get; internal set; }
+
+ ///
+ /// Gets the requested send time for the notification(s)
+ ///
+ public DateTime RequestedSendTime { get; internal set; }
+
+ ///
+ /// Gets the preferred notification channel
+ ///
+ public NotificationChannel NotificationChannel { get; internal set; }
+
+ ///
+ /// Gets a list of recipients
+ ///
+ public List Recipients { get; internal set; }
+
+ ///
+ /// Gets the creator of the notification request
+ ///
+ public Creator Creator { get; internal set; }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public NotificationOrderRequest(string? sendersReference, string creatorShortName, List templates, DateTime requestedSendTime, NotificationChannel notificationChannel, List recipients)
+ {
+ SendersReference = sendersReference;
+ Creator = new(creatorShortName);
+ Templates = templates;
+ RequestedSendTime = requestedSendTime;
+ NotificationChannel = notificationChannel;
+ Recipients = recipients;
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ internal NotificationOrderRequest()
+ {
+ Creator = new Creator(string.Empty);
+ Templates = new List();
+ Recipients = new List();
+ }
+}
diff --git a/src/Notifications/Core/Models/Orders/NotificationOrderWithStatus.cs b/src/Notifications/Core/Models/Orders/NotificationOrderWithStatus.cs
new file mode 100644
index 00000000..5c79f450
--- /dev/null
+++ b/src/Notifications/Core/Models/Orders/NotificationOrderWithStatus.cs
@@ -0,0 +1,134 @@
+#nullable enable
+using System.Text.Json.Serialization;
+
+using Altinn.Notifications.Core.Enums;
+
+namespace Altinn.Notifications.Core.Models.Orders;
+
+///
+/// A class representing a registered notification order with status information.
+///
+public class NotificationOrderWithStatus : IBaseNotificationOrder
+{
+ /// >
+ public Guid Id { get; internal set; }
+
+ /// >
+ public string? SendersReference { get; internal set; }
+
+ /// >
+ public DateTime RequestedSendTime { get; internal set; }
+
+ /// >
+ public Creator Creator { get; internal set; } = new(string.Empty);
+
+ /// >
+ public DateTime Created { get; internal set; }
+
+ /// >
+ public NotificationChannel NotificationChannel { get; internal set; }
+
+ ///
+ /// Gets the processing status of the notication order
+ ///
+ public ProcessingStatus ProcessingStatus { get; internal set; } = new();
+
+ ///
+ /// Gets the summary of the notifiications statuses
+ ///
+ public Dictionary NotificationStatuses { get; set; } = new();
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public NotificationOrderWithStatus(Guid id, string? sendersReference, DateTime requestedSendTime, Creator creator, DateTime created, NotificationChannel notificationChannel, ProcessingStatus processingStatus)
+ {
+ Id = id;
+ SendersReference = sendersReference;
+ RequestedSendTime = requestedSendTime;
+ Creator = creator;
+ Created = created;
+ NotificationChannel = notificationChannel;
+ ProcessingStatus = processingStatus;
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ internal NotificationOrderWithStatus()
+ {
+ }
+
+ ///
+ /// Adds an entry to the notification statuses for the provided type
+ ///
+ public void SetNotificationStatuses(NotificationTemplateType type, int generated, int succeeded)
+ {
+ NotificationStatuses.Add(type, new NotificationStatus() { Generated = generated, Succeeded = succeeded });
+ }
+}
+
+///
+/// A class representing a summary of status overviews of all notification channels
+///
+///
+/// External representaion to be used in the API.
+///
+public class ProcessingStatus
+{
+ ///
+ /// Gets the status
+ ///
+ [JsonPropertyName("status")]
+ public OrderProcessingStatus Status { get; internal set; }
+
+ ///
+ /// Gets the description
+ ///
+ [JsonPropertyName("description")]
+ public string? StatusDescription { get; internal set; }
+
+ ///
+ /// Gets the date time of when the status was last updated
+ ///
+ [JsonPropertyName("lastUpdate")]
+ public DateTime LastUpdate { get; internal set; }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public ProcessingStatus(OrderProcessingStatus status, DateTime lastUpdate, string? statusDescription = null)
+ {
+ Status = status;
+ StatusDescription = statusDescription;
+ LastUpdate = lastUpdate;
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ internal ProcessingStatus()
+ {
+ }
+}
+
+///
+/// A class representing a summary of status overviews of all notification channels
+///
+///
+/// External representaion to be used in the API.
+///
+public class NotificationStatus
+{
+ ///
+ /// Gets the number of generated notifications
+ ///
+ [JsonPropertyName("generated")]
+ public int Generated { get; internal set; }
+
+ ///
+ /// Gets the number of succeeeded notifications
+ ///
+ [JsonPropertyName("succeeded")]
+ public int Succeeded { get; internal set; }
+}
diff --git a/src/Notifications/Core/Models/Recipient.cs b/src/Notifications/Core/Models/Recipient.cs
new file mode 100644
index 00000000..c0517c41
--- /dev/null
+++ b/src/Notifications/Core/Models/Recipient.cs
@@ -0,0 +1,44 @@
+#nullable enable
+using Altinn.Notifications.Core.Models.Address;
+
+namespace Altinn.Notifications.Core.Models;
+
+///
+/// Class representing a notification recipient
+///
+public class Recipient
+{
+ ///
+ /// Gets the recipient id
+ ///
+ public string RecipientId { get; set; } = string.Empty;
+
+ ///
+ /// Gets a list of address points for the recipient
+ ///
+ public List AddressInfo { get; set; } = new List();
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public Recipient(string recipientId, List addressInfo)
+ {
+ RecipientId = recipientId;
+ AddressInfo = addressInfo;
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public Recipient(List addressInfo)
+ {
+ AddressInfo = addressInfo;
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public Recipient()
+ {
+ }
+}
diff --git a/src/Notifications/Core/Models/Recipients/EmailRecipient.cs b/src/Notifications/Core/Models/Recipients/EmailRecipient.cs
new file mode 100644
index 00000000..8d9e9216
--- /dev/null
+++ b/src/Notifications/Core/Models/Recipients/EmailRecipient.cs
@@ -0,0 +1,18 @@
+#nullable enable
+namespace Altinn.Notifications.Core.Models.Recipients;
+
+///
+/// Class representing an email recipient
+///
+public class EmailRecipient
+{
+ ///
+ /// Gets or sets the recipient id
+ ///
+ public string? RecipientId { get; set; } = null;
+
+ ///
+ /// Gets or sets the toaddress
+ ///
+ public string ToAddress { get; set; } = string.Empty;
+}
diff --git a/src/Notifications/Core/Models/ServiceError.cs b/src/Notifications/Core/Models/ServiceError.cs
new file mode 100644
index 00000000..12658322
--- /dev/null
+++ b/src/Notifications/Core/Models/ServiceError.cs
@@ -0,0 +1,36 @@
+#nullable enable
+namespace Altinn.Notifications.Core.Models;
+
+///
+/// A class representing a service error object used to transfere error information from service to controller.
+///
+public class ServiceError
+{
+ ///
+ /// The error code
+ ///
+ /// An error code translates directly into an HTTP status code
+ public int ErrorCode { get; private set; }
+
+ ///
+ /// The error message
+ ///
+ public string? ErrorMessage { get; private set; }
+
+ ///
+ /// Create a new instance of a service error
+ ///
+ public ServiceError(int errorCode, string errorMessage)
+ {
+ ErrorCode = errorCode;
+ ErrorMessage = errorMessage;
+ }
+
+ ///
+ /// Create a new instance of a service error
+ ///
+ public ServiceError(int errorCode)
+ {
+ ErrorCode = errorCode;
+ }
+}
diff --git a/src/Notifications/Core/Repository/Interfaces/IOrderRepository.cs b/src/Notifications/Core/Repository/Interfaces/IOrderRepository.cs
new file mode 100644
index 00000000..e6050b48
--- /dev/null
+++ b/src/Notifications/Core/Repository/Interfaces/IOrderRepository.cs
@@ -0,0 +1,53 @@
+#nullable enable
+using Altinn.Notifications.Core.Enums;
+using Altinn.Notifications.Core.Models.Orders;
+
+namespace Altinn.Notifications.Core.Repository.Interfaces;
+
+///
+/// Interface describing all repository actions for notification orders
+///
+public interface IOrderRepository
+{
+ ///
+ /// Creates a new notification order in the database
+ ///
+ /// The order to save
+ /// The saved notification order
+ public Task Create(NotificationOrder order);
+
+ ///
+ /// Gets a list of notification orders where requestedSendTime has passed
+ ///
+ /// A list of notification orders
+ public Task> GetPastDueOrdersAndSetProcessingState();
+
+ ///
+ /// Sets processing status of an order
+ ///
+ public Task SetProcessingStatus(Guid orderId, OrderProcessingStatus status);
+
+ ///
+ /// Gets an order based on the provided id within the provided creator scope
+ ///
+ /// The order id
+ /// The short name of the order creator
+ /// A notification order if it exists
+ public Task GetOrderById(Guid id, string creator);
+
+ ///
+ /// Gets an order with process and notification status based on the provided id within the provided creator scope
+ ///
+ /// The order id
+ /// The short name of the order creator
+ /// A notification order if it exists
+ public Task GetOrderWithStatusById(Guid id, string creator);
+
+ ///
+ /// Gets an order based on the provided senders reference within the provided creator scope
+ ///
+ /// The senders reference
+ /// The short name of the order creator
+ /// A list of notification orders
+ public Task> GetOrdersBySendersReference(string sendersReference, string creator);
+}
diff --git a/src/Notifications/Core/Services/DateTimeService.cs b/src/Notifications/Core/Services/DateTimeService.cs
new file mode 100644
index 00000000..a48321cd
--- /dev/null
+++ b/src/Notifications/Core/Services/DateTimeService.cs
@@ -0,0 +1,19 @@
+#nullable enable
+using System.Diagnostics.CodeAnalysis;
+
+using Altinn.Notifications.Core.Services.Interfaces;
+
+namespace Altinn.Notifications.Core.Services;
+
+///
+/// Implemntation of a dateTime service
+///
+[ExcludeFromCodeCoverage]
+public class DateTimeService : IDateTimeService
+{
+ ///
+ public DateTime UtcNow()
+ {
+ return DateTime.UtcNow;
+ }
+}
diff --git a/src/Notifications/Core/Services/EmailNotificationOrderService.cs b/src/Notifications/Core/Services/EmailNotificationOrderService.cs
new file mode 100644
index 00000000..0535e4a0
--- /dev/null
+++ b/src/Notifications/Core/Services/EmailNotificationOrderService.cs
@@ -0,0 +1,66 @@
+#nullable enable
+using Altinn.Notifications.Core.Configuration;
+using Altinn.Notifications.Core.Models;
+using Altinn.Notifications.Core.Models.NotificationTemplate;
+using Altinn.Notifications.Core.Models.Orders;
+using Altinn.Notifications.Core.Repository.Interfaces;
+using Altinn.Notifications.Core.Services.Interfaces;
+
+using Microsoft.Extensions.Options;
+
+namespace Altinn.Notifications.Core.Services;
+
+///
+/// Implementation of the .
+///
+public class EmailNotificationOrderService : IEmailNotificationOrderService
+{
+ private readonly IOrderRepository _repository;
+ private readonly IGuidService _guid;
+ private readonly IDateTimeService _dateTime;
+ private readonly string _defaultFromAddress;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public EmailNotificationOrderService(IOrderRepository repository, IGuidService guid, IDateTimeService dateTime, IOptions config)
+ {
+ _repository = repository;
+ _guid = guid;
+ _dateTime = dateTime;
+ _defaultFromAddress = config.Value.DefaultEmailFromAddress;
+ }
+
+ ///
+ public async Task<(NotificationOrder? Order, ServiceError? Error)> RegisterEmailNotificationOrder(NotificationOrderRequest orderRequest)
+ {
+ Guid orderId = _guid.NewGuid();
+ DateTime created = _dateTime.UtcNow();
+
+ var templates = SetFromAddressIfNotDefined(orderRequest.Templates);
+
+ var order = new NotificationOrder(
+ orderId,
+ orderRequest.SendersReference,
+ templates,
+ orderRequest.RequestedSendTime,
+ orderRequest.NotificationChannel,
+ orderRequest.Creator,
+ created,
+ orderRequest.Recipients);
+
+ NotificationOrder savedOrder = await _repository.Create(order);
+
+ return (savedOrder, null);
+ }
+
+ private List SetFromAddressIfNotDefined(List templates)
+ {
+ foreach (var template in templates.OfType().Where(template => string.IsNullOrEmpty(template.FromAddress)))
+ {
+ template.FromAddress = _defaultFromAddress;
+ }
+
+ return templates;
+ }
+}
diff --git a/src/Notifications/Core/Services/GuidService.cs b/src/Notifications/Core/Services/GuidService.cs
new file mode 100644
index 00000000..d6614803
--- /dev/null
+++ b/src/Notifications/Core/Services/GuidService.cs
@@ -0,0 +1,19 @@
+#nullable enable
+using System.Diagnostics.CodeAnalysis;
+
+using Altinn.Notifications.Core.Services.Interfaces;
+
+namespace Altinn.Notifications.Core.Services;
+
+///
+/// Implementation of the GuidServiceS
+///
+[ExcludeFromCodeCoverage]
+public class GuidService : IGuidService
+{
+ ///
+ public Guid NewGuid()
+ {
+ return Guid.NewGuid();
+ }
+}
diff --git a/src/Notifications/Core/Services/Interfaces/IDateTimeService.cs b/src/Notifications/Core/Services/Interfaces/IDateTimeService.cs
new file mode 100644
index 00000000..8ff6b2b1
--- /dev/null
+++ b/src/Notifications/Core/Services/Interfaces/IDateTimeService.cs
@@ -0,0 +1,14 @@
+#nullable enable
+namespace Altinn.Notifications.Core.Services.Interfaces;
+
+///
+/// Interface describing a dateTime service
+///
+public interface IDateTimeService
+{
+ ///
+ /// Provides DateTime UtcNow
+ ///
+ ///
+ public DateTime UtcNow();
+}
diff --git a/src/Notifications/Core/Services/Interfaces/IEmailNotificationOrderService.cs b/src/Notifications/Core/Services/Interfaces/IEmailNotificationOrderService.cs
new file mode 100644
index 00000000..01d54a36
--- /dev/null
+++ b/src/Notifications/Core/Services/Interfaces/IEmailNotificationOrderService.cs
@@ -0,0 +1,18 @@
+#nullable enable
+using Altinn.Notifications.Core.Models;
+using Altinn.Notifications.Core.Models.Orders;
+
+namespace Altinn.Notifications.Core.Services.Interfaces;
+
+///
+/// Interface for the email notification order service
+///
+public interface IEmailNotificationOrderService
+{
+ ///
+ /// Registers a new order
+ ///
+ /// The email notification order request
+ /// The registered notification order
+ public Task<(NotificationOrder? Order, ServiceError? Error)> RegisterEmailNotificationOrder(NotificationOrderRequest orderRequest);
+}
diff --git a/src/Notifications/Core/Services/Interfaces/IGuidService.cs b/src/Notifications/Core/Services/Interfaces/IGuidService.cs
new file mode 100644
index 00000000..16b81efc
--- /dev/null
+++ b/src/Notifications/Core/Services/Interfaces/IGuidService.cs
@@ -0,0 +1,13 @@
+#nullable enable
+namespace Altinn.Notifications.Core.Services.Interfaces;
+
+///
+/// Interface describing a guid service
+///
+public interface IGuidService
+{
+ ///
+ /// Generates a new Guid
+ ///
+ public Guid NewGuid();
+}
diff --git a/src/Notifications/LocalTestNotifications/LocalOrderRepository.cs b/src/Notifications/LocalTestNotifications/LocalOrderRepository.cs
new file mode 100644
index 00000000..4b3cab55
--- /dev/null
+++ b/src/Notifications/LocalTestNotifications/LocalOrderRepository.cs
@@ -0,0 +1,79 @@
+#nullable enable
+using System.Text.Json;
+
+using Altinn.Notifications.Core.Enums;
+using Altinn.Notifications.Core.Models.Orders;
+using Altinn.Notifications.Core.Repository.Interfaces;
+
+using LocalTest.Configuration;
+
+using Microsoft.Extensions.Options;
+
+namespace LocalTest.Notifications.Persistence.Repository
+{
+ public class LocalOrderRepository : IOrderRepository
+ {
+ private readonly LocalPlatformSettings _localPlatformSettings;
+ private readonly JsonSerializerOptions _serializerOptions;
+
+ public LocalOrderRepository(
+ IOptions localPlatformSettings)
+ {
+ _localPlatformSettings = localPlatformSettings.Value;
+ Directory.CreateDirectory(GetNotificationsDbPath());
+
+ _serializerOptions = new JsonSerializerOptions
+ {
+ WriteIndented = true,
+ PropertyNamingPolicy = JsonNamingPolicy.CamelCase
+ };
+ }
+
+ public Task Create(NotificationOrder order)
+ {
+ string path = GetOrderPath(order.Id);
+
+ string serializedOrder = JsonSerializer.Serialize(order, _serializerOptions);
+ FileInfo file = new FileInfo(path);
+ file.Directory?.Create();
+ File.WriteAllText(file.FullName, serializedOrder);
+
+ return Task.FromResult(order);
+ }
+
+ public Task GetOrderById(Guid id, string creator)
+ {
+ throw new NotImplementedException();
+ }
+
+ public Task> GetOrdersBySendersReference(string sendersReference, string creator)
+ {
+ throw new NotImplementedException();
+ }
+
+ public Task GetOrderWithStatusById(Guid id, string creator)
+ {
+ throw new NotImplementedException();
+ }
+
+ public Task> GetPastDueOrdersAndSetProcessingState()
+ {
+ throw new NotImplementedException();
+ }
+
+ public Task SetProcessingStatus(Guid orderId, OrderProcessingStatus status)
+ {
+ throw new NotImplementedException();
+ }
+
+ private string GetOrderPath(Guid orderId)
+ {
+ return Path.Combine(GetNotificationsDbPath(), "orders", orderId + ".json");
+ }
+
+ private string GetNotificationsDbPath()
+ {
+ return _localPlatformSettings.LocalTestingStorageBasePath + _localPlatformSettings.NotificationsStorageFolder;
+ }
+ }
+}
diff --git a/src/Notifications/LocalTestNotifications/NotificationsServiceExtentions.cs b/src/Notifications/LocalTestNotifications/NotificationsServiceExtentions.cs
new file mode 100644
index 00000000..f9f101e5
--- /dev/null
+++ b/src/Notifications/LocalTestNotifications/NotificationsServiceExtentions.cs
@@ -0,0 +1,30 @@
+using Altinn.Notifications.Core.Configuration;
+using Altinn.Notifications.Core.Repository.Interfaces;
+using Altinn.Notifications.Core.Services;
+using Altinn.Notifications.Core.Services.Interfaces;
+using Altinn.Notifications.Extensions;
+using Altinn.Notifications.Models;
+using Altinn.Notifications.Validators;
+using FluentValidation;
+using LocalTest.Notifications.Persistence.Repository;
+
+namespace LocalTest.Notifications.LocalTestNotifications;
+
+public static class NotificationsServiceExtentions
+{
+ public static void AddNotificationServices(this IServiceCollection services, string baseUrl)
+ {
+ // Notifications services
+ ValidatorOptions.Global.LanguageManager.Enabled = false;
+ ResourceLinkExtensions.Initialize(baseUrl);
+
+ services.Configure((c) => c.DefaultEmailFromAddress = "localtest@altinn.no");
+
+ services
+ .AddSingleton, EmailNotificationOrderRequestValidator>()
+ .AddSingleton()
+ .AddSingleton()
+ .AddSingleton()
+ .AddSingleton();
+ }
+}
\ No newline at end of file
diff --git a/src/Notifications/README.md b/src/Notifications/README.md
new file mode 100644
index 00000000..edbb7d05
--- /dev/null
+++ b/src/Notifications/README.md
@@ -0,0 +1,10 @@
+## Mock of Altinn Notifications for localtest
+
+Repository: https://github.com/Altinn/altinn-notifications
+
+Things to be aware of when copying code:
+
+- Persistence: code should not be copied directly, rather create a new implementation of the required repository class/method that writes data to disk.
- Controllers: code should be copied, but changes might be required related to authorization (e.g., platform access token is not included in local requests)
+- Core: code can be copied directly without changes
+
+Update Startup.cs with required services.
\ No newline at end of file
diff --git a/src/Startup.cs b/src/Startup.cs
index 532a7f28..ef45057f 100644
--- a/src/Startup.cs
+++ b/src/Startup.cs
@@ -28,6 +28,7 @@
using LocalTest.Clients.CdnAltinnOrgs;
using LocalTest.Configuration;
using LocalTest.Helpers;
+using LocalTest.Notifications.LocalTestNotifications;
using LocalTest.Services.Authentication.Implementation;
using LocalTest.Services.Authentication.Interface;
using LocalTest.Services.Authorization.Implementation;
@@ -43,14 +44,7 @@
using LocalTest.Services.TestData;
using Microsoft.AspNetCore.Authorization;
-using Microsoft.AspNetCore.Builder;
-using Microsoft.AspNetCore.Hosting;
-using Microsoft.AspNetCore.Http;
-using Microsoft.AspNetCore.StaticFiles.Infrastructure;
-using Microsoft.Extensions.Configuration;
-using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.FileProviders;
-using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Logging;
using Microsoft.IdentityModel.Tokens;
@@ -127,6 +121,11 @@ public void ConfigureServices(IServiceCollection services)
services.AddTransient();
services.AddTransient();
+ // Notifications services
+
+ GeneralSettings generalSettings = Configuration.GetSection("GeneralSettings").Get();
+ services.AddNotificationServices(generalSettings.BaseUrl);
+
// Storage services
services.AddSingleton();
services.AddTransient();