Send email from NetSuite reliably by using a server-side SuiteScript integration that calls Volanea’s REST API after a record is created. This guide shows a practical pattern for sales-order confirmations, but the same approach works for customer records, cases, invoices, custom records, and other NetSuite business events.
There is no native Volanea NetSuite SuiteApp, Marketplace listing, or one-click connector to install. The direct integration route is NetSuite SuiteScript: a server-side script can make outbound HTTPS requests with NetSuite’s N/https module, which lets your account call Volanea without exposing the email API key in a browser or client-side form.
The implementation below uses a User Event script attached to the Sales Order record. Its concrete trigger is NetSuite’s afterSubmit entry point when the record operation is CREATE. In business terms: when a sales order is saved as a new record, NetSuite builds an email payload and sends it to Volanea.
What this NetSuite integration does
The direct pattern has four parts:
- A sales order is created in NetSuite.
- A User Event script runs in
afterSubmitfor theCREATEevent. - The script reads fields from the saved sales order, including the transaction number, recipient email, and total.
- The script sends a
POSTrequest to Volanea’sPOST /v1/sendendpoint, using Bearer authentication and anIdempotency-Keyheader.
That sequence is intentionally server-side. NetSuite client scripts run in a user’s browser, where a secret API key could be exposed to anyone able to inspect the page or its network activity. A User Event script runs on the NetSuite server after the record is committed, making it the appropriate place for an outbound transactional email call.
For a basic order confirmation, the email body can contain values NetSuite already has:
- The sales order transaction number (
tranid) - The order recipient’s email (
email) - The order total (
total) - The NetSuite internal record ID (
id) - A stable idempotency value derived from the order ID and email purpose
Volanea accepts a JSON request containing from, to, subject, text, and html. Its single-message endpoint can send to one recipient or up to 50 recipients in one request, but an order confirmation should usually address one recipient at a time. That makes the send history, customer support investigation, suppression behavior, and retry behavior much easier to understand.
Why use SuiteScript instead of a NetSuite workflow email action
NetSuite SuiteFlow workflows can perform built-in actions, including sending NetSuite email. But a workflow’s built-in Send Email action does not turn the workflow into an arbitrary HTTP client for a third-party transactional email API.
SuiteScript is the appropriate direct route because NetSuite’s N/https module supports outbound HTTPS calls from server scripts. A User Event script can react to a record event, construct a request body, add HTTP headers, post the JSON payload, and inspect the HTTP response.
For this use case, a User Event script has a few important advantages:
It uses a real record-processing trigger
NetSuite calls User Event scripts when record operations occur. The example in this guide specifically handles the CREATE operation in afterSubmit, which means the message is attempted only after the new sales order has been saved.
This matters because an order number, internal ID, total, and custom fields can be incomplete or unavailable before the record is committed. Sending after submission also avoids emailing an order confirmation for a transaction the user later abandons before saving.
It keeps the sending decision close to the record
The record is the source of truth. Your script can decide whether to send based on fields such as order status, a custom “send confirmation” checkbox, a payment indicator, a subsidiary, a customer category, or whether the order contains a particular item.
For example, you might send only when a sales order is created through an ecommerce integration, not when an internal employee creates a draft order. That decision can be expressed directly in the script before calling Volanea.
It supports safe API authentication
NetSuite includes API Secrets management for values such as passwords and API keys. A secret can be referenced from SuiteScript without hardcoding the plaintext key in the script file. The secret is not suitable for a Client Script, Suitelet page source, custom record field visible to users, or a browser-side configuration object.
It supports idempotency
An integration should treat an email send as a business event, not merely as a successful HTTP request. If the first request reaches Volanea but NetSuite loses the response, blindly retrying could create a second receipt. Volanea supports the Idempotency-Key request header on POST /v1/send, so the same logical send can be retried safely with the same key.
The trigger: Sales Order created in NetSuite
This guide uses the following trigger:
NetSuite record type: Sales Order
Script type: User Event
Entry point:afterSubmit
Event type:CREATE
A User Event deployment can be restricted to the Sales Order record type. The afterSubmit function receives a context object. The function checks context.type and exits unless the operation was a new-record creation.
This is more precise than using a vague “order changed” rule. A sales order can be edited multiple times: shipping information may change, a sales rep may update a memo, payment details may arrive later, or fulfillment may proceed in stages. If every edit triggered the same notification, customers could receive repeated confirmation messages.
For an initial confirmation, CREATE is a clear business event. If your actual requirement is “send when the order reaches a particular status,” use a distinct pattern: compare the old and new values in afterSubmit, and send only when the status changes from one known state to another. Do not rely on every EDIT event being a meaningful lifecycle transition.
Prepare Volanea before adding the NetSuite script
Before NetSuite can send, create a Volanea secret key and verify the domain you plan to use in the from address. The sender address in the code, such as orders@example.com, must belong to a domain you have configured for sending.
Use a dedicated transactional sender rather than a personal employee address. For example:
orders@example.comfor confirmations and receiptsbilling@example.comfor invoices and payment noticessupport@example.comfor case updates
Keep the production key separate from any test key. That gives your team a safer way to test script deployments without accidentally sending customer-facing messages from the production domain.
The Volanea API reference documents the endpoint, authentication format, accepted message fields, and API responses. Consult the email API reference and setup guides when you need to add templates, attachments, scheduled delivery, webhooks, or a different sending pattern.
Store the Volanea API key securely in NetSuite
The API key belongs in NetSuite API Secrets, not in the SuiteScript source file and not in a normal script parameter.
In NetSuite, API Secrets are managed at Setup > Company > Preferences > API Secrets. A secret is referenced by its script ID and the secret value itself cannot be displayed after storage. SuiteScript 2.x can use that reference with secure-string APIs when constructing an authenticated outbound request.
Create a secret with a script ID such as:
custsecret_volanea_api_key
Then save the Volanea secret key value there. The example code references that script ID inside a secure string:
const authorization = https.createSecureString({
input: 'Bearer {custsecret_volanea_api_key}'
});
The braces are important: NetSuite substitutes the secret when it prepares the secure string. The script can pass the secure value as an HTTP Authorization header without storing a literal sk_... key in the source code.
What not to do with the API key
Do not put the key in any of these locations:
- A Client Script or JavaScript bundle delivered to a browser
- A custom field on a sales order, customer, or custom record
- An inline HTML field, Suitelet response, or URL parameter
- A source-controlled SuiteScript file
- A normal text script parameter that administrators or other users may view
- A debug log statement or error message
Client-side scripts are especially risky. NetSuite’s outbound HTTPS APIs are not designed to let an unauthenticated client-side context make arbitrary protected third-party calls. More importantly, a browser-visible configuration makes a long-lived email API key recoverable by someone who should never have it.
Use least privilege around the secret as well. Limit who can manage API Secrets and who can edit or deploy the script. Rotate the Volanea key if it is ever exposed, if ownership changes, or as part of your organization’s credential-rotation process.
Working SuiteScript: map a new sales order to a Volanea email
The following SuiteScript 2.1 file is a User Event script. It reads standard sales-order fields, builds the JSON payload NetSuite sends to Volanea, sets the Bearer token securely, and uses a deterministic idempotency key.
This example assumes the Sales Order’s standard email field contains the recipient email address. If your account stores the recipient in a custom body field instead, replace email with that field’s actual script ID, such as custbody_order_recipient_email.
/**
* @NApiVersion 2.1
* @NScriptType UserEventScript
*/
define(['N/https', 'N/log'], (https, log) => {
const VOLANEA_SEND_URL = 'https://api.volanea.com/v1/send';
const FROM_ADDRESS = 'orders@example.com';
function escapeHtml(value) {
return String(value ?? '')
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
}
function afterSubmit(context) {
// Concrete NetSuite trigger: a Sales Order has been created.
if (context.type !== context.UserEventType.CREATE) {
return;
}
const order = context.newRecord;
const orderId = order.id;
const orderNumber = order.getValue({ fieldId: 'tranid' });
const recipientEmail = order.getValue({ fieldId: 'email' });
const total = order.getValue({ fieldId: 'total' });
// Do not call the API without a usable destination address.
if (!recipientEmail) {
log.error({
title: 'Volanea order email skipped: missing recipient',
details: `Sales Order internal ID ${orderId} has no email value.`
});
return;
}
const safeOrderNumber = escapeHtml(orderNumber);
const safeTotal = escapeHtml(total);
// This is the actual JSON payload sent from NetSuite to Volanea.
const payload = {
from: FROM_ADDRESS,
to: [recipientEmail],
subject: `We received your order ${orderNumber}`,
text: [
`Thanks for your order ${orderNumber}.`,
`Order total: ${total}`,
'We will send another update when your order progresses.'
].join('\n'),
html: [
`<h1>Thanks for your order ${safeOrderNumber}</h1>`,
`<p>Order total: <strong>${safeTotal}</strong></p>`,
'<p>We will send another update when your order progresses.</p>'
].join('')
};
// Same sales order + same email purpose = same logical send.
const idempotencyKey = `netsuite-salesorder-${orderId}-confirmation-v1`;
// The API key is stored in NetSuite API Secrets, not plaintext code.
const authorization = https.createSecureString({
input: 'Bearer {custsecret_volanea_api_key}'
});
try {
const response = https.post({
url: VOLANEA_SEND_URL,
headers: {
'Content-Type': 'application/json',
'Authorization': authorization,
'Idempotency-Key': idempotencyKey
},
body: JSON.stringify(payload)
});
if (response.code < 200 || response.code >= 300) {
log.error({
title: 'Volanea send failed',
details: JSON.stringify({
salesOrderId: orderId,
statusCode: response.code,
responseBody: response.body,
idempotencyKey: idempotencyKey
})
});
return;
}
log.audit({
title: 'Volanea order email accepted',
details: JSON.stringify({
salesOrderId: orderId,
salesOrderNumber: orderNumber,
recipient: recipientEmail,
statusCode: response.code,
idempotencyKey: idempotencyKey
})
});
} catch (error) {
log.error({
title: 'Volanea HTTPS request error',
details: JSON.stringify({
salesOrderId: orderId,
idempotencyKey: idempotencyKey,
name: error.name,
message: error.message
})
});
}
}
return { afterSubmit };
});
Field mapping in the example
The table below makes the mapping explicit rather than treating the email body as a black box.
| NetSuite sales-order field | SuiteScript expression | Volanea field or usage |
|---|---|---|
| Internal ID | order.id | Part of Idempotency-Key |
| Transaction number | order.getValue({ fieldId: 'tranid' }) | subject, text, and html |
| Recipient email | order.getValue({ fieldId: 'email' }) | to[0] |
| Sales order total | order.getValue({ fieldId: 'total' }) | text and html |
| Fixed transactional sender | FROM_ADDRESS | from |
| Message purpose/version | confirmation-v1 | Part of Idempotency-Key |
The message uses both text and html. The HTML version gives mailbox clients a richer presentation, while the plain-text version remains useful for text-only mail clients, accessibility contexts, support tooling, and fallback rendering.
The escapeHtml function is not decorative. Transaction numbers and totals are comparatively safe-looking values, but any value rendered into HTML should be escaped. If you later include customer-entered fields such as a purchase-order note, shipping instructions, custom memo, or product description, HTML escaping becomes essential.
Deploy and test the script carefully
Upload the file to the NetSuite File Cabinet, create a Script record for the User Event script, and deploy it to the Sales Order record type. Restrict the deployment to the audiences and execution contexts that make sense for your account.
For initial testing, avoid broad production exposure. Use a test sender and a controlled recipient address if possible. Create a single test order, then inspect both NetSuite’s script execution logs and Volanea’s send activity.
A disciplined test sequence looks like this:
- Confirm the Volanea sending domain is verified and the
fromaddress is valid. - Create the NetSuite API Secret and grant only the necessary administrators and script access.
- Deploy the User Event script to Sales Orders.
- Create one sales order with a known test email value in the
emailfield. - Check the execution log for the
Volanea order email acceptedaudit entry. - Confirm the email appears in Volanea’s send log and arrives in the test mailbox.
- Edit the same sales order and verify that no new confirmation is sent, because the sample handles only
CREATE.
If you decide to send on an order-stage transition later, write separate tests for each path: status unchanged, status changed into the target stage, status changed out of the stage, script retried, recipient missing, and order created through an integration instead of the NetSuite UI.
Make duplicate protection part of the design
A successful integration must handle uncertainty. The outbound call could reach Volanea and create a send, but NetSuite might not receive the HTTP response because of a network interruption. Or an administrator might rerun a process after seeing an error. Without duplicate protection, a retry can become a duplicate order confirmation.
The example uses this idempotency key:
netsuite-salesorder-<internal-id>-confirmation-v1
That design includes three stable pieces of information:
- The integration source:
netsuite-salesorder - The specific business record: the internal sales-order ID
- The message purpose and content version:
confirmation-v1
The key should remain exactly the same when retrying the same confirmation. It should change for a legitimately different transactional email. For instance, a shipping update could use:
netsuite-salesorder-<internal-id>-shipment-<fulfillment-id>-v1
An invoice notice could use:
netsuite-invoice-<invoice-id>-payment-due-v1
Avoid including a current timestamp in the idempotency key. A timestamp makes every retry look like a new operation, defeating the purpose of idempotency.
When this breaks
This integration crosses a specific boundary: a NetSuite server-side script makes an HTTPS call to Volanea. Most failures occur at that hop, in the data leading into it, or in the ambiguity created when a request times out.
NetSuite retries or duplicate execution cause repeated sends
A script can run more than once because of an operational retry, a record recreation, an integration replay, or a deployment change. If each execution generates a new random request identifier, Volanea sees separate email requests.
Use the deterministic Idempotency-Key pattern from the example. For one sales-order confirmation, the same order ID and message version must yield the same key every time. If NetSuite repeats the request, Volanea can recognize that it represents the same logical send.
Also keep the User Event condition narrow. The sample only sends on CREATE, not every EDIT. If you later introduce multiple triggers, use a custom “email sent” field or a custom outbound-message record only when you need additional business-state tracking beyond API idempotency.
The outbound HTTPS call times out
NetSuite’s https.post has connection and request timing limits. In particular, an outbound request can time out if it takes too long to establish the connection or transmit the request body. The important operational detail is that a timeout does not prove the downstream service did not receive the message.
Treat an unknown result as retryable only with the same idempotency key. Log the order ID, HTTP status when available, and key, but never log the Volanea API key. For high-value communications, consider adding a durable retry queue using a custom record or a scheduled processing pattern rather than relying on a user-event execution alone.
A payload field is absent on some records
The standard sales-order email field may be blank for some orders, especially if records are created by integrations, imports, or internal processes that do not populate the same fields as your UI workflow. A recipient may also live on a customer, contact, or custom field in your account rather than on the transaction itself.
The sample deliberately stops when recipientEmail is blank. Do not substitute an arbitrary internal address or send to a sales rep just to make the API call succeed. Instead, decide which source is authoritative, validate that source, and create an exception workflow for records that do not meet the sending requirements.
If recipient data is plan- or role-dependent in your NetSuite configuration, verify field availability in the specific account and role where the script executes. Custom fields can also be unavailable in a sandbox, a subsidiary-specific form, or a deployment copied between accounts if the field IDs differ.
The sender domain is not ready
If the from address uses a domain that has not been verified in Volanea, the API request can be rejected or mail can fail delivery policy checks. Configure and verify the sending domain first, including the DNS records Volanea provides for its sending setup.
Do not use an employee’s consumer mailbox address as a transactional sender. A consistent domain-aligned sender is better for authentication, customer recognition, reply handling, and deliverability operations.
The code sends malformed or unsafe content
A hand-built HTML string can break if record values contain special characters. Escape every dynamic value inserted into HTML. Keep a plain-text version as well.
For complex receipts with line items, tax detail, addresses, and localized currencies, consider rendering from a template rather than continually extending string concatenation in one User Event file. The send endpoint supports template-oriented workflows as well as direct HTML and text content.
Direct SuiteScript versus middleware
The direct SuiteScript approach is usually the best choice when your requirement is straightforward: a NetSuite record event should send a transactional email and your team can maintain a small server-side script.
Use direct SuiteScript when you need:
- A short path from a saved NetSuite record to an email send
- Access to fields at the record event
- Server-side secret handling inside NetSuite
- Deterministic idempotency based on internal record IDs
- Fewer external systems to monitor
Middleware can still be appropriate when the workflow spans multiple systems. For example, you may want NetSuite to feed an automation platform or your own webhook service, enrich the payload with warehouse or CRM data, create approval steps, and then call Volanea. That architecture can be useful, but it introduces another retry domain. Your middleware must preserve a stable idempotency key and must not expose the Volanea key in a browser-accessible automation step.
If you use Zapier, Make, or a custom webhook receiver, send only the business data required to make the email decision. Let the middleware hold the Volanea key as a server-side credential, map the record fields explicitly, and return quickly to the originating system. Add logging that lets you trace a particular NetSuite order ID through the middleware and into the email provider.
Operational practices for production email
The code sample is intentionally compact, but a production implementation deserves clear ownership and monitoring.
First, separate acceptance from delivery. A successful response from the send endpoint means Volanea accepted the request for processing; it is not the same as proof that the recipient opened the email or that a mailbox accepted it. Use Volanea delivery events and message activity when investigating issues.
Second, define who owns failed sends. A missing email field is a data-quality problem. A rejected sender is an email-configuration problem. A timeout is an integration reliability problem. A bounce may be a recipient-quality problem. Different teams may own each category, but all of them need enough information to act without exposing customer data or secrets unnecessarily.
Third, maintain a small change-management checklist for email edits:
- Update the idempotency version when a changed template should be treated as a distinct business message.
- Keep the same version when retrying the identical message.
- Test custom field IDs in sandbox before moving a deployment to production.
- Test the exact execution context used by integrations and imports.
- Verify the intended sender domain before activating a new sender address.
- Monitor failures, bounce patterns, and suppression results after rollout.
Finally, decide whether your message is transactional or promotional. An order confirmation is transactional: it is directly related to a customer’s purchase. Do not quietly turn it into a marketing campaign by adding unrelated promotional content without considering consent, local requirements, and your own communication policy.
Conclusion
To send email from NetSuite with Volanea, use a server-side SuiteScript integration rather than looking for a native marketplace app. A Sales Order User Event script can run in afterSubmit when a record is created, map sales-order fields into Volanea’s POST /v1/send JSON payload, authenticate with a NetSuite API Secret, and protect against duplicate sends with a deterministic Idempotency-Key.
Start small with one event and one email type. Validate the recipient field, use a verified sender, log non-sensitive identifiers, and test the retry path as carefully as the happy path. Once that foundation is reliable, you can extend the same architecture to invoices, fulfillment notices, support cases, customer onboarding, and custom-record workflows.
FAQ
Does Volanea have a native NetSuite app or SuiteApp?
No. Volanea does not provide a native NetSuite Marketplace app, SuiteApp, or installable NetSuite plugin. The direct option is a NetSuite SuiteScript integration that calls Volanea’s HTTPS REST API.
What NetSuite event starts the email in this example?
The example uses a Sales Order User Event script. It sends only when NetSuite calls afterSubmit with the CREATE event type, meaning a new sales order has been saved.
Where should the Volanea API key be stored in NetSuite?
Store it in NetSuite API Secrets, then reference the secret from server-side SuiteScript with a secure string. Do not hardcode it in a script, place it in a browser-side Client Script, or store it in a visible record field.
How do I prevent duplicate order-confirmation emails?
Send the same Idempotency-Key for every retry of the same business event. For example, use the stable NetSuite sales-order internal ID plus a message purpose and version: netsuite-salesorder-123-confirmation-v1.
Can I send when an order changes status instead of when it is created?
Yes. Use afterSubmit for edits, compare the old and new status values, and send only when the record enters the exact status that should trigger the email. Use a separate idempotency key for that lifecycle event.