Sign Up

Sign Up to our social questions and Answers Engine to ask questions, answer people’s questions, and connect with other people.

Have an account? Sign In

Have an account? Sign In Now

Sign In

Login to our social questions & Answers Engine to ask questions answer people’s questions & connect with other people.

Sign Up Here

Forgot Password?

Don't have account, Sign Up Here

Forgot Password

Lost your password? Please enter your email address. You will receive a link and will create a new password via email.

Have an account? Sign In Now

You must login to ask a question.

Forgot Password?

Need An Account, Sign Up Here

Please briefly explain why you feel this question should be reported.

Please briefly explain why you feel this answer should be reported.

Please briefly explain why you feel this user should be reported.

Sign InSign Up

The NetSuite Pro

The NetSuite Pro Logo The NetSuite Pro Logo

The NetSuite Pro Navigation

  • Home
  • About Us
  • Tutorials
    • NetSuite Scripting
    • NetSuite Customization
    • NetSuite Integration
    • NetSuite Advanced PDF Templates
    • NetSuite Reporting & Analytics Guide
    • Real-World NetSuite Examples
  • Blog
  • Contact Us
Search
Ask A Question

Mobile menu

Close
Ask A Question
  • Home
  • About Us
  • Tutorials
    • NetSuite Scripting
    • NetSuite Customization
    • NetSuite Integration
    • NetSuite Advanced PDF Templates
    • NetSuite Reporting & Analytics Guide
    • Real-World NetSuite Examples
  • Blog
  • Contact Us
Home/ Real-World NetSuite Examples/Dynamic “From” Email by Subsidiary (Branded Transaction Emails)

Dynamic “From” Email by Subsidiary (Branded Transaction Emails)

💼 Business problem

Multi-subsidiary (or multi-brand) accounts need customer emails to come from the correct brand mailbox (e.g., billing@brandA.com for Subsidiary A and accounts@brandB.com for B). The standard “Email” action often uses a single sender or the logged-in user—leading to SPF/DMARC issues and confused replies.

Goal: Provide a Send Branded Email action on transactions that:

  • Picks author (sender employee/service mailbox) by subsidiary
  • Uses a template (subject/body merge)
  • Attaches the PDF for the transaction
  • Sets Reply-To, CC/BCC, and logs an audit note

🧠 Approach

  • A Suitelet renders a small form (To/CC/BCC, template picker, preview) and sends the email using N/email.
  • A User Event (beforeLoad) adds a “Send Branded Email” button to Invoices/Estimates/Sales Orders.
  • JSON mapping (script parameter) maps subsidiaryId → {authorEmployeeId, replyTo, defaultCc}.
  • Use N/render to generate the transaction PDF attachment from your Advanced PDF template.

🔧 Prerequisites

  • Create service Employees for each brand mailbox (or use company-wide email if enabled). Ensure they can send email.
  • Optional File Cabinet email HTML templates, or keep inline.
  • Deployment parameter (Long Text): custscript_brand_email_map with JSON like:
{
  "2": { "author": 1234, "replyTo": "accounts@brandA.com", "cc": "billing@brandA.com" },
  "3": { "author": 5678, "replyTo": "invoices@brandB.com", "cc": "" },
  "default": { "author": 1234, "replyTo": "accounts@brandA.com", "cc": "" }
}

(IDs are examples: 2=Subsidiary A, 3=Subsidiary B; author=Employee internal ID used as email sender.)

  • Optional body field on transactions for audit: custbody_last_brand_email_note (Text).

🧩 User Event (adds the button)

/**
 * @NApiVersion 2.1
 * @NScriptType UserEventScript
 * Title: UE | Add "Send Branded Email" button
 */
define(['N/url','N/runtime'], (url, runtime) => {
  const beforeLoad = (ctx) => {
    const { form, newRecord, type } = ctx;
    if (![ctx.UserEventType.VIEW, ctx.UserEventType.EDIT].includes(type)) return;
    if (!['invoice','estimate','salesorder','cashsale'].includes(newRecord.type)) return;

    const slUrl = url.resolveScript({
      scriptId: 'customscript_sl_brand_email',
      deploymentId: 'customdeploy_sl_brand_email',
      params: { txnId: newRecord.id, txnType: newRecord.type }
    });

    form.addButton({
      id: 'custpage_send_branded_email',
      label: 'Send Branded Email',
      functionName: `window.open('${slUrl}','_blank')`
    });
  };
  return { beforeLoad };
});

🧩 Suitelet (form → send email)

/**
 * @NApiVersion 2.1
 * @NScriptType Suitelet
 * Title: SL | Send Branded Email
 */
define(['N/ui/serverWidget','N/runtime','N/record','N/render','N/email','N/file','N/format','N/log'],
(ui, runtime, record, render, email, file, format, log) => {

  const P_MAP = 'custscript_brand_email_map'; // deployment parameter

  function onRequest(ctx){
    const req = ctx.request, res = ctx.response;
    const txnId = req.parameters.txnId, txnType = req.parameters.txnType || 'invoice';
    if (!txnId) return writeError(res, 'Missing transaction id');

    if (req.method === 'GET') {
      const { toEmail, customerName, subsidiaryId, tranId, amount, defaultSubject, defaultBody } =
        primeTxnInfo(txnType, txnId);

      const map = loadMap();
      const cfg = map[subsidiaryId] || map.default || {};
      const form = ui.createForm({ title: `Send Branded Email — ${tranId}` });

      const fldTo = form.addField({ id:'custpage_to', label:'To', type: ui.FieldType.EMAIL });
      fldTo.defaultValue = toEmail || '';
      form.addField({ id:'custpage_cc', label:'CC', type: ui.FieldType.TEXT }).defaultValue = cfg.cc || '';
      form.addField({ id:'custpage_bcc', label:'BCC', type: ui.FieldType.TEXT });

      const fldSubj = form.addField({ id:'custpage_subject', label:'Subject', type: ui.FieldType.TEXT });
      fldSubj.defaultValue = defaultSubject;

      const fldBody = form.addField({ id:'custpage_body', label:'Body (HTML allowed)', type: ui.FieldType.LONGTEXT });
      fldBody.defaultValue = defaultBody;

      const fldReply = form.addField({ id:'custpage_replyto', label:'Reply-To', type: ui.FieldType.EMAIL });
      fldReply.defaultValue = cfg.replyTo || '';

      form.addField({ id:'custpage_txnid', label:'txnId', type: ui.FieldType.TEXT }).updateDisplayType({ displayType: ui.FieldDisplayType.HIDDEN }).defaultValue = String(txnId);
      form.addField({ id:'custpage_txntype', label:'txnType', type: ui.FieldType.TEXT }).updateDisplayType({ displayType: ui.FieldDisplayType.HIDDEN }).defaultValue = String(txnType);

      form.addSubmitButton({ label: 'Send Email' });
      res.writePage(form);
      return;
    }

    // POST — send
    const to = req.parameters.custpage_to;
    const cc = req.parameters.custpage_cc;
    const bcc = req.parameters.custpage_bcc;
    const subject = req.parameters.custpage_subject || '';
    const body = req.parameters.custpage_body || '';
    const replyTo = req.parameters.custpage_replyto || '';
    const postTxnId = req.parameters.custpage_txnid;
    const postTxnType = req.parameters.custpage_txntype;

    try {
      const info = primeTxnInfo(postTxnType, postTxnId);
      const map = loadMap();
      const cfg = map[info.subsidiaryId] || map.default || {};
      if (!cfg.author) throw new Error('Missing author in mapping for this subsidiary');

      const pdf = renderPDF(postTxnType, postTxnId); // file.File object
      const author = Number(cfg.author);

      email.send({
        author,
        recipients: to,
        cc: (cc || '').split(',').map(s=>s.trim()).filter(Boolean),
        bcc: (bcc || '').split(',').map(s=>s.trim()).filter(Boolean),
        subject: merge(subject, info),
        body: merge(body, info),
        attachments: pdf ? [pdf] : [],
        relatedRecords: { transactionId: Number(postTxnId) },
        replyTo
      });

      // audit on transaction
      try {
        record.submitFields({
          type: toRecType(postTxnType),
          id: Number(postTxnId),
          values: { custbody_last_brand_email_note: `Sent via Suitelet by ${author} → ${to} (${new Date().toISOString()})` },
          options: { enableSourcing: false, ignoreMandatoryFields: true }
        });
      } catch (_e){}

      res.write(`<html><body><h3>Email sent</h3><p>To: ${to}</p><a href="javascript:window.close()">Close</a></body></html>`);

    } catch (e) {
      log.error('send failed', e);
      writeError(res, `Failed to send: ${e.message || e}`);
    }
  }

  function primeTxnInfo(txnType, id){
    const rec = record.load({ type: toRecType(txnType), id: Number(id) });
    const entity = rec.getValue('entity');
    const toEmail = rec.getValue('email') || rec.getValue('custbody_email_override') || '';
    const customerName = rec.getText('entity') || '';
    const subsidiaryId = String(rec.getValue('subsidiary') || '');
    const tranId = rec.getValue('tranid') || '';
    const total = rec.getValue('total') || '';
    const defaultSubject = `[${tranId}] Invoice from {{company}}`;
    const defaultBody = [
      `Hello ${customerName || 'Customer'},`,
      `<br/><br/>Please find your ${txnType} <b>${tranId}</b> attached.`,
      `<br/>Total: ${format.format({ value: total, type: format.Type.CURRENCY })}`,
      `<br/><br/>Thank you,`,
      `<br/>{{company}}`
    ].join('\n');

    return { toEmail, customerName, subsidiaryId, tranId, amount: total, defaultSubject, defaultBody };
  }

  function renderPDF(txnType, id){
    try {
      return render.transaction({ entityId: Number(id), printMode: render.PrintMode.PDF, inCompanyLanguage: false });
    } catch (_e) { return null; }
  }

  function merge(str, info){
    if (!str) return '';
    return String(str)
      .replace(/\{\{company\}\}/g, runtime.accountId) // or fetch company name/subsidiary name if you prefer
      .replace(/\{\{tranid\}\}/g, info.tranId)
      .replace(/\{\{amount\}\}/g, info.amount);
  }

  function loadMap(){
    try {
      const raw = runtime.getCurrentScript().getParameter({ name: P_MAP }) || '{}';
      return JSON.parse(raw);
    } catch (_e) { return {}; }
  }

  function toRecType(type){
    switch (String(type)) {
      case 'invoice': return record.Type.INVOICE;
      case 'salesorder': return record.Type.SALES_ORDER;
      case 'estimate': return record.Type.ESTIMATE;
      case 'cashsale': return record.Type.CASH_SALE;
      default: return record.Type.INVOICE;
    }
  }

  function writeError(res, msg){
    res.write(`<html><body><h3 style="color:#b00;">${escapeHtml(msg)}</h3><a href="javascript:window.close()">Close</a></body></html>`);
  }
  function escapeHtml(s){ return String(s||'').replace(/[&<>"']/g,m=>({'&':'&','<':'<','>':'>','"':'"',"'":'''}[m])); }

  return { onRequest };
});

✅ How it works in the UI

  1. Open an Invoice (or SO/Estimate) → click Send Branded Email.
  2. Suitelet pre-fills To, Subject, Body, Reply-To, CC based on subsidiary mapping.
  3. Click Send → it emails with the correct sender and attaches the PDF.
  4. An audit note is stored on the transaction.

🧪 Testing checklist

  • Map Subsidiary → Author (Employee ID) in the JSON parameter.
  • Test with two subsidiaries to confirm the From mailbox switches.
  • Verify Reply-To goes to the brand inbox.
  • Confirm the PDF attaches and the subject/body placeholders render.
  • DMARC/SPF: send to an external mailbox and inspect headers (“From”, “Sender”, “Reply-To”).

🌱 Enhancements

  • Pull subsidiary legal name and brand phone into the body (use record.load on Subsidiary or cache in the map).
  • Add a template selector (dropdown from custom record of templates).
  • Add a “Send Statement” mode (render a Statement PDF via render.statement).
  • Add role permissions: only AR roles see the button.
  • Add multi-language placeholders based on customer language.

📌 Summary

NeedSolution
Emails must come from the correct brandSubsidiary→sender JSON mapping
Attach branded PDFsrender.transaction attachment
Keep replies in the right inboxreplyTo per subsidiary
Track and auditBody field note per send
Share
  • Facebook

Leave a ReplyCancel reply

Sidebar

Ask A Question

Stats

  • Questions 6
  • Answers 6
  • Best Answers 0
  • Users 2
  • Popular
  • Answers
  • Rocky

    Issue in running a client script in NetSuite SuiteScript 2.0 ...

    • 1 Answer
  • admin

    How can I send an email with an attachment in ...

    • 1 Answer
  • admin

    How do I avoid SSS_USAGE_LIMIT_EXCEEDED in a Map/Reduce script?

    • 1 Answer
  • admin
    admin added an answer The issue is usually caused by following Wrong script file… September 14, 2025 at 10:33 pm
  • admin
    admin added an answer Steps to send an Invoice PDF by email: define(['N/email', 'N/render',… August 28, 2025 at 3:05 am
  • admin
    admin added an answer This error means your script hit NetSuite’s governance usage limit… August 28, 2025 at 3:02 am

Top Members

Rocky

Rocky

  • 1 Question
  • 22 Points
Begginer
admin

admin

  • 5 Questions
  • 2 Points

Trending Tags

clientscript netsuite scripting suitescript

Explore

  • Home
  • Add group
  • Groups page
  • Communities
  • Questions
    • New Questions
    • Trending Questions
    • Must read Questions
    • Hot Questions
  • Polls
  • Tags
  • Badges
  • Users
  • Help

Footer

© 2025 The NetSuite Pro. All Rights Reserved