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:
- Purchase Orders
- Sales Orders
- Item Fulfillments
- Customer Invoices
The NetSuite Transaction Email Address Reference Challenge
- 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.
- 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.
- We then would run the saved search and add a filter condition to pass in the ID of the record being created;
- We would take the results, automatically de-duplicate the list, and append any other existing transaction email addresses already in place.
- Finally, we turn it into one string delimited by semi-colons and then update the transaction’s email field.
NetSuite SuiteScript to Solve Email Challenge
/* * 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())); } }
What type of search did you use? What fields were listed besides transaction internal id and email?
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
Hi Marty,
Is it possible to send statements to multiple email addresses?
-Andrew
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.
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
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.
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
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()));
}
}
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