Skip to main content

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 TypeCommon Use
PostCreate TriggerNotify on creation
PostUpdate TriggerNotify on update
PromoteActionCommandNotify on state transition
SchedulerPeriodic 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

  1. Fetch the instance and its User-type attributes.
  2. Build UserValue[] arrays for recipients and CC recipients.
  3. Construct a Notification object with InstanceId, Subject, Body (HTML string), Recipients, and optionally CcRecipients.
  4. 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

PropertyTypeDescription
InstanceIdGuidThe instance the notification is associated with
SubjectstringEmail subject line
BodystringHTML email body
RecipientsUserValue[]Primary recipients (To)
CcRecipientsUserValue[]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:

PropertyTypeDescription
InstanceIdGuidThe instance to post the message on
InstanceNamestringDisplay name of the instance
MessageBodystringThe message content (plain text or HTML)
SenderUserValueThe sender (can be a non-Protrak user — supply only UserEmail)
RecipientsUserValue[]Additional recipients; pass empty array if none beyond the sender

Use SendNotification for outbound email to specific users. Use PostInstanceMessageAsync to add content to an instance's message feed — typically when ingesting inbound email replies.


Key Services

ServiceMethodPurpose
INotificationServiceSendNotificationSends an outbound email notification
IInstanceServicePostInstanceMessageAsyncPosts a message to an instance's message/activity feed
IInstanceServiceGetInstanceAsyncFetch instance and its User attributes
IHomeServiceGetTenantSettingsRetrieve timezone and date format for date formatting