How do I add a template and blind copy a user group via Inbound Action?

neil_b
Tera Guru

Hi,

 

I have this inbound action script below: 

(function runAction( /*GlideRecord*/ current, /*GlideRecord*/ event, /*EmailWrapper*/ email, /*ScopedEmailLogger*/ logger, /*EmailClassifier*/ classifier) {

	// Prep fields
    var groupSysID = '12345678910'; // User Group Sys ID
    var subject = email.subject;
    var bodyHtml = email.body_html;
    var bodyText = email.body_text;
    var sender = email.sys_updated_by;
    var mailType = "send-ready";


    // Create a new email record in the Outbox
    var mail = new GlideRecord("sys_email");
    mail.initialize();
    mail.mailbox.setDisplayValue("Outbox"); // Place the new email in the Outbox
    mail.subject = subject;
    mail.type = mailType;

	var t = new GlideTemplate.get('1a2b3c4d5e6f7g'); // Email Template Sys ID
	gs.info('t is ' + t);
	gs.info('name is ' + t.name);
	gs.info('sys_id is ' + t.sys_id);
	gs.info('layout is ' + t.email_layout);

	t.apply('mail');
	mail.applyTemplate('Our Company Notification Template'); // NAME of Email Template

	// Get list of group members
    var groupMembers = getGroupMembers(groupSysID);
	mail.recipients = sender;
	mail.blind_copied = groupMembers.join(',');

    // Include HTML body if available and set content type
    if (bodyHtml) {
        mail.body_html = bodyHtml;
        mail.body = bodyHtml; // Fallback for plain text clients
        mail.content_type = "text/html";
    } else {
        mail.body = bodyText;
        mail.content_type = "text/plain";
    }

	// Insert email record
    mail.insert();

	// Function to retrieve list of users from group
    function getGroupMembers(groupId) {
        var members = [];
        var grMem = new GlideRecord('sys_user_grmember');
        grMem.addQuery('group', groupSysID);
        grMem.addActiveQuery();
        grMem.query();
        while (grMem.next()) {
            members.push(grMem.user.email.toString());
        }
        return members;
    }
	
})(current, event, email, logger, classifier);

 

I have tried to debug but this is what I get:

undefined.png

 

I'm not able to use our company configured template on the outgoing notification and it's also not blind copying the users in the group. Any ideas?

1 ACCEPTED SOLUTION

Hi Neil,

That is an excellent catch. Since we are moving away from the manual sys_email insert, we need a mechanism to "transport" that dynamic content (Subject and Body) from the Inbound Action to the Notification.

You are absolutely correct: Email Scripts are the key here.

Since gs.eventQueue only accepts string parameters, the best practice is to package your Subject and Body into a JSON Object and pass it via Parm2.

Here is the updated implementation:

1. Update Inbound Action (The Packer) We will package your data into a JSON string and pass it in the 4th argument (Parameter 2).

 
(function runAction(/*GlideRecord*/ current, /*GlideRecord*/ event, /*EmailWrapper*/ email, /*ScopedEmailLogger*/ logger, /*EmailClassifier*/ classifier) {

    var groupSysID = '12345678910'; 

    // 1. Build a Payload Object with the data you want to transfer
    var payload = {
        subject: email.subject + '', // ensure string
        body: email.body_html ? email.body_html + '' : email.body_text + ''
    };

    // 2. Fire Event: Pass GroupID in Parm1, and JSON Payload in Parm2
    gs.eventQueue('inbound.group.notification', current, groupSysID, JSON.stringify(payload));

})(current, event, email, logger, classifier);

 

2. Update the Email Script (The Unpacker) Modify the add_group_bcc script we created earlier. It will now handle both the BCC logic and the content injection.

  • Script Name: add_group_bcc

 
(function runMailScript(/* GlideRecord */ current, /* TemplatePrinter */ template, /* Optional EmailOutbound */ email, /* Optional GlideRecord */ email_action, /* Optional GlideRecord */ event) {

    // --- PART A: Handle Content (Subject & Body) ---
    if (event.parm2) {
        var data = JSON.parse(event.parm2); // Unpack the JSON
        
        // 1. Set the Subject dynamically
        if (data.subject) {
            email.setSubject(data.subject);
        }

        // 2. Print the original HTML body
        if (data.body) {
            template.print(data.body);
        }
    }

    // --- PART B: Handle BCC (Group Members) ---
    var groupId = event.parm1;
    if (groupId) {
        var grMem = new GlideRecord('sys_user_grmember');
        grMem.addQuery('group', groupId);
        grMem.addActiveQuery();
        grMem.query();

        while (grMem.next()) {
            email.addBCC(grMem.user.email.toString());
        }
    }

})(current, template, email, email_action, event);

3. Final Notification Config

  • Subject: Leave it empty (or put a placeholder), as the script will overwrite it.

  • Message HTML: Only strictly needs the script call: ${mail_script:add_group_bcc}

How this works: The Inbound Action bundles the original email's content into a text package -> The Event carries it -> The Email Script opens the package and paints the new email with the original subject and HTML body, while preserving your Company Template wrapper.

If this response helps you solve the issue, please mark it as Accepted Solution.
This helps the community grow and assists others in finding valid answers faster.


Best regards,
Brandão.

View solution in original post

11 REPLIES 11

Itallo Brandão
Tera Guru

Hi @neil_b ,

The issue you are facing stems from a confusion between Record Templates (sys_template) and Email Templates/Layouts (sysevent_email_template).

The Diagnosis:

  1. Why the Template fails: The method mail.applyTemplate() and the GlideTemplate class are designed to apply Field Value templates (e.g., setting Default Priority/Category on an Incident form). They are not designed to wrap email body text with HTML Layouts (Logos, Footers). That is why your logs show undefined properties—the object doesn't work the way you expect for Email Layouts.

  2. Why Manual Insert is risky: Manually inserting records into sys_email bypasses the Notification subsystem. This makes it extremely difficult to apply Layouts, handle user notification preferences, or manage complex recipients like BCC groups.

The Solution (Best Practice): Instead of manually building the email object, you should trigger an Event and let a standard Notification handle the Template and BCC logic.

Step 1: Register an Event

  • Go to Event Registry.

  • Create a new event, e.g., inbound.group.notification.

Step 2: Update your Inbound Action Replace your entire complex script with this simple trigger. We will pass the Group SysID as Parameter 1.

 
(function runAction(/*GlideRecord*/ current, /*GlideRecord*/ event, /*EmailWrapper*/ email, /*ScopedEmailLogger*/ logger, /*EmailClassifier*/ classifier) {

    var groupSysID = '12345678910'; // Your Group Sys ID
    // Fire the event. passing the Group ID in Parm1
    gs.eventQueue('inbound.group.notification', current, groupSysID, email.sys_updated_by);

})(current, event, email, logger, classifier);

Step 3: Create the Notification

  • System Notification > Notifications > New

  • When to send: Event is fired -> inbound.group.notification.

  • Who will receive: (Leave empty, we will handle BCC in script).

  • What it contains:

    • Email Template: Select your "Our Company Notification Template" here (This solves issue #1 natively).

    • Message HTML: Add the content you want.

    • Description: Include a Mail Script call: ${mail_script:add_group_bcc}.

Step 4: Create the Mail Script (For BCC)

  • System Notification > Email > Notification Email Scripts > New

  • Name: add_group_bcc

  • Script:

(function runMailScript(/* GlideRecord */ current, /* TemplatePrinter */ template, /* Optional EmailOutbound */ email, /* Optional GlideRecord */ email_action, /* Optional GlideRecord */ event) {

    // Retrieve the Group ID passed in Parm1
    var groupId = event.parm1;

    if (groupId) {
        var grMem = new GlideRecord('sys_user_grmember');
        grMem.addQuery('group', groupId);
        grMem.addActiveQuery();
        grMem.query();

        while (grMem.next()) {
            // This is the magic method to add BCC
            email.addBCC(grMem.user.email.toString());
        }
    }

})(current, template, email, email_action, event);

Summary: By moving to the Event > Notification model, ServiceNow automatically applies the correct HTML Template/Layout wrapper for you, and the Mail Script cleanly handles the BCC loop.

If this response helps you solve the issue, please mark it as Accepted Solution.
This helps the community grow and assists others in finding valid answers faster.

Best regards,
Brandão.

Hi @Itallo Brandão I will try this method you are suggesting.

 

I have one question though. How can I get the contents and subject from my inbound email, and use those two components in the outgoing notification? I'm using this section to obtain the subject and body of the inbound email.

    var subject = email.subject;
    var bodyHtml = email.body_html;
    var bodyText = email.body_text;

I'm using this section to insert those two components in the outgoing email.

    var mail = new GlideRecord("sys_email");
    mail.initialize();
    mail.mailbox.setDisplayValue("Outbox"); // Place the new email in the Outbox
    mail.subject = subject;
    mail.type = mailType;
    mail.recipients = sender;

    // Include HTML body if available and set content type
    if (bodyHtml) {
        mail.body_html = bodyHtml;
        mail.body = bodyHtml; // Fallback for plain text clients
        mail.content_type = "text/html";
    } else {
        mail.body = bodyText;
        mail.content_type = "text/plain";
    }

	// Insert email record
    mail.insert();

Your suggested method skips the other lines of code that I need to accomplish this. Would I have to use email scripts to obtain the information from the inbound email to use in the outgoing notification?

Hi Neil,

That is an excellent catch. Since we are moving away from the manual sys_email insert, we need a mechanism to "transport" that dynamic content (Subject and Body) from the Inbound Action to the Notification.

You are absolutely correct: Email Scripts are the key here.

Since gs.eventQueue only accepts string parameters, the best practice is to package your Subject and Body into a JSON Object and pass it via Parm2.

Here is the updated implementation:

1. Update Inbound Action (The Packer) We will package your data into a JSON string and pass it in the 4th argument (Parameter 2).

 
(function runAction(/*GlideRecord*/ current, /*GlideRecord*/ event, /*EmailWrapper*/ email, /*ScopedEmailLogger*/ logger, /*EmailClassifier*/ classifier) {

    var groupSysID = '12345678910'; 

    // 1. Build a Payload Object with the data you want to transfer
    var payload = {
        subject: email.subject + '', // ensure string
        body: email.body_html ? email.body_html + '' : email.body_text + ''
    };

    // 2. Fire Event: Pass GroupID in Parm1, and JSON Payload in Parm2
    gs.eventQueue('inbound.group.notification', current, groupSysID, JSON.stringify(payload));

})(current, event, email, logger, classifier);

 

2. Update the Email Script (The Unpacker) Modify the add_group_bcc script we created earlier. It will now handle both the BCC logic and the content injection.

  • Script Name: add_group_bcc

 
(function runMailScript(/* GlideRecord */ current, /* TemplatePrinter */ template, /* Optional EmailOutbound */ email, /* Optional GlideRecord */ email_action, /* Optional GlideRecord */ event) {

    // --- PART A: Handle Content (Subject & Body) ---
    if (event.parm2) {
        var data = JSON.parse(event.parm2); // Unpack the JSON
        
        // 1. Set the Subject dynamically
        if (data.subject) {
            email.setSubject(data.subject);
        }

        // 2. Print the original HTML body
        if (data.body) {
            template.print(data.body);
        }
    }

    // --- PART B: Handle BCC (Group Members) ---
    var groupId = event.parm1;
    if (groupId) {
        var grMem = new GlideRecord('sys_user_grmember');
        grMem.addQuery('group', groupId);
        grMem.addActiveQuery();
        grMem.query();

        while (grMem.next()) {
            email.addBCC(grMem.user.email.toString());
        }
    }

})(current, template, email, email_action, event);

3. Final Notification Config

  • Subject: Leave it empty (or put a placeholder), as the script will overwrite it.

  • Message HTML: Only strictly needs the script call: ${mail_script:add_group_bcc}

How this works: The Inbound Action bundles the original email's content into a text package -> The Event carries it -> The Email Script opens the package and paints the new email with the original subject and HTML body, while preserving your Company Template wrapper.

If this response helps you solve the issue, please mark it as Accepted Solution.
This helps the community grow and assists others in finding valid answers faster.


Best regards,
Brandão.

Hi @Itallo Brandão thank you so much for providing the code! I have copy pasted what you suggested and when testing my inbound action, the event was triggered but the state errored. Any ideas why it would error? It appears Parm1 and Parm2 were both properly captured.

error.png

Here is the URI (which shows no record):

URI.png

Here is the inbound action script:

	var groupSysID = '12345678910'; 

    // 1. Build a Payload Object with the data you want to transfer
    var payload = {
        subject: email.subject + '', // ensure string
        body: email.body_html ? email.body_html + '' : email.body_text + ''
    };

    // 2. Fire Event: Pass GroupID in Parm1, and JSON Payload in Parm2
    gs.eventQueue('inbound.group.notification', current, groupSysID, JSON.stringify(payload));

Here is the event registration as well:event reg.pngHave I configured something incorrectly?