At the February 2024 Aptify Developer RoundTable, a great question came up: How to I send a message when a field value changes on a record?
Scenario
Here’s our scenario, we want to send a magic link to members who’s address gets flagged as a Bad Address from the Person record, so that they can update the address without needing to login.
Event Flow
Ok, next lets break down the event flow that is going to occur:
- From a Person record, add the addresses “Bad Address” field is set to true (to simply things we’ll just do business address).
- On the Person’s Before Save, check if the Bad Address was set from false to true. If do, set a Temp Field that Bad Address has changed to true.
- On the Person’s After Save, check for the Temp Field exists and if does, send message.
- The Message Run will use a message template uses a Scripted Message Part to retrieve the necessary details to create an html anchor with a href value that includes a query string value of the necessary encrypted value for the magic link.
And here are all the records we will create to do this:
- Before Save Event Definition (was bad address set to true)
- After Save Event Definition (does temp field exist)
- Process Flow (create message run)
Message Part and Template
Check out my previous article Send Magic Link in Message for creating the message part and template with a magic link.
Process Pipeline
Little Background First
When you save a record in Aptify you would think that within the “AfterSave” event, you should be able to evaluate if a fields OldValue equals something and the CurrentValue equals something to know if was changed, right? Well. No. In Aptify, the Generic Entity (record) does store two states of the record, the previous “old” state and the current state. What happens when you save a record, there is the internal “Save” event that does the commit to the database. Ok, so now that the data been committed the GE record essentially “reloads” itself as if you were just opening the record. This means the “old” and “current” state are now the same as it was just pulled from the DB. Now the AfterSave event execute.
This is why if you want to persist a field change to AfterSave you need to implement your own system. The simplest way to do this is using the Temporary Field feature of the GE. The GE has two methods for setting a fields value:
geRecord.SetValue("FieldName", value)
geRecord.SetAddValue("FieldName", value)
The difference is that with .SetValue, if the FieldName doesn’t exist, Aptify either throws and error or just doesn’t do anything (it’s been a long while since I’ve run code that this has occurred with). With .SetAddValue, if the FieldName doesn’t exist Aptify will add the field in memory and, the field will be included if the GE is serialized (This passing the GE to the Async Server).
When using Temporary Fields, I highly suggest you come up with a name unique naming scheme for you organization so that they will never conflict with actual field names. What has worked perfectly for me over the years is: _x{DescriptiveName}_{OrgAcronym}, for this example we’ll use: _xBadAddress_sa.
Ok, now let create the Event Definitions.
Event Definitions
Wait, wait, wait! Event definition? Why would we create a event definition instead of doing Aptify 101, and creating a simple Before Save Event Handler that executes a Process Flow?? That is an excellent question and very valid point. You 100% totally can do that and there is really good reasons to do that:
- You can easily flip it on and off
- You get all the benefits of a process flow
- Utilize existing process flow components
- Rule Scripts
- Could create a process flow component where you can keep all your before save logic in a single C# object
Ok, what if I want to go more advanced and ask “Why not a GE Plug-in”? Again, 100% valid option. GE plug-ins though required you code and override the base GE class. If it makes sense for you org, then I’m all for it.
Ok, why did I choose to create an Event Definition? Well, I just wanted to showcase this way of using an Event Definition. Were going to create a BeforeSave and an AfterSave event definition specifically for the Persons entity. No there is a very critical thing you have to know. These event definition will execute, regardless if an Event Handler uses them or not. This actually is for a lot of the OTB base events. This means No you can not flip it on and off. Also, you only have the option of writing it as a VB script and any updates are immediately live. Please, please, please, defensive programming, catch manage your errors/exceptions and remember, if the event fails, depending on the event, it can prevent the save or the event sequence from continuing. So at the end of the day, you might prefer the normal Event Handler route instead. But overall the process is the same.
Before Save Definition
Field | Value |
---|---|
Name | Persons Before Save - Set Temp Fields_sa |
Category | Use the one that make most sense for you org |
Description | A custom event handler that is used to set custom Temporary Fields to be utilized by AfterSave events that take in the GE Object. |
Event Scope | Entity |
Base Event | BeforeSave |
Firing Sequence | Post-Process |
Entity | Persons |
Script | see below |
' Exception Publish Only for Testing
Aptify.Framework.ExceptionManagement.ExceptionManager.Publish(
New System.Exception(
String.Format("Persons Before Save - Set Temp Fields_sa - IsDirty: {0} - Value: {1} - CBool: {2}", geRecord.Fields.Item("BusinessBadAddress").GetOldValue <> geRecord.GetValue("BusinessBadAddress"), geRecord.GetValue("BusinessBadAddress"), CBool(geRecord.GetValue("BusinessBadAddress")))
)
)
' Clear Temp Field
' We want to make sure we don't possibly start an endless loop as remember AfterSave events might
' update values and re-save the GE
geRecord.Fields.Remove("_xBadAddress_sa")
Try
' Business Bad Address has changed and is currently set to True
If geRecord.Fields.Item("BusinessBadAddress").GetOldValue <> geRecord.GetValue("BusinessBadAddress") AndAlso CBool(geRecord.GetValue("BusinessBadAddress")) Then
' Set Temp Field
geRecord.SetAddValue("_xBadAddress_sa", True)
End If
Catch ex As System.Exception
' Publish Exception
Aptify.Framework.ExceptionManagement.ExceptionManager.Publish(
New System.Exception("Persons Before Save - Set Temp Fields_sa Event Definition Exception", ex)
)
End Try
' Always return true so Aptify can keep functioning
oResult.Success = True
After Save Definition
Field | Value |
---|---|
Name | Persons After Save - Bad Address Flagged_sa |
Category | Use the one that make most sense for you org |
Description | A custom event handler that looks for the custom Bad Address flag. |
Event Scope | Entity |
Base Event | AfterSave |
Firing Sequence | Post-Process |
Entity | Persons |
Script | see below |
' Exception Publish Only for Testing
Aptify.Framework.ExceptionManagement.ExceptionManager.Publish(
New System.Exception(
String.Format("Persons After Save - Bad Address Flagged_sa - IndexOf: {0} - Value: {1} - CBool: {2}", geRecord.Fields.IndexOf("_xBadAddress_sa"), geRecord.GetValue("_xBadAddress_sa"), CBool(geRecord.GetValue("_xBadAddress_sa")))
)
)
Try
' Set the result to if the Temp field exists and Business Bad Address is true
oResult.Success = geRecord.Fields.IndexOf("_xBadAddress_sa") >= 0 AndAlso CBool(geRecord.GetValue("BusinessBadAddress"))
Catch ex As System.Exception
' Publish exception error
Aptify.Framework.ExceptionManagement.ExceptionManager.Publish(
New System.Exception("Persons After Save - Bad Address Flagged_sa Event Definition Exception", ex)
)
' Return False so that nothing executes and Aptify can keep working
oResult.Success = False
End Try
Process Flow that Sends Message
There are a couple different ways to send a message from Aptify, I cheated a little and just did a simple Rule Script to grab the details needed to create a message run.
Field | Value |
---|---|
Name | Send Bad Address Notification w/Update Magic Link_sa |
Description | Sends an email to the person to update their address with a magic link so they don’t need to login. |
Category | Use the one that make most sense for you org |
Event Scope | Entity |
Base Event | AfterSave |
Firing Sequence | Post-Process |
Entity | Persons |
Input Properties
Name | Type | Is Required |
---|---|---|
PersonID | Integer | True |
Result Codes
Code | Description | Is Success |
---|---|---|
Success | Success | True |
Failed | Failed | True |
Rule Script
Field | Value |
---|---|
Name | Send Bad Address Notification w/Update Magic Link - Execution_sa |
Script | see below |
' Set default result code
Dim sResult As String = "SUCCESS"
Try
' Exception Publish Only for Testing
Aptify.Framework.ExceptionManagement.ExceptionManager.Publish(
New System.Exception(
String.Format("Send Bad Address Notification w/Update Magic Link - To: {0}", oProperties.GetProperty("PersonID"))
)
)
' Get Necessary Details
Dim dt As System.Data.DataTable
Dim sqlParams() As System.Data.SqlClient.SqlParameter = {
oDataAction.GetDataParameter("@PersonID", System.Data.SqlDbType.Int, oProperties.GetProperty("PersonID"))
}
With New System.Text.StringBuilder
.Append("SELECT mt.ID [TemplateID], mt.Name, mt.DefaultMessageSystemID [SystemID], mt.DefaultMessageSourceID [SourceID], mt.Subject, mt.HTMLBody, mt.ToType, mt.ToValue, p.ID [PersonID]")
.Append("FROM vwMessageTemplates mt ")
.Append("INNER JOIN Person p ON p.ID = @PersonID ")
.Append("WHERE mt.Name = 'Bad Address w/Update Magic Link_sa'")
dt = oDataAction.GetDataTableParametrized(.ToString, System.Data.CommandType.Text, sqlParams)
End With
If dt Is Nothing OrElse dt.Rows.Count <> 1 Then
Throw New System.Exception(String.Format("Unable to retrieve Bad Address w/Update Magic Link_sa Message Template Data for person id: {0}", oProperties.GetProperty("PersonID")))
End If
Dim dr As System.Data.DataRow = dt.Rows(0)
' Create Message Run
Dim oApp As New Aptify.Framework.Application.AptifyApplication(oDataAction.UserCredentials)
Dim oGE As Aptify.Framework.BusinessLogic.GenericEntity.AptifyGenericEntityBase
oGE = oApp.GetEntityObject("Message Runs", -1)
With oGE
' This is the most minimal needed info the Message Run GE needs to send a message
.SetValue("MessageSystemID", dr("SystemID"))
.SetValue("MessageSourceID", dr("SourceID"))
.SetValue("MessageTemplateID", dr("TemplateID"))
.SetValue("ToType", dr("ToType"))
.SetValue("ToValue", dr("ToValue"))
.SetValue("SourceType", "ID String")
.SetValue("IDString", dr("PersonID"))
.SetValue("ApprovalStatus", "Approved")
If Not .Save(False) Then
Throw New System.Exception(.LastError)
End If
End With
Catch ex As System.Exception
Aptify.Framework.ExceptionManagement.ExceptionManager.Publish(ex)
sResult = "FAILED"
End Try
oResultCode.Value = sResult
Action Map
Code | Is Success | Action | Process Flow Result Code |
---|---|---|---|
SUCCESS | True | End Process | Success |
FAILED | True | End Process | Failed |
Shameless Upsell
If your interested in doing more transactional email based on field field changes easily without needing the scripting of Event Definitions, Event Handlers and Process Flow, I highly encourage you to reach our to PerByte, Inc and ask them about their Advanced Messaging System (Message Triggers).
Yes, this is a shameless plug/upsell for PerByte. I was part of the team for 5yr, and the Message Triggers are awesome!
Aptify Clients
Continue the conversation on the Community Forums.