Extend NetSuite’s Transaction Email Address List

NetSuite Technical

Tags: ,

This article is relevant if you are looking to extend NetSuite’s default “Email” or “To Be E-Mailed” {tobeemailed} capacity.

Background

By default, one of NetSuite’s strengths is that it has the ability to generate email transactions from most every transaction.  In our implementations, the ones that come up the most frequently are:

  1. Purchase Orders
  2. Sales Orders
  3. Item Fulfillments
  4. Customer Invoices
Fundamentally, NetSuite organizes these all the documents as a general Transaction Type.  NetSuite wants to read the “Email” as specified on the Transaction’s main Entity reference as the suggested email address to distribute the communication.  For a Purchase Order, the Entity will be a Vendor.  For a Sales Order, the Entity will be a Customer.  All good, but what do you do if you want a different email address reference?    What if you want to address multiple parties?

The NetSuite Transaction Email Address Reference Challenge

For a recent client engagement, we helped them leverage some work they were doing to solve this problem.   In their use case, they had created saved searches that would query all the relevant contacts’ email addresses under selected roles on the entity record.  They also would pull in sales people and other parties into the email list that needed to receive the communication.
The saved search query got them halfway there.  For every communication, they manually ran the Saved Search, outputted the list of email address results to Excel, de-duplicated the list and turned it into one long semi-colon string that would finally be cut-and-paste into transaction’s “To Be Emailed” field.  This became tedious fast and they needed a better way.
Seeing the pattern, and their need for flexibility, here is what we did:
  1. For every transaction, we created a SuiteScript that would run when the transaction was created.  This way, it would happen only once behind the scenes.
  2. We defined SuiteScript parameters which was specified on the deployment: a) a reference to the respective saved search and b) the column name in the saved search that contained the email address being looked up.
  3. We then would run the saved search and add a filter condition to pass in the ID of the record being created;
  4. We would take the results, automatically de-duplicate the list, and append any other existing transaction email addresses already in place.
  5. Finally, we turn it into one string delimited by semi-colons and then update the transaction’s email field.
In our client’s situation, they would not email their transactions from the Create event as they would always perform a record review for reasonableness once it was first committed.  Hence, they would observe the email address results of our automation; once the transaction was approved, they would turn on the “To Be E-Mailed” checkbox and NetSuite would send the email;  This saved them time and brought back the joy.

NetSuite SuiteScript to Solve Email Challenge

The following code pattern assumes you have background in SuiteScript implementation.  Of course, there are different approaches, but I hope this technique saves you time.
/*
 *  on transaction update, runs a saved search to retrieve a list of email addresses, de-dupes the list, and saves it into the 'email' field of the transaction
 */

function PROLECTO01_transactionBeforeSubmit(type) {

	var funcName = "PROLECTO01_transactionBeforeSubmit (" + type + " " + nlapiGetRecordType() + " " + nlapiGetRecordId() + ")";

	// we don't do anything on delete, and if this is a create, then we'll let it run on the afterSubmit, as we don't have a record ID yet
	if ((type == "create") || (type == "delete"))
		return;

	try {

		var emailList = PROLECTO01_getEmailListFromSavedSearch(nlapiGetRecordId());

		// we are here only on an update

		if (emailList)
			nlapiSetFieldValue("email",PROLECTO01_combineAndDedupeEmails(emailList + ';' + nlapiGetFieldValue("email")));

		nlapiLogExecution("DEBUG", funcName, "Updated Email List: " + nlapiGetFieldValue("email"));
	} catch (e) {
		nlapiLogExecution('ERROR', funcName, (e.name || e.getCode()) + ":" + (e.message || e.getDetails()));
	}
};

function PROLECTO01_transactionAfterSubmit(type) {

	var funcName = "PROLECTO01_transactionAfterSubmit (" + type + " " + nlapiGetRecordType() + " " + nlapiGetRecordId() + ")";

	// on the afterSubmit, we ONLY do it if this is a create; for updates, it was already handled by the beforeSubmit
	if (type != "create")  return;

	try {
		// we are here only on create; since record was already written, just post the single field
		var emailList = PROLECTO01_getEmailListFromSavedSearch(nlapiGetRecordId());

		if (emailList) {
			nlapiLogExecution("DEBUG", funcName, "email list found -- updating field ");
			nlapiSubmitField(nlapiGetRecordType(), nlapiGetRecordId(), "email",PROLECTO01_combineAndDedupeEmails(emailList + ';' + nlapiGetFieldValue("email")));
		}
		nlapiLogExecution("DEBUG", funcName, "Updated Email List: " + nlapiLookupField(nlapiGetRecordType(), nlapiGetRecordId(), "email"));
	} catch (e) {
		nlapiLogExecution('ERROR', funcName, (e.name || e.getCode()) + ":" + (e.message || e.getDetails()));
	}
};

// takes a list of commma or semi-colon separated emails, and returns a de-duped, semi-colon separated list
function PROLECTO01_combineAndDedupeEmails(emailList) {
	var funcName = "PROLECTO01_combineAndDedupeEmails";

	try {
		var tempList = emailList.split(",").join(";").split(";");
		var theList = [];

		for (var i = 0; i < tempList.length; i++) {
			var email = tempList[i].trim();

			if (theList.indexOf(email) < 0)
				theList.push(email);
		}

		nlapiLogExecution("DEBUG", funcName, theList.join(";"));

		return theList.join(";");
	} catch (e) {
		nlapiLogExecution('ERROR', funcName, (e.name || e.getCode()) + ":" + (e.message || e.getDetails()));
	}

}

// this function executes a saved search, filtering it by the transaction ID of the current record; it then extracts the merged email field
function PROLECTO01_getEmailListFromSavedSearch(tranId) {
	var funcName = "PROLECTO01_getEmailListFromSavedSearch";

	try {
		//here is the reference ot the script parameters which vary by deployment
		var searchId = nlapiGetContext().getSetting("SCRIPT", "custscript_prolecto_saved_search_id");
		var emailFieldLabel = nlapiGetContext().getSetting("SCRIPT", "custscript_prolecto_email_field_label");

		nlapiLogExecution("DEBUG", funcName, "Looking for field " + emailFieldLabel + " in Saved Search " + searchId);

		var filters = new Array();
		filters.push(new nlobjSearchFilter("internalid",null,"anyof",tranId))
		var searchResults = nlapiSearchRecord(null, searchId, filters, null) || [];
		nlapiLogExecution("DEBUG", funcName, searchResults.length + " rows returned");

		if (searchResults.length > 0) {
			var searchResultColumns = searchResults[0].getAllColumns();
			for (var columnNdx in searchResultColumns) {

				var column = searchResultColumns[columnNdx];
				var row = 0;

				if (column.getLabel() == emailFieldLabel) {
					var emailList = "";
					for (var row = 0; row < searchResults.length; row++)
						if (searchResults[row].getValue(column)) {
							nlapiLogExecution("DEBUG", funcName, "row " + row + " has " + searchResults[row].getValue(column));

							if (emailList.length > 0)
								emailList += ";";

							emailList += searchResults[row].getValue(column);
						}

					// nlapiLogExecution("DEBUG", funcName, "returning: " + emailList);
					return emailList;
				}
			}

			nlapiLogExecution("ERROR", funcName, "No column found with label '" + emailFieldLabel + "'");
			return;

		} else
			nlapiLogExecution("ERROR", funcName, "No rows found executing saved search " + searchId);
	} catch (e) {
		nlapiLogExecution('ERROR', funcName, (e.name || e.getCode()) + ":" + (e.message || e.getDetails()));
	}
}

Push NetSuite to Save You Time and Be More Productive

While using NetSuite, one of the commonly missed opportunities is taking control of a situation.  Most NetSuite users are information workers who are well paid to think and solve concerns.  Yet many times, the tool’s standard behavior demands operations that are mindless, tedious, and consume valuable time and energy that can be shifted to much more productive concerns.  You are not stuck with this default inferior way of working; the NetSuite Platform is full of untapped potential.  If you are ready to take matters into your hands and make work more enjoyable, let’s have a conversation.

Be Sociable, Share!

Marty Zigman

Holding all three official certifications, Marty is regarded as the top NetSuite expert and leads a team of senior professionals at Prolecto Resources, Inc. He is a former Deloitte & Touche CPA and has held CTO roles. For over 30 years, Marty has produced leadership in ERP, CRM and eCommerce business systems. Contact Marty to set up a conversation.

More Posts - Website - Twitter - Facebook - LinkedIn - YouTube

About Marty Zigman

Holding all three official certifications, Marty is regarded as the top NetSuite expert and leads a team of senior professionals at Prolecto Resources, Inc. He is a former Deloitte & Touche CPA and has held CTO roles. For over 30 years, Marty has produced leadership in ERP, CRM and eCommerce business systems. Contact Marty to set up a conversation.

Biography • Website • X (Twitter) • Facebook • LinkedIn • YouTube

9 thoughts on “Extend NetSuite’s Transaction Email Address List

  1. Eileen says:

    What type of search did you use? What fields were listed besides transaction internal id and email?

  2. Marty Zigman says:

    Hello Eileen,

    The search typically will be entity based. But it doesn’t have to be. The code reference allows you to set a script deployment parameter to your saved search, what ever it may be. It goes further by specifying the second script parameter to indicate which field has the email address. This approach puts you in control without needing to modify the script to fit your specific requirements.

    Marty

  3. Andrew Cohen says:

    Hi Marty,

    Is it possible to send statements to multiple email addresses?

    -Andrew

  4. Marty Zigman says:

    I believe there are email limitations with the native customer statement bulk driver. One approach is to program the message object. We pretty much don’t deal with this email limitation problem any longer with our Content Renderer Engine. See this article. We give this tool to all of our clients.

  5. Andrew Cohen says:

    Thanks Marty. The Content Renderer Engine looks awesome, but we’re not a client (would love to be, but we’re not quite large enough). I think we found a workaround, however, using workflows. Since a workflow can use any email field from the customer record, we set up two workflows. We kick the workflow off on the 10th with a filter looking for customers with a balance and/or a transaction within the last month. Then we apply a 1 day delay so that we can attach a statement that goes from same day last month (the 11th of the previous month) to yesterday (the 10th of the current month). It seems to replicate the built-in NetSuite functionality pretty well. Our first full run will be May 10th so I’ll let you know how it goes.

    -Andrew

  6. Kiran says:

    Greetings Mr.Martin,
    I follow our blog regularly. Our company has a requirement to send Invoices to multiple contacts. i tried the code that you have specified.
    1. Created a Customer saved search that fetches all contact emails.
    2. Deployed the script on invoice transaction.
    3. Script parameters are given on deploy page.
    4. Set General preferences.
    But I get an error saying – Looking for field null in Saved Search null.
    Could you please help me with this. I would be so very thankful to you.

  7. Marty Zigman says:

    Hi Kiran,

    It’s hard to comment on what you share here. We do this sort of thing for all of our clients. It seems you may benefit from our team helping on your specific situation. Send me a note here and we can get you help.

    Marty

  8. David says:

    I am trying to make this code work but receiving 0 errors, but also not getting an email in the field. I added our internal id of the search to final function and the custom label of the email column. Code below:

    // this function executes a saved search, filtering it by the transaction ID of the current record; it then extracts the merged email field
    function PROLECTO01_getEmailListFromSavedSearch(tranId) {
    var funcName = “PROLECTO01_getEmailListFromSavedSearch”;

    try {
    //here is the reference to the script parameters which vary by deployment
    var searchId = nlapiGetContext().getSetting(990, “custscript_prolecto_saved_search_id”);
    var emailFieldLabel = nlapiGetContext().getSetting(‘Email’, “custscript_prolecto_email_field_label”);

    nlapiLogExecution(“DEBUG”, funcName, “Looking for field ” + emailFieldLabel + ” in Saved Search ” + searchId);

    var filters = new Array();
    filters.push(new nlobjSearchFilter(“internalid”,null,”anyof”,tranId))
    var searchResults = nlapiSearchRecord(null, searchId, filters, null) || [];
    nlapiLogExecution(“DEBUG”, funcName, searchResults.length + ” rows returned”);

    if (searchResults.length > 0) {
    var searchResultColumns = searchResults[0].getAllColumns();
    for (var columnNdx in searchResultColumns) {

    var column = searchResultColumns[columnNdx];
    var row = 0;

    if (column.getLabel() == emailFieldLabel) {
    var emailList = “”;
    for (var row = 0; row 0)
    emailList += “;”;

    emailList += searchResults[row].getValue(column);
    }

    // nlapiLogExecution(“DEBUG”, funcName, “returning: ” + emailList);
    return emailList;
    }
    }

    nlapiLogExecution(“ERROR”, funcName, “No column found with label ‘” + emailFieldLabel + “‘”);
    return;

    } else
    nlapiLogExecution(“ERROR”, funcName, “No rows found executing saved search ” + searchId);
    } catch (e) {
    nlapiLogExecution(‘ERROR’, funcName, (e.name || e.getCode()) + “:” + (e.message || e.getDetails()));
    }
    }

  9. Marty Zigman says:

    David,

    Are you getting good information in your logs? Did you hook up the other functions to push the emailList into the email field?

    Marty

Leave a Reply

Your email address will not be published. Required fields are marked *