Tracing a mystery BPM


Here’s a weird situation where one customer (in Customer Entry, 10.0.700.4) seems to require something in the CustCnt.EmailAddress field.If it’s missing, I get this error message when I save, but can’t find where it’s coming from:


I created a test customer that doesn’t have an email, and Epicor took it without error. I can also delete and save in Person Contact Maintenance. How can I figure out what’s causing this pop-up?


  1. I figured it’s a BPM, but couldn’t see one connected either to the Customer or CustCnt methods that would cause. We’ve got 3 BPM’s connected to the Customer.Update method, but none seem to do this. Is there a way to search for the text in the error message if I export all the BPM’s?
  2. I didn’t see a data directive that would do that (just a change log), or something in Extended Property Maintenance making it required.
  3. I turned on trace after deleting the email and clicking save, and it points to CustCntImpl, but I only see CustCnt in Method Directive Maintenance.

Thanks for any suggestions!

Here’s snippets of the trace to preserve the innocent (I can provide more if nec…)

  <localTime>4/24/2018 15:57:55:3774008 PM</localTime>
    <parameter name="ds" type="CustCntDataSet">
      <CustCntDataSet xmlns="">


    <paramDataSet name="ds" useDataSetNbr="0">
      <changedValue tableName="CustCnt" rowState="Modified" rowNum="0" colName="EMailAddress"><![CDATA[]]></changedValue>

And the full error message:

Business Layer Exception

A valid Email Address is required.

Exception caught in: Epicor.ServiceModel

Error Detail

Description: A valid Email Address is required.
Program: Erp.Services.BO.CustCnt.dll
Method: CustCntBeforeUpdate
Table: CustCnt
Field: EmailAddress
Server Trace Stack: at Erp.Services.BO.CustCntSvc.CustCntBeforeUpdate()
at Ice.Services.Trace.TablesetProfilingCollector.DoRowEventTrace(String tableName, String methodName, Int32 rowCount, Action action)
at Ice.TablesetBound3.UpdateRow(IceDataContext context, Int32 tableNum, IIceTable table, IceRow updatedRow, IceRow originalRow, TablesetProfilingCollector parentTraceCollector) at Ice.TablesetBound3.WriteTable(IceDataContext context, Int32 tableIndex, IIceTable table, TablesetProfilingCollector parentTraceCollector)
at Ice.TablesetBound3.InnerUpdate(IceDataContext context, TFullTableset tableset) at Erp.Services.BO.CustCntSvc.Update(CustCntTableset& ds) at Erp.Services.BO.CustCntSvcFacade.Update(CustCntTableset& ds) at SyncInvokeUpdate(Object , Object[] , Object[] ) at System.ServiceModel.Dispatcher.SyncMethodInvoker.Invoke(Object instance, Object[] inputs, Object[]& outputs) at Epicor.Hosting.OperationBoundInvoker.Invoke(Object instance, Func2 func)
at Epicor.Hosting.Wcf.EpiOperationInvoker.Invoke(Object instance, Object[] inputs, Object[]& outputs)
at System.ServiceModel.Dispatcher.DispatchOperationRuntime.InvokeBegin(MessageRpc& rpc)
at System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage5(MessageRpc& rpc)
at System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage31(MessageRpc& rpc)
at System.ServiceModel.Dispatcher.MessageRpc.Process(Boolean isOperationContextSet)
at System.ServiceModel.Dispatcher.ChannelHandler.DispatchAndReleasePump(RequestContext request, Boolean cleanThread, OperationContext currentOperationContext)
at System.ServiceModel.Dispatcher.ChannelHandler.HandleRequest(RequestContext request, OperationContext currentOperationContext)
at System.ServiceModel.Dispatcher.ChannelHandler.AsyncMessagePump(IAsyncResult result)
at System.Runtime.Fx.AsyncThunk.UnhandledExceptionFrame(IAsyncResult result)
at System.Runtime.AsyncResult.Complete(Boolean completedSynchronously)
at System.ServiceModel.Channels.SecurityChannelListener1.ReceiveItemAndVerifySecurityAsyncResult2.InnerTryReceiveCompletedCallback(IAsyncResult result)
at System.Runtime.Fx.AsyncThunk.UnhandledExceptionFrame(IAsyncResult result)
at System.Runtime.AsyncResult.Complete(Boolean completedSynchronously)
at System.ServiceModel.Channels.TransportDuplexSessionChannel.TryReceiveAsyncResult.OnReceive(IAsyncResult result)
at System.Runtime.Fx.AsyncThunk.UnhandledExceptionFrame(IAsyncResult result)
at System.Runtime.AsyncResult.Complete(Boolean completedSynchronously)
at System.ServiceModel.Channels.SynchronizedMessageSource.ReceiveAsyncResult.OnReceiveComplete(Object state)
at System.ServiceModel.Channels.SessionConnectionReader.OnAsyncReadComplete(Object state)
at System.Runtime.Fx.AsyncThunk.UnhandledExceptionFrame(IAsyncResult result)
at System.Net.LazyAsyncResult.Complete(IntPtr userToken)
at System.Net.Security.NegotiateStream.ProcessFrameBody(Int32 readBytes, Byte[] buffer, Int32 offset, Int32 count, AsyncProtocolRequest asyncRequest)
at System.Net.Security.NegotiateStream.ReadCallback(AsyncProtocolRequest asyncRequest)
at System.Net.FixedSizeReader.CheckCompletionBeforeNextRead(Int32 bytes)
at System.Net.FixedSizeReader.ReadCallback(IAsyncResult transportResult)
at System.Runtime.AsyncResult.Complete(Boolean completedSynchronously)
at System.ServiceModel.Channels.ConnectionStream.IOAsyncResult.OnAsyncIOComplete(Object state)
at System.ServiceModel.Channels.SocketConnection.OnReceiveAsync(Object sender, SocketAsyncEventArgs eventArgs)
at System.Net.Sockets.SocketAsyncEventArgs.FinishOperationSuccess(SocketError socketError, Int32 bytesTransferred, SocketFlags flags)
at System.Net.Sockets.SocketAsyncEventArgs.CompletionPortCallback(UInt32 errorCode, UInt32 numBytes, NativeOverlapped* nativeOverlapped)
at System.Threading._IOCompletionCallback.PerformIOCompletionCallback(UInt32 errorCode, UInt32 numBytes, NativeOverlapped* pOVERLAP)

Client Stack Trace

at Epicor.ServiceModel.Channels.ImplBase`1.ShouldRethrowNonRetryableException(Exception ex, DataSet[] dataSets)
at Erp.Proxy.BO.CustCntImpl.Update(CustCntDataSet ds)
at Erp.Adapters.CustCntAdapter.OnUpdate()
at Ice.Lib.Framework.EpiBaseAdapter.Update()
at Erp.UI.App.CustomerEntry.Transaction.UpdateContacts()

Generally if it’s a BPM that throws the error “BPM” in mentioned in the full error text. I’m not seeing that here.

On this CustID how many contacts do they have?


I’d have a look at this func on that form

1 Like

Did you try one where the email isn’t formatted like it’s an email. Like no @ sign, and no .com.

Thanks guys!

Brandon - It’ll take text only without error, such as ‘NONE’.

Chris - Where do dig more into the Erp.UI.App.CustomerEntry.Transaction.UpdateContacts() function?

Decompile that dll:
Erp.UI.CustomerEntry in your client folder. That func is found in the Transaction class

Did you check Extended Property Maintenance? You can set fields to be required there.

Wouldn’t that affect the test customer he created?

If they entered anything, even something like “n/a” or “None” it’d still get past that check as it doesn’t verify a valid email address. We had an issue like this in our old E10.0 that someone set a field to required and it started to cause issues.

Check the Extended Properties in the CustCnt table. You may have set a REquired Checkbox on that field.

Randy & Jose - Extended Properties in CustCnt table for EmailAddress is not checked ‘Required’. I can create a test customer with a blank email address, with no error. Not sure why this one customer (from what I can tell) has the Alert pop-up as email required.


Chris - Time for me to google how to decompile a DLL. I learn something new every day!

I found these threads:

  1. How can I see what business objects and methods are being called on an action and how do I use them?
  2. Any way to get grid format options to stick?
  3. Data Directives "Find corresponding table to data definition"

Nah I dont think thats a good direction anymore, it probably wont help unless you are c#'er and intend to dig deep.

I think I would focus on that specific customer. Look at all of the related settings and tables. Is this box checked?

I’d also search for all BPM directives and share that list here so the community can suggest some to check.

Hi Andris,

I usually like to separate possible Epicor bug / data problem from my BPMs by turning all BPMs off and seeing if get error still.
Use this method to disable all bpms:


Chris - Thanks. dnSpy showed my how complicated it is under the hood! I’m proud of myself for finding the Transaction section, he he he. :slight_smile:

Sync email is checked. I tried with and without, and no change (if I delete email in Customer Maint). If I’m in PerCon, I can delete the email and save without error.

Here are the BPM’s on Customer, and none seem to be tied to this CustID or raise an exception for email:

I thought maybe the address verification from Avalara might affect it, but unchecking Verify Address on our Customer Entry form doesn’t change things.

Nancy - Great idea! I’ll give that a shot.

Thanks guys.

What does the AssignAllEmailsAttribute do?

1 Like

Chris - AssignAllEmailsAttribute sets the Customer’s attribute to add them to our mailing list.

What about Data Directives, can you show us any Data Directives on the Customer or CustCnt tables?
Also, do you have any Method Directives on the CustCnt BO?
Seems like whatever the condition is, it’s dependent on some other value.

I have seen something similar before, in that instance there was a BPM to send an email, we were using a data field for the email address, but Epicor complained it wasn’t a proper email address, so I had to add one into the CC field.

What do the CorrectAddress and AtMail BPMs do?

Nancy - I tried disabling all BPM's via the web.config setting (very cool, by the way!), and still got the error message! I verified that other BPM's didn't fire (was concerned I'd need to recycle IIS or somehow reset things, but it didn't seem required). This same customer behavior is in our test db, so that's where I'm playing...

Rick - We have a few Data Directives for Customer, and none for CustCnt. CL-#### is only change logs, and the CatReqAutoPrint and CatRequest are for our mailings, which set fields via widgets, and do not have alerts.

CustCnt has 2 method directives. PrimaryCheckbox sets fields only, and currently isn’t enabled. LimitAMZNContacts shows up blank on the preview screen, and also isn’t enabled. That one looks for a CustID=### condition, and sets the pageSize argument.


Mark - CorrectAddress runs the mailing address through Avalara’s address verification via C# code, and doesn’t seem to touch the email field. AtMail looks for a condition that the ttCustomer.EmailAddress has an @mail or @usa domain name, and then sends an email to a manager.

Andy - The Customers email is, and I can’t find anything that would cause that to bomb out. Other customers let me delete their emails. I’ve tried to replicate the scenario, and can’t figure it out.

Thanks for the suggestions, everyone!