Time: 20 minutes | Level: Some SuiteScript helps
You will learn: How to turn plain text alerts into tidy messages with a reusable helper file.
You will need: The working alert from Step 2.
Plain text alerts work, but they get dull fast. Slack’s Block Kit lets you lay out messages with a header, tidy columns of facts and buttons, so people can understand an alert at a glance.
Good news: Block Kit is just JSON. If you can build an object in JavaScript, you can build a Block Kit message.
The building blocks you will use most
| Block | What it looks like | When to use it |
|---|---|---|
header |
Large bold title | The first line of the alert |
section with text |
A paragraph | A sentence of context |
section with fields |
Two neat columns | Order number, customer, total, date |
divider |
A thin line | Separating sections |
context |
Small grey text | “Sent by NetSuite at 10:42” |
actions |
Buttons | Open in NetSuite, Approve, Reject |
You can design and preview messages visually in Slack’s Block Kit Builder (search for it on api.slack.com), then copy the JSON across. That is the easiest way to get a layout you like.
A finished example
Here is what a “big order” alert looks like as Block Kit JSON:
{
"text": "Big order received: SO-10482",
"blocks": [
{
"type": "header",
"text": { "type": "plain_text", "text": "Big order received" }
},
{
"type": "section",
"fields": [
{ "type": "mrkdwn", "text": "*Order*\nSO-10482" },
{ "type": "mrkdwn", "text": "*Customer*\nAcme Kitchens" },
{ "type": "mrkdwn", "text": "*Total*\n$14,200.00" },
{ "type": "mrkdwn", "text": "*Ship date*\nFriday, Oct 2" }
]
},
{
"type": "actions",
"elements": [
{
"type": "button",
"text": { "type": "plain_text", "text": "Open in NetSuite" },
"url": "https://1234567.app.netsuite.com/app/accounting/transactions/salesord.nl?id=5521"
}
]
},
{
"type": "context",
"elements": [ { "type": "mrkdwn", "text": "Sent by NetSuite" } ]
}
]
}
Notice the top-level text. Slack shows it in notifications and on devices that cannot render blocks, so always include a short, meaningful one.
Step-by-step: turn it into a reusable helper
Rather than pasting JSON into every script, create one small helper library that every alert can share. That keeps messages consistent and means you fix a problem in one place.
1. Create a library file
Create slack_lib.js in the File Cabinet:
/**
* @NApiVersion 2.1
* @NModuleScope SameAccount
*
* Small helper for building and sending Slack messages.
*/
define(['N/https', 'N/log'], (https, log) => {
// Turn a list of [label, value] pairs into Block Kit "fields".
const toFields = (pairs) =>
pairs.map(([label, value]) => ({
type: 'mrkdwn',
text: '*' + label + '*\n' + (value === null || value === undefined || value === '' ? '-' : value)
}));
// Build a standard alert: header, fields, and an optional link button.
const buildAlert = ({ title, summary, facts, linkUrl, linkLabel }) => {
const blocks = [
{ type: 'header', text: { type: 'plain_text', text: title.substring(0, 150) } }
];
if (facts && facts.length) {
// Slack allows up to 10 fields per section.
blocks.push({ type: 'section', fields: toFields(facts.slice(0, 10)) });
}
if (linkUrl) {
blocks.push({
type: 'actions',
elements: [{
type: 'button',
text: { type: 'plain_text', text: linkLabel || 'Open in NetSuite' },
url: linkUrl
}]
});
}
return { text: summary || title, blocks };
};
// Send a payload to an incoming webhook. Returns true if Slack accepted it.
const postToWebhook = (webhookUrl, payload) => {
const response = https.post({
url: webhookUrl,
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload)
});
if (response.code !== 200) {
log.error('Slack webhook failed', response.code + ' ' + response.body);
return false;
}
return true;
};
return { buildAlert, postToWebhook };
});
2. Use it from your User Event script
Change your define line to load the library using its File Cabinet path, or better, a path relative to your script:
define(['N/runtime', 'N/url', 'N/log', './slack_lib'],
(runtime, url, log, slack) => {
const afterSubmit = (context) => {
try {
if (context.type !== context.UserEventType.CREATE) return;
const script = runtime.getCurrentScript();
const webhookUrl = script.getParameter({ name: 'custscript_slack_webhook_url' });
const order = context.newRecord;
const total = Number(order.getValue({ fieldId: 'total' })) || 0;
if (total < 10000) return;
const domain = url.resolveDomain({ hostType: url.HostType.APPLICATION });
const path = url.resolveRecord({ recordType: 'salesorder', recordId: order.id });
const payload = slack.buildAlert({
title: 'Big order received',
summary: 'Big order received: ' + order.getValue({ fieldId: 'tranid' }),
facts: [
['Order', order.getValue({ fieldId: 'tranid' })],
['Customer', order.getText({ fieldId: 'entity' })],
['Total', '$' + total.toFixed(2)],
['Ship date', order.getText({ fieldId: 'shipdate' })]
],
linkUrl: 'https://' + domain + path
});
slack.postToWebhook(webhookUrl, payload);
} catch (e) {
log.error('Slack alert failed', e.name + ': ' + e.message);
}
};
return { afterSubmit };
});
Two reminders. Keep the @NApiVersion and @NScriptType header comment at the top of the User Event script. And upload both files into the same folder, so the relative path ./slack_lib can find the library.
Tips for messages people actually like
- Lead with what happened. “Big order received” beats “Notification”.
- Put the most useful facts first. People skim the first two or three fields.
- Always include a link back to NetSuite. It is the one thing people reliably want.
- Use emoji sparingly. One consistent emoji per alert type helps people scan; a whole row of them looks like a party invitation.
- Keep it short. If a message needs scrolling, it is really a report, not an alert.
- Mention people carefully. In Slack,
<@U12345678>pings a specific person and<!here>pings everyone online. Use them only where a reply is genuinely needed.
Escaping special characters
Slack treats &, < and > as special in mrkdwn text. If a customer name could contain them (for example “Smith & Sons”), replace them with &, < and > before sending. A tiny helper does it:
const esc = (s) => String(s).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
What is next
You can now send handsome alerts. Let us make them interactive: approve NetSuite records straight from Slack.