Acumatica + Twilio = A Hackathon Solution in 4 Hours

Marco Villasenor | September 12, 2017

With the growth of the web, there has also been a boom of software companies that provide access to their applications through web APIs and even offer microservices for very specific functions that help build complex and more powerful applications.

Why is this interesting? Well, this rise of APIs has created an expanding ecosystem of services and tools businesses can integrate into their own processes. If their systems allow it, a company can focus on its core operation while benefiting from functionality that would take too long to develop or maintain to be cost effective.

I like to think Acumatica has cloud in its DNA and it really shows when we work on integrations: it’s much easier to work with applications and services that provide a web API. If we need to work from within an external application, Acumatica allows us to easily expose data via OData and business objects through SOAP and REST web services. On the other hand, if we need to consume a service from within Acumatica, its Customization Platform provides tools to embed the new features into existing parts of the ERP.

A Practical Example

Last February, we participated in the first Acumatica Hackathon held during the Acumatica Summit in San Diego. Our team decided to work on a project that showed the power of “connecting clouds” by adding voice and SMS notifications to Acumatica by using Twilio.

Twilio is a cloud communications platform that provides web APIs to add messaging, voice, and video in web and mobile applications. The module and its source code are available in github: Acumatica-Twilio.

This project serves as a good example of how quickly it is to build a self contained module that adds specific functionality to Acumatica, so let’s do a step-by-step review of how this module was built.

Project Design

The first thing we needed to do is create a new customization project so the new functionality can be deployed and even shared for publication in other instances. The following diagram shows an overview of all the module parts:

Overview of all the module parts

Using Twilio’s API

In order to send SMS and Voice notifications from Acumatica using Twilio, we needed to consume its web API.
Luckily, there is a Twilio C#/.Net helper library which handles all requests and can be referenced directly.

So we added a reference to the library to our project using NuGet:


Install-Package Twilio

We then built a class to wrap the library and expose two simple methods that allowed us to either send an SMS message or start a call:


private TwilioRestClient _client;
public string Origin { get; set; }

public TwilioNotification(string sid, string token)
{
_client = new TwilioRestClient(sid, token);
}

public void SendSMS(string number, string message)
{
var msg = _client.SendMessage(Origin, number, message);

if (msg.RestException != null)
{
throw new PXException(msg.RestException.Message);
}
}

public void SendCall(string number, string message)
{
var call = _client.InitiateOutboundCall(Origin, number, MessageUrl(message));

if (call.RestException != null)
{
throw new PXException(call.RestException.Message);
}
}

If this looks simple, it’s because it is! For the SMS notifications, we just provide an origin and destination number along with the desired text message and Twilio does the rest.

Voice notifications need a little more of preparation. For voice, Twilio offers a simple text-to-speech service that we can use to read messages as audio. This service requires us to provide a callback URL where their engine looks for instructions of what to do when the call is connected.

We handled this by using one of the utility services offered by Twilio called “twimlets”. These micro apps generate Twilio instructions on the fly from the information they get on the URL query string.

By using the simple message twimlet, we can just build an URL with our message as part of the query string and the twimlet will build the required code for Twilio to say it.


private static string TwimletBase = "http://twimlets.com/message";

...

public static string MessageUrl(params string[] messages)
{
var messageCollection = new NameValueCollection();

for (int i = 0; i < messages.Length; i++)
{
messageCollection.Add("Message[" + i + "]", messages[i]);
}

return TwimletBase + messageCollection.ToQueryString();
}

Setup Screen

In order to create an instance of the client library, we needed to provide the credentials of a Twilio account, so we programmed a Setup screen to enable any user to enter their own.

It’s always a good practice to use the PXRSACryptString attribute provided by Acumatica to encrypt the credentials in the new database table.


#region TwilioAccountSid

public abstract class twilioAccountSid : IBqlField { }

[PXRSACryptString(255)]
[PXDefault]
[PXUIField(DisplayName = "Twilio Account Sid")]
public string TwilioAccountSid { get; set; }

#endregion

#region TwilioAuthToken

public abstract class twilioAuthToken : IBqlField { }

[PXRSACryptString(255)]
[PXDefault]
[PXUIField(DisplayName = "Twilio Auth Token")]
public string TwilioAuthToken { get; set; }

#endregion

Twilio requires sending the original phone number for voice notifications, so we also added a field to store this number so it is always sent in the call request.

Finally, we also added the option of selecting the notification templates used for voice and SMS notifications.


#region SMSNotificationID

public abstract class sMSNotificationID : IBqlField { }

[PXDBInt]
[PXDefault]
[PXSelector(typeof(Notification.notificationID),
DescriptionField = typeof(Notification.name), ValidateValue = true)]
[PXUIField(DisplayName = "Notification ID")]
public int? SMSNotificationID { get; set; }

#endregion

#region CallNotificationID

public abstract class callNotificationID : IBqlField { }

[PXDBInt]
[PXDefault]
[PXSelector(typeof(Notification.notificationID),
DescriptionField = typeof(Notification.name), ValidateValue = true)]
[PXUIField(DisplayName = "Call Notification ID")]
public int? CallNotificationID { get; set; }

#endregion

Once we defined the Setup DAC, we created a simple Graph for it and the related Page as TW101000 and added it to the Site Map under Management -> Configure -> Twilio Integration.

Twilio Integration

Adding Actions

Now comes the interesting part: adding the action to trigger a notification. The Acumatica Customization Platform allows us to extend existing business logic by using the PXGraphExtension class.

For our project, we decided to send notifications for invoices with due balance, but this behavior can easily be added to other business objects.

First, we created an extension of the ARInvoiceMaint graph and added a private helper function to handle sending the notifications. Depending on its type, the function prepares the content from the NotificationID configured in the setup graph and calls our Twilio class to send the message.


public void SendTwilioNotification(ARInvoiceEntry invGraph, string notificationType, bool isMassProcess = false)
{
ARInvoiceEntryPXExt invGraphExt = invGraph.GetExtension<ARInvoiceEntryPXExt>();

//Raise error if Twilio Integration Setup not configured
if (invGraphExt.TwilioSetupInfo.Current == null)
throw new PXException(Messages.TwilioAccountNotSetup);

//Get current billing contact
if (invGraph.Billing_Contact.Current == null)
invGraph.Billing_Contact.Current = invGraph.Billing_Contact.Select();
ARContact contact = invGraph.Billing_Contact.Current;

if (contact == null || String.IsNullOrEmpty(contact.Phone1))
throw new PXException(Messages.InvoiceBillingContactNotExists);

int? iNotificationID = (notificationType == TwilioNotificationType.SMS) ?
invGraphExt.TwilioSetupInfo.Current?.SMSNotificationID :
invGraphExt.TwilioSetupInfo.Current?.CallNotificationID;

//Raise notification appropriate error message
if (notificationType == TwilioNotificationType.SMS)
{
if (iNotificationID == null)
throw new PXException(Messages.SMSNotificationIDNotSpecified);
}
else if (notificationType == TwilioNotificationType.OutBoundCall)
{
if (iNotificationID == null)
throw new PXException(Messages.CallNotificationIDNotSpecified);
}
else
throw new PXException(Messages.UNSpecifiedTwilioNotificationType);

//Get the notification
PX.SM.Notification notification = PXSelect<PX.SM.Notification,
Where<PX.SM.Notification.notificationID, Equal<Required<PX.SM.Notification.notificationID>>>>.
Select(invGraph, invGraphExt.TwilioSetupInfo.Current.SMSNotificationID);

if (notification == null)
throw new PXException(Messages.NotificationNotFound);

//Process the datafields and get Subject and Notification Body ready.
string subjectNotification = String.Format("{0} - {1}", (notificationType == TwilioNotificationType.SMS) ?
Messages.SMSSubjectPrefix : Messages.CallSubjectPrefix,
PX.Data.Wiki.Parser.PXTemplateContentParser.
Instance.Process(notification.Subject, invGraph,
invGraph.Document.Current.GetType(), null));
string bodyNotification = PX.Data.Wiki.Parser.PXTemplateContentParser.
Instance.Process(notification.Body, invGraph,
invGraph.Document.Current.GetType(), null);

//Create Twilio Notification
var twilio = new TwilioNotification(invGraphExt.TwilioSetupInfo.Current.TwilioAccountSid,
invGraphExt.TwilioSetupInfo.Current.TwilioAuthToken)
{
Origin = invGraphExt.TwilioSetupInfo.Current.TwilioFromPhoneNumber
};

if (notificationType == TwilioNotificationType.SMS)
{
twilio.SendSMS(contact.Phone1, PX.Data.Search.SearchService.Html2PlainText(bodyNotification));
}
else if (notificationType == TwilioNotificationType.OutBoundCall)
{
twilio.SendCall(contact.Phone1, PX.Data.Search.SearchService.Html2PlainText(bodyNotification));
}
else
throw new PXException(Messages.UNSpecifiedTwilioNotificationType);

//Create Activity in Acumatica
CreateTwilioNotificationActivity(invGraph, subjectNotification, bodyNotification);
}

The last line of the function was added later to add the notification to the document activity as a nice add-on. Please review the full code to see how this was done.

After this, we added the actions to trigger each notification type. This is done by using the [PXProcessButton] attribute to each method. We wrapped the SendTwilioNotification in a PXLongOperation to send the notifications asynchronously and avoid blocking the user interface.


#region Actions

public PXAction<ARInvoice> SendSMSNotification;

[PXUIField(DisplayName = "Send SMS Notification", MapEnableRights = PXCacheRights.Insert, MapViewRights = PXCacheRights.Select)]
[PXProcessButton]
public virtual IEnumerable sendSMSNotification(PXAdapter adapter)
{
ARInvoice invoice = PXCache<ARInvoice>.CreateCopy(Base.Document.Current);
PXLongOperation.StartOperation(Base, delegate
{
ARInvoiceEntry invGraph = PXGraph.CreateInstance<ARInvoiceEntry>();
invGraph.Document.Current = invoice;
ARInvoiceEntryPXExt invGraphExt = invGraph.GetExtension<ARInvoiceEntryPXExt>();
invGraphExt.SendTwilioNotification(invGraph, TwilioNotificationType.SMS);
});
return adapter.Get();
}

public PXAction<ARInvoice> SendCallNotification;

[PXUIField(DisplayName = "Send Call Notification", MapEnableRights = PXCacheRights.Insert, MapViewRights = PXCacheRights.Select)]
[PXButton(SpecialType = PXSpecialButtonType.Cancel)]
public virtual IEnumerable sendCallNotification(PXAdapter adapter)
{
ARInvoice invoice = PXCache<ARInvoice>.CreateCopy(Base.Document.Current);
PXLongOperation.StartOperation(Base, delegate
{
ARInvoiceEntry invGraph = PXGraph.CreateInstance<ARInvoiceEntry>();
invGraph.Document.Current = invoice;
ARInvoiceEntryPXExt invGraphExt = invGraph.GetExtension<ARInvoiceEntryPXExt>();
invGraphExt.SendTwilioNotification(invGraph, TwilioNotificationType.OutBoundCall);
});
return adapter.Get();
}

#endregion

Finally we included rules to control when to enable the actions in the ARInvoice_RowSelected event and we added the actions to the Actions menu.


public override void Initialize()
{
TwilioNotificationSetup setup = TwilioSetupInfo.Current;

Base.action.AddMenuAction(SendSMSNotification);
Base.action.AddMenuAction(SendCallNotification);
}

Send SMS Notification

Processing screen

As the last part of the module, we built a simple processing screen. The processing screen was build as a new graph that shows all invoices with pending balance.

A custom DAC added as a processing filter allows the user to select the action (SMS or voice notification) and to filter documents by date, workgroup or owner.

The process delegate for each selected document just calls the SendTwilioNotification method with the selected action.

Send Twilio Notification

Conclusion

As we have seen, we were able to add very useful functionality to our ERP system in a few hours. By leveraging the capacities of Acumatica xRP and its Customization Framework we can offer powerful features to our customers in a shorter time by connecting to existing SaaS applications available right now.

Editors Note

The coding of the above integration was accomplished in only 4 hours! Take a moment to take that in to really appreciate, not only the power or the Acumatica xRP Platform – but also the skill and creativity of Marco and their team. You can see clearly, a very powerful combination.

For more information about Syntegh, please visit their website and Twitter feed.

Marco Villasenor

Technology Director at interastar

Categories: Developers, Platform

Subscribe to our bi-weekly newsletter

Evaluating Cloud Financials Systems?

Don’t go any further until you’ve read Gartner’s analysis of what 10 leading vendors have to offer. You’ll also learn why Gartner named Acumatica a Visionary.