Dynamics AX For Retail Enterprise POS (EPOS) has functionality to email receipts but the implementation of the feature has a few tradeoffs:
- Email does not get sent immediately at the conclusion of the transaction. Instead, the process goes like this: 1) a transaction is uploaded to HQ (via P-job); 2) it gets calculated on a statement; 3) it then gets posted via statement posting; 4) finally, the Send Email Receipts batch process runs and sends the email. Statement posting is usually done nightly, so this means emails usually won’t get sent out until the next day. While a simple customization will make Send Email Receipts send email for unposted transactions, there is still a lag until the P-job uploads the transactions. Randy Higgins has a good blog post on how this process works.
- Generation of the receipt is done via an SSRS report. This adds quite a bit of overhead to the process.
- Receipts are sent via a PDF attachment instead of the body of the email.
- Changing the look and feel of the receipt means customizing an SSRS report instead of using the receipt designer. This means that the emailed receipts will look very different than the printed receipt.
These limitations are largely overcome in the Modern Point of Sale (MPOS) for AX 2012 R3 and Microsoft Dynamics 365 for Operations:
- Email is sent via SMTP immediately at the conclusion of the transaction. This is done with a Realtime Service call to Headquarters.
- The receipt is sent in the body of the email message.
- Generation of the email receipt is done by the MPOS application. This is done by using the same layout as the printed receipt.
Randy’s follow-up blog post for MPOS goes through the details of how this process works.
Ideas for Improvement
Because upgrading to MPOS is not an option for some customers, I looked into some options for improving email receipts in EPOS. Here are a few ideas I came up with:
- Change the Send Email Receipts batch process to forego SSRS and generate a message body (HTML or simple text) and send directly. This would get past the SSRS performance issue and PDF attachment, but would be a lot of X++ development and would still not necessarily match the printed receipt. It would also not get past the lag of needing to upload the the transactions via the P-job first.
- Have EPOS generate and send an email directly via SMTP without calling Realtime Service. This would eliminate the Realtime Service call at the end of each transaction, but it would also mean managing SMTP communication directly from each EPOS instance. This would also mean database changes (EPOS would have to have information about its SMTP server) and making sure that SMTP communication from dozens or hundreds of different IP addresses is healthy. I like having AX manage the SMTP traffic and besides, EPOS can call a Realtime Service call just as easily as MPOS can.
- Mirror the MPOS functionality as much as possible as an EPOS customization. I settled on this one so as to not introduce a third way of emailing receipts: there is still just the “MPOS way” and the “EPOS way.”
There are really only a few things that the customization has to do:
- Generate the email body of the receipt
- Determine the email address to send it to
- Call the same Realtime Service method that MPOS does to send the message
Realtime Service call from PostEndTransaction Trigger
As a refresher, I talk about customizing Realtime Service (previously Transaction Service) in this blog post. In this case, the X++ work is already done for us since we’ll be using the same method that MPOS calls: SendEmail. A good place to add this call is in the PostEndTransaction trigger (in the TransactionTriggers project).
The RetailTransactionService::SendEmail X++ method takes the following parameters:
- SysEmailId emailId: This is the email template as defined in Organization Administration > Setup > E-mail Templates. MPOS hard-codes this to “EmailRecpt” so we’ll do the same.
- CustLanguageId languageId: This is the language of the EPOS client. We have this available in the System.Threading.Thread.CurrentThread.CurrentUICulture.Name variable.
- Email email: The recipient’s email address. EPOS already has logic to ask the user to confirm an email address when needed. This gets stored to the RECEIPTEMAIL column of the RETAILTRANSACTIONTABLE and is used by the Send Email Receipts batch job. We can use the same value in our trigger: retailTransaction.ReceiptEmailAddress
- str serializedMappings: This is a series of key/value pairs for substitution in your email template. For instance, if you have a %customername% in the body of your email template, you would send in the pair “key: customername, value:Contoso Entertainment” as part of this XML. As noted in Randy’s blog post, MPOS only sends one key/value pair: %message%. This means that we will send the entire receipt as this one key/value: “key: message, value: a big string for the entire receipt”.
- str xmlData: Not used.
- boolean isTraceable: Hard-coded to false.
- boolean isWithRetries: Hard-coded to true.
Note that I pulled this from MPOS code in the SDK: Retail SDK CU9\Commerce Run-time\Workflow\Orders\GetReceiptRequestHandler.cs (the parameters are in a different order).
In our EPOS code, our Realtime Service call looks like this:
Getting the Receipt Text for the Email Body
The text of a printed receipt is generated in the Printing service. We could repeat that logic in our PostEndTransaction trigger but that seems like an unnecessary task when the work has already been done for us.
Keep in mind that receipt printing is pretty simple: using a form layout as defined in the Receipt Format Designer, data from the Retail Transaction object is converted to a simple string of formatted text. This is then sent as-is (with some simple formatting) to the printer. For our customization, we will intercept that string and save it to a PartnerData variable on the transaction (see this blog post for information about PartnerData). This value will then persist to our trigger and we can use it as the body of the email.
This can be done with a small customization to the PrintFormTransaction() method in Services\Printing\PrintingActions.cs:
Pulling it Together:
The text that comes back from GetTransformedTransaction has some special formatting characters which need to be cleaned up. This can be done in the trigger (more code taken from the MPOS SDK):
Note that this is essentially the same code from MPOS (GetReceiptRequestHandler.cs).
Also, even though we are only sending in a single key/value pair, to make it consistent with MPOS, it still needs to be sent in as a serialized collection. I’ve added a helper class and a couple helper methods for this usage:
Finally there is some simple logic of whether to send the message and some basic error handling:
If things work correctly, you can test it with a customer that has an email address associated. EPOS will prompt the user to confirm or change the recipient’s address:
And if you are using smtp4dev for your testing, you’ll see the message come across:
You’ll find the the full source code for this customization attached below and it can be used as-is. I’ve included the baseline R3 CU12 versions of the files from the SDK which can be used for code diffs.
There are a few areas that you might want to improve upon:
- The receipt email depends on a Realtime Service call. If this call fails the user will get a message but there is no retry logic in place (which is is the same limitation as MPOS). You may want to add some error handling or a mechanism to track failed messages. Also, if Realtime Service connection is lost, it may add processing time to the transactions before a timeout occurs.
- For retry logic, you could look into adding a button on the Show Journal page to re-send the email message. Alternatively, it might be useful to have the ability to send an email directly from AX.
- By default, EPOS will only prompt to send messages for named customers that have an email address associated with their account. You may want to add UI and logic to prompt for an email receipt for one-off customers as well.
- If you look closely at the email body above you’ll notice that the horizontal spacing is not implemented correctly. I noticed this as I was writing up this post and it is also an issue in MPOS. Keep an eye out for an update to this article when I find a solution for that problem.