Send Notification Pattern
Overview
The Send Notification pattern uses INotificationService.SendNotification to send email notifications to users. Notifications include a subject, an HTML body, a list of recipients (To), and an optional CC list. Recipient information (UserId, UserName, UserEmail) is typically read from User-type attributes on the instance.
When to Use
- Notify assignees or team members after an instance is created.
- Alert stakeholders when a workflow state transition occurs.
- Send daily/scheduled digests of pending tasks via a Scheduler program.
Applicable Program Types
| Program Type | Common Use |
|---|---|
| PostCreate Trigger | Notify on creation |
| PostUpdate Trigger | Notify on update |
| PromoteActionCommand | Notify on state transition |
| Scheduler | Periodic digest notifications |
Important: Never send notifications from a Pre-trigger (PreCreate, PreUpdate). If the transaction rolls back, the email will already have been sent.
Pattern Structure
- Fetch the instance and its User-type attributes.
- Build
UserValue[]arrays for recipients and CC recipients. - Construct a
Notificationobject withInstanceId,Subject,Body(HTML string),Recipients, and optionallyCcRecipients. - Call
NotificationService.SendNotification(notificationMessage).
Code Example
Sends an email to the task assignee with the work order manager and creator on CC.
using Prorigo.Protrak.API.Contracts;
using Prorigo.Protrak.API.Services;
using Prorigo.Protrak.Programs;
using System;
using System.Collections.Generic;
using System.Linq;
namespace YourNamespace.Programs
{
/// <summary>
/// Type: Post Create Trigger
/// Configured for: Type Task
/// Trigger: After task instance is created
/// Summary: Sends an email to the task assignee; CCs the creator and project manager.
/// </summary>
public class NotifyTaskAssigneeOnTaskCreation : IPostCreateTriggerProgramAsync
{
public IInstanceService InstanceService { get; set; }
public INotificationService NotificationService { get; set; }
public IHomeService HomeService { get; set; }
private readonly string ATTRIBUTE_ASSIGNEE = "Assignee";
private readonly string ATTRIBUTE_SCHEDULED_START = "ScheduledStartDate";
private readonly string ATTRIBUTE_CREATOR = "Creator";
private readonly string ATTRIBUTE_WORKORDER_REF = "WorkOrderToTaskRef";
private readonly string ATTRIBUTE_WORKORDER_MANAGER = "WorkOrderManager";
private readonly string APP_URL = "https://yourapp.com/";
public async Task RunAsync(Guid instanceId)
{
var task = await InstanceService.GetInstanceAsync(instanceId, new[]
{
ATTRIBUTE_ASSIGNEE, ATTRIBUTE_SCHEDULED_START,
ATTRIBUTE_CREATOR, ATTRIBUTE_WORKORDER_REF
});
var assignees = task.GetUserAttributeValue(ATTRIBUTE_ASSIGNEE);
if (assignees == null || assignees.Length == 0) return;
var assignee = assignees.First();
var recipients = new[] { ToUserValue(assignee) };
var ccList = await BuildCcRecipientsAsync(task);
var startDate = GetFormattedStartDate(task);
NotificationService.SendNotification(new Notification
{
InstanceId = task.Id,
Subject = $"Task {task.Name} is assigned to you.",
Body = $"Hello {assignee.UserName},<br/><br/>" +
$"A new task has been created and assigned to you.<br/>" +
$"Scheduled start date: {startDate}<br/>" +
$"<a href='{APP_URL}Task/view/{task.Id}'>View Task</a><br/><br/>" +
$"Regards,<br/>Team<br/>" +
$"<i>Note: This is a system generated mail, please do not reply.</i>",
Recipients = recipients,
CcRecipients = ccList
});
}
private async Task<UserValue[]> BuildCcRecipientsAsync(Instance task)
{
var cc = new List<UserValue>();
// Add creator
var creator = task.GetUserAttributeValue(ATTRIBUTE_CREATOR)?.FirstOrDefault();
if (creator != null) cc.Add(ToUserValue(creator));
// Add work order manager
var workOrderRef = task.GetReferenceAttributeValue(ATTRIBUTE_WORKORDER_REF);
if (workOrderRef != null && workOrderRef.Length > 0)
{
var workOrder = await InstanceService.GetInstanceAsync(
workOrderRef.First().Id, new[] { ATTRIBUTE_WORKORDER_MANAGER });
var manager = workOrder.GetUserAttributeValue(ATTRIBUTE_WORKORDER_MANAGER)?.FirstOrDefault();
if (manager != null) cc.Add(ToUserValue(manager));
}
return cc.ToArray();
}
private string GetFormattedStartDate(Instance task)
{
var tenantSettings = HomeService.GetTenantSettings();
var tz = TimeZoneInfo.FindSystemTimeZoneById(tenantSettings.DateTimeFormat.TimeZoneId);
var dateFormat = tenantSettings.DateTimeFormat.DateFormat;
var date = task.GetDateAttributeValue(ATTRIBUTE_SCHEDULED_START);
if (date == null) return string.Empty;
return TimeZoneInfo.ConvertTimeFromUtc(
DateTime.SpecifyKind(date.Value, DateTimeKind.Utc), tz).ToString(dateFormat);
}
private static UserValue ToUserValue(UserValue u)
=> new UserValue { UserId = u.UserId, UserName = u.UserName, UserEmail = u.UserEmail };
}
}
Notification Object Properties
| Property | Type | Description |
|---|---|---|
InstanceId | Guid | The instance the notification is associated with |
Subject | string | Email subject line |
Body | string | HTML email body |
Recipients | UserValue[] | Primary recipients (To) |
CcRecipients | UserValue[] | CC recipients (optional) |
Building Recipients from User Attributes
// Read a single-value User attribute
var users = instance.GetUserAttributeValue("AssigneeName");
if (users != null && users.Length > 0)
{
var recipient = new UserValue
{
UserId = users[0].UserId,
UserName = users[0].UserName,
UserEmail = users[0].UserEmail
};
}
// Read a multi-value User attribute (collect all users)
var recipients = instance.GetUserAttributeValue("TeamMembers")
?.Select(u => new UserValue { UserId = u.UserId, UserName = u.UserName, UserEmail = u.UserEmail })
.ToArray() ?? Array.Empty<UserValue>();
Tenant Timezone for Dates in Emails
Always convert DateValue (stored as UTC) to the tenant's local timezone before including it in notifications:
var tenantSettings = HomeService.GetTenantSettings();
var tz = TimeZoneInfo.FindSystemTimeZoneById(tenantSettings.DateTimeFormat.TimeZoneId);
var localDate = TimeZoneInfo.ConvertTimeFromUtc(
DateTime.SpecifyKind(dateAttr.DateValue.Value, DateTimeKind.Utc), tz);
var formatted = localDate.ToString(tenantSettings.DateTimeFormat.DateFormat);
Posting a Message to an Instance (PostInstanceMessageAsync)
In addition to email, you can post a message directly to an instance's message/activity feed using InstanceService.PostInstanceMessageAsync. This is useful when inbound emails are processed and their content should appear in an instance's discussion thread.
var message = new InstanceMessage
{
InstanceId = ticketInstanceId,
InstanceName = ticketInstance.Name,
MessageBody = emailBodyText,
Sender = new UserValue { UserEmail = senderEmail },
Recipients = Array.Empty<UserValue>()
};
await InstanceService.PostInstanceMessageAsync(message);
InstanceMessage properties:
| Property | Type | Description |
|---|---|---|
InstanceId | Guid | The instance to post the message on |
InstanceName | string | Display name of the instance |
MessageBody | string | The message content (plain text or HTML) |
Sender | UserValue | The sender (can be a non-Protrak user — supply only UserEmail) |
Recipients | UserValue[] | Additional recipients; pass empty array if none beyond the sender |
Use
SendNotificationfor outbound email to specific users. UsePostInstanceMessageAsyncto add content to an instance's message feed — typically when ingesting inbound email replies.
Key Services
| Service | Method | Purpose |
|---|---|---|
INotificationService | SendNotification | Sends an outbound email notification |
IInstanceService | PostInstanceMessageAsync | Posts a message to an instance's message/activity feed |
IInstanceService | GetInstanceAsync | Fetch instance and its User attributes |
IHomeService | GetTenantSettings | Retrieve timezone and date format for date formatting |