Skip to main content
Skip table of contents

Using Outbound Delivery channels and Mustache templates

Introduction

RPI’s Outbound Delivery channel allows you to build your own custom channel execution capabilities. This feature provides a mechanism for RPI to notify an external system that a campaign audience is complete and awaiting a custom channel execution. Once the external system has completed its channel execution activities, it can provide delivery states back to RPI for reporting and downstream decisioning. The Outbound Delivery channel is similar to an export channel with additional functionality to support the processing of custom delivery states and generation of Mustache templates for content rendering.

The sample code in this topic integrates with the mGage SMS Gateway and will render and execute messages individually for each recipient in the channel execution.

image-20251006-154137.png

High-level process steps

  1. Interaction executed

  2. Outbound delivery executed

    1. Data file extracted

    2. Content file extracted

    3. External system notified

  3. External system merges content and data and sends to channel vendor

  4. Channel vendor sends messages and provides back disposition data

  5. External process provides disposition data back to RPI and links to the offer

What is Mustache?

Mustache is a logic-less templating language under MIT License. It was originally developed for Ruby and has open-source ports for over 40 different programming languages. You can learn more about Mustache and logic-less templates at http://mustache.github.io/mustache.5.html. The Wikipedia entry for Mustache (template system) describes it as “a ‘logic-less’ system because it lacks any explicit control flow statements, like if and else conditionals or for loops; however, both looping and conditional evaluation can be achieved using section tags processing lists and lambdas.”

The RPI implementation of Mustache templates does not utilize lambdas. Instead, RPI will evaluate any Dynamic Assets and export the final rendered content for each recipient record.

Configuring an Outbound Delivery channel

The Outbound Delivery channel configuration shares many common configuration settings with other RPI outbound channels. Note here that we are specifying an HTTP Post/JSON Service URL. This endpoint will be covered in more detail later, but this will serve as a trigger to notify an external system that RPI has an audience awaiting custom channel execution.

image-20251006-164050.png
image-20251006-164121.png

The Content Settings section of the channel configuration allows you to specify whether or not to generate Mustache templates during channel execution. When the Use content setting is selected, RPI will generate a Mustache template as part of the channel execution process. If enabled, you will need to select an External Content Provider to use as an external folder for digital asset hosting and a Recipient key for deduplication. Optionally, you can override the Mustache start and end tags for custom Mustache implementations.

image-20251006-164539.png

The Extract Settings section of the channel configuration allows you to specify whether or not an export file is generated as part of channel execution. When Generate export file is selected, RPI will generate an export file based on the selected Export template. The resulting export file will include the attributes defined in the export template as well as any additional attributes defined in an Outbound Delivery Offer, any associated Digital Assets (personalization fields, dynamic content, etc.) and the address key (if specified) during channel execution. RPI will also include ChannelExecutionID and RPContactID for each recipient in the extract, by default, for each channel execution.

image-20251006-164808.png

In this example, the export template only has PID defined in its list of attributes. The export template is also configured to use JSON as its export format. The JSON export option has two output methods:

  • Object list method: exports each recipient records as a JSON object delimited with a new line. Using this option, the resultant file will not pass JSON validation. This option is convenient for allowing the consuming system to read the file line-by-line to process recipients individually.

  • Array method: outputs the recipients as a large JSON array of objects. Using this method, the entire file can be consumed through a JSON parser and deserialized into an array of recipient objects.

image-20251006-165049.png

The Extract Settings section of the channel configuration allows you to specify the method with which channel states are brought back into RPI as well as the Delivery states that are expected to come back. You can configure the channel to use Redpoint Data Management (RPDM) to bring back state results by selecting the Import state results via DM option. When selected, you will need to specify an RPDM project repository path and a State result folder path. When configured to use RPDM, the channel sync task will first check for the existence of a new file in the State result folder path and invoke the configured RPDM project to process the delivery states if a new file is detected. This example will use the State import table option. This option allows users to specify a database table to monitor for new delivery states. When configured to use a State import table, the channel sync task will query the specified database table and process any new delivery states. The default delivery states for Outbound Delivery channels include Total Count, Duplicates, and Targeted. In this example, Delivered and Failed are defined as additional Delivery states. When using the State import table option, you can optionally specify a Web service URL to act as a callback service to allow for the collection of delivery states in real time. An implementation of this service ships with RPI and is located in deployment files under "Plugins Services\OutboundDeliveryCallbackService" and is attached below for your convenience.

image-20251006-165449.png

Outbound Delivery offer

The Outbound Delivery offer type allows you to select a Digital Asset to use as content for a channel execution. It also allows you to specify additional export attributes to be included in the channel execution export file. In this example, no additional attributes are specified as RPI will automatically include any attributes associated with the "mGage Content" Digital Asset.

image-20251006-172503.png

The "mGage Content" object is a Text Asset that includes the FIRST_NAME attribute and a Dynamic Asset named "mGage Dynamic Content".

image-20251006-172413.png

The "mGage Dynamic Content" object is a Dynamic Asset that renders a promotion based on the value of the INCOME attribute.

image-20251006-172307.png

Channel Execution outputs

Upon executing an Interaction using the configured Outbound Delivery channel, a number of files will be created in the specified export path. These files are:

  • RPI_Export_[Channel Execution ID].txt: standard RPI export file.

  • RPI_Export_[Channel Execution ID].txt_summary.txt: standard RPI export summary file. This file can be suppressed by selecting the Suppress summary file option in the channel configuration.

  • RPI_Export_[Channel Execution ID]_Content.txt: the Mustache content template file generated for this channel execution.

  • RPI_Export_[Channel Execution ID]_sample.txt: standard RPI export sample file. This file can be suppressed by selecting the Suppress sample file option in the channel configuration.

  • RPI_Export_[Channel Execution ID]_Source.txt: the Mustache source/data file generated for this channel execution.

Content with personalization and a single-level smart asset

This example illustrates the outcomes of a Redpoint Interaction workflow that utilizes a single text smart asset within the outbound delivery offer. By leveraging this smart asset, the workflow effectively enhances the communication process, ensuring that the message delivered is both clear and engaging for the recipient.

The integration of a text smart asset allows for a streamlined approach to content delivery, enabling personalized messaging that resonates with the audience. This not only improves the overall user experience but also increases the likelihood of achieving desired engagement metrics.

RPI_Export_2015_Source.txt

The contents of the RPI_Export_2015_Source.txt file for this channel execution are:

JS
{"PRIMARY_PHONE":"447583253424","ChannelExecutionID":2015,"RPContactID":550023,"PID":50001,"FIRST_NAME":"Mike","INCOME":"$25,000-$34,999","rootAssetContent_1":"20% off and free shipping"}
{"PRIMARY_PHONE":"447920071551","ChannelExecutionID":2015,"RPContactID":550024,"PID":50002,"FIRST_NAME":"Mike","INCOME":"$100,000-$124,999","rootAssetContent_1":"10% off"}
{"PRIMARY_PHONE":"17817527758","ChannelExecutionID":2015,"RPContactID":550025,"PID":50003,"FIRST_NAME":"Chris","INCOME":"$200,000-$249,999","rootAssetContent_1":"free shipping"}
{"PRIMARY_PHONE":"306955472892 ","ChannelExecutionID":2015,"RPContactID":550026,"PID":50004,"FIRST_NAME":"Test1","INCOME":"$50,000-$99,999","rootAssetContent_1":"20% off and free shipping"}
{"PRIMARY_PHONE":"31650489921","ChannelExecutionID":2015,"RPContactID":550027,"PID":50005,"FIRST_NAME":"Test2","INCOME":"<1,000","rootAssetContent_1":"20% off and free shipping"}
{"PRIMARY_PHONE":"16178031195","ChannelExecutionID":2015,"RPContactID":550028,"PID":50006,"FIRST_NAME":"Michael","INCOME":"$35,000-$49,999","rootAssetContent_1":"20% off and free shipping"}

The following fields are included in the source file:

  • PRIMARY_PHONE: the address key defined for the Outbound Delivery channel configuration.

  • ChannelExecutionID: the ID of the channel execution in RPI. This field is automatically added by RPI.

  • RPContactID: the offer history primary key generated by RPI for each channel recipient. This field is automatically added by RPI.

  • PID: the primary key of the Person table defined as an export attribute in the default export template for the Outbound Delivery channel.

  • FIRST_NAME: a personalization attribute defined in the Outbound Delivery offer defined for this interaction.

  • INCOME: the attribute used drive content in the Dynamic Asset associated with the offer.

  • rootAssetContent_1: an RPI generated field with the output value of the Dynamic Asset for each channel recipient.

RPI_Export_2015_Content.txt

The contents of the RPI_Export_2015_Content.txt file for this channel execution are:

TEXT
{{FIRST_NAME}}, you're a great customer! Enjoy {{rootAssetContent_1}} on your next order.

When this Mustache template is rendered for a record in the source file, the FIRST_NAME and rootAssetContent_1 tags will be replaced by the FIRST_NAME and rootAssetContent_1 fields from the source JSON, respectively.

Content with personalization and nested smart assets

This example illustrates the outcomes of a Redpoint Interaction workflow that utilizes multiple nested text smart assets within the outbound delivery offer. The integration of these smart assets allows for a more dynamic and personalized communication strategy, enhancing the overall effectiveness of the outbound delivery process.

By leveraging nested text smart assets, the workflow can adapt to various customer segments and preferences, ensuring that each recipient receives tailored content that resonates with their specific needs and interests. This approach not only improves engagement rates but also fosters a stronger connection between the brand and its audience.

RPI_Export_5192_Source.txt

The contents of the RPI_Export_5192_Source.txt file for this channel execution are:

JS
{"CustomerKey":1,"ChannelExecutionID":5192,"RPContactID":52760742,"EmailAddress":"greg-1c4496+user1@inbox.mailtrap.io","FirstName":"Angela","YearlyIncome":138864.00,"HouseOwnerFlag":"1","Gender":"F","TotalChildren":4,"NumberCarsOwned":1,"rootAssetContent_1":"Your income is between $130,000 and $170,000","rootAssetContent_2":"3-4 Kids","rootAssetContent_3":"1-2 Cars"},
{"CustomerKey":2,"ChannelExecutionID":5192,"RPContactID":52760743,"EmailAddress":"greg-1c4496+user1@inbox.mailtrap.io","FirstName":"Melissa","YearlyIncome":147050.00,"HouseOwnerFlag":"0","Gender":"F","TotalChildren":4,"NumberCarsOwned":3,"rootAssetContent_1":"Your income is between $130,000 and $170,000","rootAssetContent_2":"You are not the homeowner","rootAssetContent_3":"3-4 Cars"},
{"CustomerKey":3,"ChannelExecutionID":5192,"RPContactID":52760744,"EmailAddress":"greg-1c4496+user1@inbox.mailtrap.io","FirstName":"Jessica","YearlyIncome":81049.00,"HouseOwnerFlag":"1","Gender":"F","TotalChildren":4,"NumberCarsOwned":3,"rootAssetContent_1":"Your income is between $10,000 and $90,000","rootAssetContent_2":"3-4 Kids","rootAssetContent_3":"3-4 Cars"},
{"CustomerKey":4,"ChannelExecutionID":5192,"RPContactID":52760745,"EmailAddress":"greg-1c4496+user1@inbox.mailtrap.io","FirstName":"Jose","YearlyIncome":93654.00,"HouseOwnerFlag":"0","Gender":"M","TotalChildren":0,"NumberCarsOwned":3,"rootAssetContent_1":"Your income is between $10,000 and $90,000","rootAssetContent_2":"You are not the homeowner","rootAssetContent_3":"3-4 Cars"},
{"CustomerKey":5,"ChannelExecutionID":5192,"RPContactID":52760746,"EmailAddress":"greg-1c4496+user1@inbox.mailtrap.io","FirstName":"Parker","YearlyIncome":36590.00,"HouseOwnerFlag":"1","Gender":"M","TotalChildren":2,"NumberCarsOwned":3,"rootAssetContent_1":"Your income is between $10,000 and $90,000","rootAssetContent_2":"Male","rootAssetContent_3":"3-4 Cars"},
{"CustomerKey":6,"ChannelExecutionID":5192,"RPContactID":52760747,"EmailAddress":"greg-1c4496+user1@inbox.mailtrap.io","FirstName":"Samantha","YearlyIncome":115215.00,"HouseOwnerFlag":"0","Gender":"F","TotalChildren":0,"NumberCarsOwned":1,"rootAssetContent_1":"Your income is between $100,000 and $130,000","rootAssetContent_2":"You are not the homeowner","rootAssetContent_3":"1-2 Cars"}

The following fields are included in the source file:

  • CustomerKey: the address key defined for the Outbound Delivery channel configuration.

  • ChannelExecutionID: the ID of the channel execution in RPI. This field is automatically added by RPI.

  • RPContactID: the offer history primary key generated by RPI for each channel recipient. This field is automatically added by RPI.

  • EmailAddress: the primary key of the Person table defined as an export attribute in the default export template for the Outbound Delivery channel.

  • FirstName: a personalization attribute defined in the Outbound Delivery offer defined for this interaction.

  • YearlyIncome: the attribute used to drive content in the Dynamic Asset associated with the offer.

  • HouseOwnerFlag: the attribute used to drive content in the Dynamic Asset associated with the offer.

  • Gender: the attribute used to drive content in the Dynamic Asset associated with the offer.

  • TotalChildren: the attribute used to drive content in the Dynamic Asset associated with the offer.

  • NumberCarsOwned: the attribute used to drive content in the Dynamic Asset associated with the offer.

  • rootAssetContent_1: an RPI generated field with the output value of the Dynamic Asset for each channel recipient.

  • rootAssetContent_2: an RPI generated field with the output value of the Dynamic Asset for each channel recipient.

  • rootAssetContent_3: an RPI generated field with the output value of the Dynamic Asset for each channel recipient.

When utilizing nested smart assets within an outbound delivery, it is important to understand that the resulting file will consolidate and flatten the various potential outcomes into a limited number of content fields. This means that instead of having multiple variations or options for each content field, the system will streamline the data into a more manageable format.

This flattening process can significantly enhance the efficiency of content delivery, as it reduces complexity and ensures that the final output is straightforward and easy to navigate. However, it is crucial to carefully plan the structure of your nested smart assets to ensure that all necessary information is captured effectively within the designated content fields.

By doing so, you can maximize the utility of the smart assets while maintaining clarity and coherence in your outbound delivery. This approach not only facilitates a smoother workflow but also helps in delivering a more polished and professional final product.

RPI_Export_5192_Content.txt

The contents of the RPI_Export_5192_Content.txt file for this channel execution are:

TEXT
{{FirstName}}, {{rootAssetContent_1}} and you have {{rootAssetContent_2}} and {{rootAssetContent_3}}.

When this Mustache template is rendered for a record in the source file, the FIRST_NAME and rootAssetContent_1, rootAssetContent_2, and rootAssetContent_3 tags will be replaced by the FIRST_NAME and rootAssetContent_1, rootAssetContent_2, and rootAssetContent_3 fields from the source JSON, respectively.

Outbound Delivery implementation

The following projects represent the implementation of an Outbound Delivery channel for mGage. The solution contains the following projects:

  • mGageCallback

  • mGageLib

  • mGageServices

  • mGageProcessor

  • mGageStateProcessor

mGageCallback

The mGageCallback project is a .NET Web Application project that implements a single ASP.NET Handler for processing callbacks from mGage to enable real-time processing of SMS statuses. The callback.ashx handler converts the Request.QueryString NameValueCollection to a Dictionary for JSON serialization then writes this JSON to the database for downstream processing of delivery states. mGage requires a response of 000 to confirm receipt of the callback on their end. This callback handler could have been implemented as a WebAPI project, but the handler provided greater flexibility, removed the rigidity of WebAPI's request contracts, and accomplished the same end.

callback.ashx
C#
using System.Collections.Generic;
using System.Linq;
using System.Web;

using mGageLib.DataAccess;

namespace mGageCallback
{
    /// <summary>
    /// Summary description for callback 
    /// </summary>
    public class callback : IHttpHandler
    {

        public void ProcessRequest(HttpContext context)
        {
            // convert query string nvc to dictionary
            Dictionary<string, string> callbackParams = context.Request.QueryString.AllKeys.ToDictionary(k => k, k => context.Request.QueryString[k]);

            CallbackQueueDA.SaveCallbackRequest(callbackParams);

            context.Response.ContentType = "text/plain";
            context.Response.Write("000");
        }

        public bool IsReusable
        {
            get
            {
                return false;
            }
        }
    }
}

A sample request to the callback handler looks like:

http://test.redpointglobal.com/mgage/callback.ashx?IFVERSION=220010&DESCRIPTION=test&STATUSTYPE=20&STATUS=0&MESSAGEID=21290&GUID=7885C705-26E1-4F71-8B8E-F5A1C8DD72E3&STATUSTIME=20050512145211%20&DESTADDRESS=00447385940836&CONNECTION=MIG01OU

mGageLib

The mGageLib project is Class Library project used in this project to facilitate code reuse across the projects in the solution. This class library contains common models, services, and data access code used across the solution.

mGageServices

Once RPI has completed its channel execution, the Outbound Delivery channel will initiate a call to the post-execution service defined in the channel configuration. The mGageServices project is a WebAPI project that implements a single endpoint for handling RPI post-execution requests. The endpoint implementation simply takes the JSON payload that RPI sends and writes it to a Jobs table in the project database along with the channel execution ID. The PostExectionController implementation follows:

PostExecutionController.cs
C#
using System;
using System.Data;
using System.Data.SqlClient;
using System.Web.Http;

using Newtonsoft.Json;

using mGageLib.Models;

namespace mGageServices.Controllers
{
    public class PostExecutionController : ApiController
    {
        // POST: api/PostExecution
        public string Post([FromBody]PostExecutionRequestModel model)
        {
            
            using (SqlConnection conn = new SqlConnection())
            {
                conn.ConnectionString = ConfigurationManager.ConnectionStrings["mGageServices"].ConnectionString;

                string json = JsonConvert.SerializeObject(model);

                using (SqlCommand cmd = new SqlCommand("InsertJob", conn))
                {
                    cmd.CommandType = CommandType.StoredProcedure;
                    cmd.Parameters.Add("@CEID", SqlDbType.BigInt);
                    cmd.Parameters.Add("@MESSAGE", SqlDbType.VarChar);
                    cmd.Parameters["@CEID"].Value = model.channelExecutionID;
                    cmd.Parameters["@MESSAGE"].Value = json;
                    conn.Open();
                    cmd.ExecuteNonQuery();
                }
            }

            return model.channelExecutionID.ToString();
        }

    }
}

The JSON that RPI includes in requests to the post-execution service looks like:

Post-execution request body
JS
{
  "interactionID": "01f8451c-e150-470d-a93d-96db2f2053d5",
  "workflowAssociationID": "9fc56f39-c4ad-4f57-9b31-d19d255f5074",
  "workflowAssociationInstanceID": "2053",
  "dataflowID": "43",
  "offerName": "mGage Offer",
  "channelName": "mGage",
  "activityName": "mGage Offer",
  "channelOfferName": "mGage",
  "channelExecutionID": "2015",
  "clientID": "24c0ebf0-a286-4d3d-88d2-c64f96979727",
  "isTest": false
}

mGageProcessor

The mGageProcessor project is a Console Application that queries the database for jobs created from post-execution service calls and performs channel execution tasks. The implementation of this console app follows:

Program.cs
C#
using System;
using System.Collections.Generic;
using System.Configuration;
using System.IO;

using Newtonsoft.Json.Linq;

using mGageLib.DataAccess;
using mGageLib.Models;
using mGageLib.Services;

namespace mGageProcessor
{
    class Program
    {
        private static string rpiExportPath = ConfigurationManager.AppSettings["rpiExportRoot"];
        private static string mustacheContentFile = "RPI_Export_{0}_Content.txt";
        private static string mustacheSourceFile = "RPI_Export_{0}_Source.txt";

        static void Main(string[] args)
        {
            List<PostExecutionRequestModel> models = JobsDA.GetPendingJobs();

            foreach(PostExecutionRequestModel model in models)
            {
                Console.WriteLine("Processing job for channel execution id: {0}", model.channelExecutionID);
                
                if (model.isTest)
                {
                    Console.WriteLine("SKIPPING... Job created in test mode");
                    JobsDA.UpdateJobStatus(model, JobStatus.Skipped);
                    continue;
                }

                //TODO: check status of channel execution before proceeding? Rolled Back?

                string mustacheContentPath = string.Format("{0}\\{1}\\{2}", rpiExportPath, model.channelExecutionID, string.Format(mustacheContentFile, model.channelExecutionID));
                Console.WriteLine("Reading Mustache content template from {0}", mustacheContentPath);

                string mustacheContent = File.ReadAllText(mustacheContentPath).Trim();
                Console.WriteLine("Mustache content template:\n{0}\n", mustacheContent);

                string mustacheSourcePath = string.Format("{0}\\{1}\\{2}", rpiExportPath, model.channelExecutionID, string.Format(mustacheSourceFile, model.channelExecutionID));
                Console.WriteLine("Reading Mustache source file from {0}", mustacheSourcePath);

                using (StreamReader sr = new StreamReader(mustacheSourcePath))
                {
                    string line;

                    while ((line = sr.ReadLine()) != null)
                    {
                        Console.WriteLine("Processing content for record: {0}", line);

                        //MustacheSource source = JsonConvert.DeserializeObject<MustacheSource>(line);
                        JObject source = null;
                        try
                        {
                            source = JObject.Parse(line);
                        }
                        catch
                        {
                            Console.WriteLine("Failed to parse JObject from {0}", line);
                            continue;
                        }

                        // these are required fields for this implementation -- your specific implementation may require different fields
                        // assumes RPContactID and ChannelExecutionID will always be there 
                        JToken PID = null;
                        JToken PrimaryPhone = null;

                        if (!source.TryGetValue("PID", out PID) || !source.TryGetValue("PRIMARY_PHONE", out PrimaryPhone))
                        {
                            Console.WriteLine("PID and/or PRIMARY_PHONE not found in source object... SKIPPING");
                            continue;
                        }

                        string renderedContent = MustacheUtil.Render(mustacheContent, source);

                        //System.Diagnostics.Debug.WriteLine
                        Console.WriteLine("Rendered content for PID {0}:\n{1}", source["PID"].Value<string>(), renderedContent);

                        Console.WriteLine("Sending message...");

                        string response = mGageUtil.SendSMS(source["PRIMARY_PHONE"].Value<string>(), renderedContent, source["RPContactID"].Value<string>());
                        Console.WriteLine("Send response: {0}", response);

                        MessageHistoryDA.SaveMessageHistory(source, response);
                        Console.WriteLine("Message history saved");
                    }
                }

                // set job to complete
                JobsDA.UpdateJobStatus(model, JobStatus.Processed);
            }

        }
    }
}

This application performs the following tasks:

  • Queries the database for pending jobs (line 22)

  • Iterates each job (lines 24-94)

    • Checks if the Interaction that created the job was run in test mode; this implementation skips all jobs created in test mode (lines 28-33)

    • Retrieves the Mustache template for the current job (lines 37-41)

    • Retrieves the source file for the current job (lines 43-44)

    • Loops through each recipient record in the source file (lines 50-89)

      • Parses the recipient record into a JObject (lines 55-64)

      • Validates the JObject for required fields (lines 68-75)

      • Renders the Mustache template for the recipient record (line 77)

      • Delivers the message through the mGage SMS gateway (line 84)

      • Saves the mGage send response to a message history table (line 87)

    • Updates the job status to complete (line 93)

Below is the output from a test execution of the mGageProcessor console application.

mGageStateProcessor

The mGageStateProcessor project is a Console Application that processes the mGage callback messages collected through the mGageCallback handler and writes delivery state data into the mGage_OutboundDelivery table. Once delivery state information is written to this table, the Task Manager sync task for mGage channel will process the data on its set schedule. The implementation of this console app follows:

Program.cs
C#
using System;
using System.Collections.Generic;


using mGageLib.DataAccess;
using mGageLib.Models;

namespace mGageStateProcessor
{
    class Program
    {
        static void Main(string[] args)
        {
            List<mGageCallback> callbacks = CallbackQueueDA.GetPendingCallbacks();

            foreach (mGageCallback callback in callbacks)
            {
                string stateName = string.Empty;
                string stateDetails = string.Empty;

                if (callback.StatusCode == 0)
                    stateName = "Delivered";
                else
                    stateName = "Failed";

                RpiChannelState channelState = new RpiChannelState()
                {
                    RPContactId = System.Convert.ToInt32(callback.MessageId),
                    ChannelExecutionId = callback.ChannelExecutionId,
                    StateName = stateName,
                    StateDetails = stateDetails,
                    EventDate = callback.Timestamp
                };

                Console.WriteLine("Saving state for ChannelExecutionId: {0}, RPContactID: {1}, StateName: {2}, StateDetails: {3}, EventDate: {4}", 
                    channelState.ChannelExecutionId, channelState.RPContactId, channelState.StateName, channelState.StateDetails, channelState.EventDate);

                ChannelStateDA.SaveChannelState(channelState);
            }
        }
    }
}

This application performs the following tasks:

  • Queries the database for pending callback messages (line 13)

  • Iterates callback message (lines 15-38)

    • Checks status code of the callback message and sets the delivery state to delivered or failed accordingly (lines 20-23)

    • Writes the delivery state data to the mGage_OutboundDelivery to await processing by the channel sync task (lines 25-37)

Below are the Offer Results for the Interaction from this channel execution.

image-20251009-150027.png

Sample outbound delivery callback service code

The following ZIP file provides an implementation of the outbound delivery callback service referenced above.

OutboundDeliveryCallbackService.7z

Other attachments

mGagePOC-db.sql

JavaScript errors detected

Please note, these errors can depend on your browser setup.

If this problem persists, please contact our support.