Email Notification Development
SkillCommunicationComplete guide to email notification development including events, templates, mail scripts, and delivery troubleshooting
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the Email Notification Development skill
What this skill tells your AI
The instructions your AI receives, as published by happy-technologies-llc/happy-platform-skills in skills/development/notifications/SKILL.md and read by ahel’s review.
Overview
This skill covers the complete process of creating and managing ServiceNow email notifications:
- Understanding notification types (event-based vs direct)
- Creating email notifications with proper triggers
- Building email templates with dynamic content
- Writing mail scripts for advanced recipient logic
- Configuring SMS and push notifications
- Troubleshooting delivery issues
When to use: When building automated notifications for record changes, approvals, SLA breaches, workflow activities, or custom business events.
Prerequisites
- Roles:
admin,notification_admin, oritil(for viewing) - Access: sysevent_email_action, sysevent_email_template, sys_script_email tables
- Knowledge: GlideRecord basics, email concepts, event registry
- Related Skills: Complete
admin/workflow-creationfor workflow-triggered notifications
Notification Architecture
Core Tables
sysevent_register (Event Registry)
|
+-- sysevent_email_action (Email Notifications)
|
+-- sysevent_email_template (Email Templates)
+-- sys_script_email (Mail Scripts)
+-- cmn_notif_message (Notification Messages)
Notification Types
| Type | Trigger | Use Case |
|---|---|---|
| Event-based | System/custom events | Standard approach - decoupled, reusable |
| Direct | Record conditions | Quick setup - tightly coupled to conditions |
| Workflow | Workflow activity | Part of workflow orchestration |
| Script | GlideEmailOutbound | Programmatic - full control |
Key Tables Reference
| Table | Purpose |
|---|---|
| sysevent_email_action | Email notification definitions |
| sysevent_email_template | Email body templates |
| sysevent_register | Event definitions |
| sys_script_email | Mail scripts for recipients |
| sys_email | Email log (sent/failed) |
| cmn_notif_message | Notification message store |
| cmn_notif_device | User notification devices |
Procedure
Step 1: Understand Event-Based vs Direct Notifications
Event-Based Notifications (Recommended):
- Triggered by events (e.g.,
incident.assigned,change.approval.rejected) - Decoupled from business rules
- Can have multiple notifications per event
- Better for complex, reusable notification logic
Direct Notifications:
- Triggered directly by record conditions
- Simpler to set up
- Tightly coupled to specific conditions
- Good for one-off notifications
Step 2: Query Existing Events
Before creating notifications, understand available events.
Query Event Registry:
Tool: SN-Query-Table
Parameters:
table_name: sysevent_register
query: table=incident
fields: sys_id,event_name,table,description,fired_by
limit: 50
Common System Events:
| Event | Table | Trigger |
|---|---|---|
| incident.assigned | incident | Incident assigned to user/group |
| incident.commented | incident | Comment added |
| incident.resolved | incident | Incident resolved |
| incident.reopened | incident | Incident reopened |
| change.approval.rejected | change_request | Change approval rejected |
| change.itil.approval.rejected | change_request | ITIL change rejected |
| sc_request.approved | sc_request | Request approved |
| sla.warning | task_sla | SLA warning threshold |
| sla.breached | task_sla | SLA breached |
| approval.inserted | sysapproval_approver | Approval record created |
| task.assigned | task | Task assigned |
Step 3: Create Custom Event (Optional)
If no existing event matches your needs, create a custom event.
Create Event Registration:
Tool: SN-Create-Record
Parameters:
table_name: sysevent_register
data:
event_name: "x_custom.incident.escalated"
table: "incident"
description: "Fired when incident is escalated to management"
fired_by: "Business Rule: Incident Escalation"
Fire Event from Business Rule:
// In business rule script
if (current.escalation.changes() && current.escalation > 0) {
gs.eventQueue('x_custom.incident.escalated', current, current.assigned_to, current.assignment_group);
}
Fire Event via Background Script:
Tool: SN-Execute-Background-Script
Parameters:
script: |
var inc = new GlideRecord('incident');
inc.get('number', 'INC0010001');
if (inc.isValidRecord()) {
gs.eventQueue('x_custom.incident.escalated', inc, inc.assigned_to.toString(), inc.assignment_group.toString());
gs.info('Event fired for: ' + inc.number);
}
description: "Fire custom escalation event for testing"
Step 4: Create Email Notification
Basic Event-Based Notification:
Tool: SN-Create-Record
Parameters:
table_name: sysevent_email_action
data:
name: "Incident Escalation Alert"
event_name: "x_custom.incident.escalated"
sys_domain: "global"
collection: "incident"
active: true
# Subject
subject: "URGENT: Incident ${number} Escalated - ${short_description}"
# Recipients
send_to_event_creator: false
recipient_users: [user_sys_id] # Specific users
recipient_groups: [group_sys_id] # Groups
send_to_event_parm_1: true # gs.eventQueue parm1 (assigned_to)
send_to_event_parm_2: true # gs.eventQueue parm2 (assignment_group)
# Content
content_type: "text/html"
message_html: |
<html>
<body>
<h2>Incident Escalated</h2>
<p>The following incident has been escalated and requires immediate attention:</p>
<table border="1" cellpadding="5">
<tr><td><b>Number:</b></td><td>${number}</td></tr>
<tr><td><b>Priority:</b></td><td>${priority}</td></tr>
<tr><td><b>Description:</b></td><td>${short_description}</td></tr>
<tr><td><b>Assigned To:</b></td><td>${assigned_to}</td></tr>
<tr><td><b>Assignment Group:</b></td><td>${assignment_group}</td></tr>
<tr><td><b>Escalation Level:</b></td><td>${escalation}</td></tr>
</table>
<p><a href="${URI_REF}">View Incident</a></p>
</body>
</html>
Direct (Condition-Based) Notification:
Tool: SN-Create-Record
Parameters:
table_name: sysevent_email_action
data:
name: "High Priority Incident Created"
# No event_name - uses conditions instead
sys_domain: "global"
collection: "incident"
action_insert: true
action_update: false
action_delete: false
condition: "priority=1^state=1" # P1, New state
active: true
subject: "P1 Incident Created: ${number}"
send_to_event_parm_1: true
content_type: "text/html"
message_html: |
<h2>Priority 1 Incident Created</h2>
<p>A new Priority 1 incident requires immediate attention.</p>
<p><b>Number:</b> ${number}</p>
<p><b>Description:</b> ${short_description}</p>
<p><a href="${URI_REF}">View Incident</a></p>
Step 5: Configure Recipients
Recipient Options in sysevent_email_action:
| Field | Purpose |
|---|---|
recipient_users | Specific user sys_ids (comma-separated) |
recipient_groups | Group sys_ids - emails all members |
send_to_event_creator | User who triggered event |
send_to_event_parm_1 | First parameter from gs.eventQueue |
send_to_event_parm_2 | Second parameter from gs.eventQueue |
recipient_fields | Record field containing user reference |
send_self | Send to user who made change |
exclude_delegates | Skip delegate users |
email_template | Reference to reusable template |
Field-Based Recipients:
Tool: SN-Update-Record
Parameters:
table_name: sysevent_email_action
sys_id: [notification_sys_id]
data:
recipient_fields: "assigned_to,opened_by,caller_id" # Comma-separated fields
Group Recipients:
Tool: SN-Update-Record
Parameters:
table_name: sysevent_email_action
sys_id: [notification_sys_id]
data:
recipient_groups: [group1_sys_id],[group2_sys_id]
Step 6: Create Mail Script for Dynamic Recipients
Mail scripts allow complex recipient logic beyond simple field references.
Create Mail Script:
Tool: SN-Create-Record
Parameters:
table_name: sys_script_email
data:
name: "Get On-Call Manager"
description: "Returns the on-call manager for the assignment group"
script: |
// Mail script to find on-call manager
// 'current' is the record, 'email' is the GlideEmailOutbound object
(function runMailScript(current, template, email, email_action, event) {
// Get on-call rotation for assignment group
var oncall = new GlideRecord('on_call_rotation');
oncall.addQuery('group', current.assignment_group);
oncall.addQuery('active', true);
oncall.query();
if (oncall.next()) {
// Add on-call user as recipient
var user = new GlideRecord('sys_user');
user.get(oncall.user);
if (user.isValidRecord() && user.email) {
email.addAddress('to', user.email, user.name);
gs.info('Mail script: Added recipient ' + user.email);
}
}
// Optionally add escalation manager
if (current.escalation > 1) {
var manager = current.assignment_group.manager;
if (manager && manager.email) {
email.addAddress('cc', manager.email, manager.name);
}
}
})(current, template, email, email_action, event);
Associate Mail Script with Notification:
Tool: SN-Update-Record
Parameters:
table_name: sysevent_email_action
sys_id: [notification_sys_id]
data:
sys_script_mail: [mail_script_sys_id]
Advanced Mail Script - Manager Hierarchy:
Tool: SN-Create-Record
Parameters:
table_name: sys_script_email
data:
name: "Get Manager Chain"
description: "Adds manager hierarchy up to specified levels"
script: |
(function runMailScript(current, template, email, email_action, event) {
var MAX_LEVELS = 3;
var userId = current.assigned_to.toString();
for (var level = 0; level < MAX_LEVELS && userId; level++) {
var user = new GlideRecord('sys_user');
user.get(userId);
if (user.isValidRecord() && user.manager) {
var manager = new GlideRecord('sys_user');
manager.get(user.manager);
if (manager.isValidRecord() && manager.email) {
email.addAddress('cc', manager.email, manager.name);
userId = manager.sys_id.toString();
} else {
break;
}
} else {
break;
}
}
})(current, template, email, email_action, event);
Step 7: Create Email Template
Email templates allow reusable HTML/text formatting.
Create Email Template:
Tool: SN-Create-Record
Parameters:
table_name: sysevent_email_template
data:
name: "Incident Alert Template"
description: "Standard template for incident notifications"
subject: "${number}: ${short_description}"
message_html: |
<!DOCTYPE html>
<html>
<head>
<style>
body { font-family: Arial, sans-serif; }
.header { background-color: #1a73e8; color: white; padding: 20px; }
.content { padding: 20px; }
.details { border-collapse: collapse; width: 100%; }
.details td { border: 1px solid #ddd; padding: 10px; }
.label { background-color: #f5f5f5; font-weight: bold; width: 30%; }
.footer { background-color: #f5f5f5; padding: 15px; font-size: 12px; }
.button { background-color: #1a73e8; color: white; padding: 10px 20px; text-decoration: none; border-radius: 4px; }
</style>
</head>
<body>
<div class="header">
<h1>Incident Alert</h1>
</div>
<div class="content">
<p>An incident requires your attention:</p>
<table class="details">
<tr>
<td class="label">Number</td>
<td>${number}</td>
</tr>
<tr>
<td class="label">Priority</td>
<td>${priority}</td>
</tr>
<tr>
<td class="label">State</td>
<td>${state}</td>
</tr>
<tr>
<td class="label">Description</td>
<td>${short_description}</td>
</tr>
<tr>
<td class="label">Assigned To</td>
<td>${assigned_to}</td>
</tr>
<tr>
<td class="label">Assignment Group</td>
<td>${assignment_group}</td>
</tr>
<tr>
<td class="label">Opened</td>
<td>${opened_at}</td>
</tr>
</table>
<p style="margin-top: 20px;">
<a href="${URI_REF}" class="button">View Incident</a>
</p>
</div>
<div class="footer">
<p>This is an automated notification from ServiceNow.</p>
<p>Do not reply to this email.</p>
</div>
</body>
</html>
message: |
Incident Alert
Number: ${number}
Priority: ${priority}
State: ${state}
Description: ${short_description}
Assigned To: ${assigned_to}
Assignment Group: ${assignment_group}
View Incident: ${URI_REF}
This is an automated notification.
collection: "incident"
Associate Template with Notification:
Tool: SN-Update-Record
Parameters:
table_name: sysevent_email_action
sys_id: [notification_sys_id]
data:
email_template: [template_sys_id]
# Clear inline content if using template
message_html: ""
message: ""
Step 8: Email Variable Syntax
Standard Field Variables:
${field_name} - Display value of field
${field_name.name} - Reference field's name
${field_name.email} - Reference field's email
${field_name.sys_id} - Reference field's sys_id
${number} - Incident number
${short_description} - Short description
${priority} - Priority display value
${state} - State display value
Special Variables:
${URI} - Record URI path only
${URI_REF} - Full URL to record
${mailto:email} - Mailto link
${event.parm1} - First event parameter
${event.parm2} - Second event parameter
${current} - Current record reference
${gs.getProperty('name')} - System property value
Conditional Content (Mail Script Required):
// In notification body script or template script
if (current.priority == 1) {
template.print('<span style="color: red; font-weight: bold;">CRITICAL</span>');
} else {
template.print('<span>' + current.priority.getDisplayValue() + '</span>');
}
Iterating Over Related Records:
// Show related tasks in notification
template.print('<h3>Related Tasks:</h3>');
template.print('<ul>');
var tasks = new GlideRecord('sc_task');
tasks.addQuery('request_item', current.sys_id);
tasks.query();
while (tasks.next()) {
template.print('<li>' + tasks.number + ' - ' + tasks.short_description + '</li>');
}
template.print('</ul>');
Step 9: Add Attachments
Include Record Attachments:
Tool: SN-Update-Record
Parameters:
table_name: sysevent_email_action
sys_id: [notification_sys_id]
data:
include_attachments: true
Programmatic Attachment via Mail Script:
Tool: SN-Create-Record
Parameters:
table_name: sys_script_email
data:
name: "Attach Report PDF"
description: "Attaches a generated report to the email"
script: |
(function runMailScript(current, template, email, email_action, event) {
// Get attachment from related record
var attach = new GlideRecord('sys_attachment');
attach.addQuery('table_name', 'incident');
attach.addQuery('table_sys_id', current.sys_id);
attach.addQuery('content_type', 'CONTAINS', 'pdf');
attach.query();
while (attach.next()) {
// Add each PDF attachment
var gsa = new GlideSysAttachment();
var content = gsa.getContentBase64(attach);
email.addAttachment(attach.file_name, attach.content_type, content);
}
})(current, template, email, email_action, event);
Step 10: Configure SMS Notifications
SMS notifications require the SMS plugin and carrier configuration.
Check SMS Capability:
Tool: SN-Query-Table
Parameters:
table_name: sys_plugins
query: name=com.snc.sms
fields: sys_id,name,active
Create SMS Notification:
Tool: SN-Create-Record
Parameters:
table_name: sysevent_email_action
data:
name: "P1 Incident SMS Alert"
event_name: "incident.assigned"
sys_domain: "global"
collection: "incident"
condition: "priority=1"
active: true
# SMS specific
sms_alternate: true # Enable SMS option
subject: "P1: ${number}" # SMS subject (carrier dependent)
message: "P1 Incident ${number}: ${short_description}. Priority: ${priority}. View: ${URI_REF}"
content_type: "text/plain"
# Recipients
send_to_event_parm_1: true
Configure SMS for User:
Tool: SN-Query-Table
Parameters:
table_name: cmn_notif_device
query: user=[user_sys_id]^type=SMS
fields: sys_id,phone_number,carrier,active
Step 11: Configure Push Notifications
Push notifications require the mobile plugin and device registration.
Create Push Notification:
Tool: SN-Create-Record
Parameters:
table_name: sysevent_email_action
data:
name: "Approval Required Push"
event_name: "approval.inserted"
collection: "sysapproval_approver"
active: true
# Push specific
push_message_only: true # Send push only, no email
subject: "Approval Required"
message: "You have a new approval request: ${document_id.short_description}"
# Recipients
recipient_fields: "approver"
Query User's Push Devices:
Tool: SN-Query-Table
Parameters:
table_name: cmn_notif_device
query: user=[user_sys_id]^type=Push
fields: sys_id,device_name,active,last_registration
Step 12: Notification Conditions and Filtering
Advanced Condition Examples:
| Condition | Purpose |
|---|---|
priority=1^state!=6 | P1, not resolved |
active=true^assigned_toISEMPTY | Active, unassigned |
sys_created_onONToday@javascript:gs.beginningOfToday()@javascript:gs.endOfToday() | Created today |
priority.changes()=true | Priority field changed |
state.changesFrom(1)^state.changesTo(2) | State changed from New to In Progress |
Condition Script (Advanced):
Tool: SN-Update-Record
Parameters:
table_name: sysevent_email_action
sys_id: [notification_sys_id]
data:
advanced_condition: |
// Return true to send notification, false to skip
function advancedCondition() {
// Don't notify if CI is in maintenance window
if (current.cmdb_ci && current.cmdb_ci.maintenance) {
return false;
}
// Only notify during business hours
var now = new GlideDateTime();
var hour = now.getLocalTime().getHourOfDayLocalTime();
if (hour < 8 || hour > 18) {
return false; // Outside business hours
}
// Check if user has opted out
var prefs = new GlideRecord('sys_user_preference');
prefs.addQuery('user', current.assigned_to);
prefs.addQuery('name', 'notification.incident.optout');
prefs.addQuery('value', 'true');
prefs.query();
if (prefs.next()) {
return false;
}
return true;
}
advancedCondition();
Step 13: Weight and Priority
Control notification frequency and importance.
Set Notification Weight:
Tool: SN-Update-Record
Parameters:
table_name: sysevent_email_action
sys_id: [notification_sys_id]
data:
weight: 10 # Higher weight = higher priority
digest: false # true = batch into digest
digest_interval: "" # hourly, daily if digest=true
omit_watermark: false # Watermark prevents duplicates
Digest Configuration:
Tool: SN-Create-Record
Parameters:
table_name: sysevent_email_action
data:
name: "Daily Incident Summary"
event_name: "incident.created"
collection: "incident"
active: true
digest: true
digest_interval: "daily"
digest_template: [digest_template_sys_id]
subject: "Daily Incident Summary - ${digest.count} incidents"
Troubleshooting Email Delivery
Step 1: Check Email Log
Query Recent Emails:
Tool: SN-Query-Table
Parameters:
table_name: sys_email
query: type=send-ready^ORtype=sent^ORtype=send-error^sys_created_onONToday@javascript:gs.beginningOfToday()@javascript:gs.endOfToday()
fields: sys_id,type,recipients,subject,sys_created_on,error,mailbox
limit: 50
orderBy: sys_created_on
orderByDesc: true
Email Status Types:
| Type | Meaning |
|---|---|
| send-ready | Queued for sending |
| sent | Successfully sent |
| send-error | Send failed |
| received | Inbound email |
| received-error | Inbound processing error |
| draft | Pending approval |
| ignored | Skipped by rule |
Find Failed Emails:
Tool: SN-Query-Table
Parameters:
table_name: sys_email
query: type=send-error^sys_created_onONLast 7 days@javascript:gs.daysAgoStart(7)@javascript:gs.daysAgoEnd(0)
fields: sys_id,recipients,subject,error,sys_created_on
limit: 100
Step 2: Check Event Queue
Query Event Queue:
Tool: SN-Query-Table
Parameters:
table_name: sysevent
query: name=x_custom.incident.escalated^sys_created_onONToday@javascript:gs.beginningOfToday()@javascript:gs.endOfToday()
fields: sys_id,name,instance,parm1,parm2,state,sys_created_on
limit: 50
Event States:
| State | Meaning |
|---|---|
| ready | Queued for processing |
| processed | Successfully processed |
| error | Processing failed |
Step 3: Verify Notification Configuration
Check Notification Is Active:
Tool: SN-Query-Table
Parameters:
table_name: sysevent_email_action
query: sys_id=[notification_sys_id]
fields: name,active,event_name,condition,recipient_users,recipient_groups,recipient_fields
Verify Event Registration:
Tool: SN-Query-Table
Parameters:
table_name: sysevent_register
query: event_name=[your_event_name]
fields: sys_id,event_name,table,description
Step 4: Test Notification
Fire Test Event:
Tool: SN-Execute-Background-Script
Parameters:
script: |
// Test notification by firing event
var inc = new GlideRecord('incident');
inc.addQuery('active', true);
inc.setLimit(1);
inc.query();
if (inc.next()) {
gs.eventQueue('x_custom.incident.escalated', inc, inc.assigned_to.toString(), inc.assignment_group.toString());
gs.info('Test event fired for: ' + inc.number);
}
description: "Fire test notification event"
Check Email Generation:
Tool: SN-Query-Table
Parameters:
table_name: sys_email
query: recipients=[test_email]^ORsubjectLIKE[incident_number]^sys_created_onONLast hour@javascript:gs.hoursAgoStart(1)@javascript:gs.hoursAgoEnd(0)
fields: sys_id,type,recipients,subject,body,error
Common Issues
Notification Not Firing
Symptom: No email generated after record change Causes:
- Notification inactive
- Condition not matched
- Event not registered
- No recipients defined
Solution:
Tool: SN-Query-Table
Parameters:
table_name: sysevent_email_action
query: collection=incident^active=true
fields: name,event_name,condition,active,recipient_users,recipient_groups
Recipients Not Receiving Email
Shortened here. Read the whole file on GitHub.
Signals
- GitHub stars
- 37
- Forks
- 13
- Last commit
- Jul 2026
Advanced
- Catalog kind
- skill
- Gateway key
notifications- Source
- github.com/happy-technologies-llc/happy-platform-skills