Skip to content

In App Message

The MovableInk SDK supports showing HTML based MI Content as Interactive In App Messages.

Implementation

MIClient.showInAppMessage(with: "https://mi.example.com/p/rp/abcd12345.html") { buttonID in 
  // User interacted with a link that has a buttonID
}
MIClient.showInAppBrowser(
    activity,
    "https://mi.example.com/p/rp/abcd12345.html",
    listener = object : MovableInAppClient.OnUrlLoadingListener {
        override fun onButtonClicked(buttonID: String) {
            // User interacted with a link that has a buttonID
        }
    },
)
RNMovableInk.showInAppMessage(
  "https://mi.example.com/p/rp/abcd12345.html", 
  (buttonId) => {
    // User interacted with a link that has a buttonID
  }
);
// Create stream subscription
StreamSubscription inAppMessageStreamSubscription;

inAppMessageStreamSubscription = _movableInkPlugin.subscribeToInAppMessageInteraction((String buttonId) {
  // User interacted with a button that contains a buttonId in the in app message
});

// Cancel stream subscription
inAppMessageStreamSubscription.cancel();

_movableInkPlugin.showInAppMessage("https://mi.example.com/p/rp/abcd12345.html");
MovableInk.showInAppMessage(
  "https://mi.example.com/p/rp/abcd12345.html",
  (buttonId) => {
    // User interacted with a link that has a buttonID
  }
);

Note the link ends with .html. When exporting your image links from Studio, make sure to change the suffix (png -> html) when using it as an In App Message in the SDK.

Customization

In Studio, when setting up buttons for your In App Messages, there are a few options that you can use on your clickthrough links to customize the In App Message experience.

Deeplinking

If you want a link from within an In App Message to deeplink into some content on your app, you should have an in app scheme setup and use that scheme for the link. your-scheme://your_path

For example, for our app, we setup a URL Scheme of inkentertainment. This is setup within your Info.plist under the URL Types section.

In our app, we've setup links that match /media/:id to open our Media Details page. To have the button open this page in our app, we'll use the URL inkentertainment://media/1. This will instruct the system to notify our app to handle this URL.

If you need a link within your In App Message to override the current frame where the In App Message is showing, you can use the InFrame query parameter on your clickthrough url.

https://example.com?inFrame

Tapping on a link with the inFrame parameter will instruct the SDK to open that link in place.

If you need link to close the In App Message UI and show an In App Browser, you can use the InAppBrowser query param.

https://example.com?inAppBrowser

Tapping on a link with the inAppBrowser parameter will instruct the SDK to open that link within an In App Browser.

If you wish to use your own In App Browser UI instead of the one the SDK provides, you can TODO.

Do not use both inFrame and inAppBrowser on your links.

By default, any links that are not app scheme links or contain the InFrame or InAppBrowser parameters that the user interacts with in an In App Message will close the In App Message UI and open the link in the users Default Browser.

Will open in default users browser externally
https://example.com
http://google.com

Will not open externally
myapp://terms_of_service
https://example.com?InFrame
https://google.com?InAppBrowser
https://google.com?InAppBrowser&buttonID=0

buttonID

You can use the buttonID parameter to be notified via the handler when a user interacts with a link that contains a buttonID value. This is useful if you need to log an event to any Analytics tools you may use.

inkentertainment://media/7?buttonID=0

analytics_identifier

You can use the analytics_identifier parameter to be notified via the handler when a user interacts with a link that contains a analytics_identifier value. This is useful if you need to log an event to any Analytics tools you may use.

inkentertainment://media/7?analytics_identifier=name

You can also append some options to the MovableInk Link that you use for your In App Message.

Dismiss

In Designer, you should setup a button with the clickthrough of dismiss://. When the user clicks this button, the In App Message will be dismissed. If you need to know when the user dismisses the In App Message via this button, you should set an analytics_identifier, such as dismiss://?analytics_identifier=dismissed.

Close Button

iOS only

By default, the In App Message will NOT have a close button. If you need to show one, you should add a button in your In App Message directly and use the dismiss:// clickthrough.

If you want to show a native close button, you can append the showCloseButton as a query parameter:

https://mi.example.com/p/rp/abcd12345.html?showCloseButton

If you want to show the close button for all in app messages, you can do so via the showCloseButton parameter when showing the In App Message: MIClient.showInAppMessage(with: link, showCloseButton: true) { buttonID in }

Using MIClient.showInAppMessage(with: link, showCloseButton: true) will force the close button to always be shown regardless of the value of hideCloseButton in the URL.

Braze

There's 2 ways to show an Interactive In App Message in Braze:

An iFrame or via the MovableInk SDK.

Using an iFrame is the easiest way to get started as you wont need to pass the message to the MovableInk SDK. Your CX Team will generate the iframe script for you and you can just copy and paste it into your Braze campaign.

Reach out to your CX Team at MovableInk to get started with this.

In Braze, when creating an In App Message Campaign, select Custom Code as the message type and insert an empty script.

<script>
</script>

BrazeInAppMessageSetup

Switch to the Settings Tab, right side of Compose, and add a new Key Value Pair.

Set the Key to mi_link Set the Value to your MI HTML Link, such as https://mi.example.com/p/rp/abcd12345.html

BrazeInAppMessageSettings

Setup the rest of your campaign as you normally would.

To show this In App Message, we'll need to be notified of an incoming message and discard it, passing along the link to the SDK to take over.

If you're already managing your Braze instance on your AppDelegate, make your AppDelegate conform to BrazeInAppMessageUIDelegate and implement the inAppMessage(_:.displayChoiceForMessage:) method.

inAppMessage(_:.displayChoiceForMessage:) will be called by the Braze SDK when it decides it wants to show an In App Message. Here, we can check if the message is a MovableInk driven message via the mi_link key and forward it's value to the MovableInk SDK to take over.

import BrazeKit
import BrazeUI
import MovableInk

class AppDelegate: NSObject, UIApplicationDelegate, BrazeInAppMessageUIDelegate {
  private var braze: Braze?

  func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
    let configuration = Braze.Configuration(
      apiKey: "BRAZE_API_KEY",
      endpoint: "BRAZE_ENDPOINT"
    )
    self.braze = Braze(configuration: configuration)

    // Setup the presenter and it's delegate
    let presenter = BrazeInAppMessageUI() 
    presenter.delegate = self
    self.braze?.inAppMessagePresenter = presenter

    return true
  }

  func inAppMessage(
    _ ui: BrazeInAppMessageUI,
    displayChoiceForMessage message: Braze.InAppMessage
  ) -> BrazeInAppMessageUI.DisplayChoice {
    // Check if the incoming message contains an mi_link
    if let miLink = message.extras["mi_link"] {
      // If it does, we'll let the MovableInk SDK handle this.
      // Log the impression to Braze, ask MIClient to show the message, and return .discard to 
      // notify the Braze SDK that we don't want it to show anything.
      logImpression(message: message)

      MIClient.showInAppMessage(with: miLink) { [weak self, message] buttonID in
        // This closure will be called if a user interacts with a link that contains the buttonID parameter.
        // We can use that to log the click back to Braze.

        self?.logClick(message: message, buttonID: buttonID)
      }

      return .discard
    }

    return .now
  }

  private func logImpression(message: Braze.InAppMessage) {
    guard let braze else { return }
    message.logImpression(using: braze)
  }

  private func logClick(message: Braze.InAppMessage, buttonID: String?) {
    guard let braze else { return }
    message.logClick(buttonId: buttonID, using: braze)
  }
}

In your code-base you will need to implement an in-app message manager listener - a class that implements IInAppMessageManagerListener

More information on implementing a custom listener can be found here

private const val KEY_MI_LINK = "mi_link"

class BrazeListener(activity: Activity) : IInAppMessageManagerListener {
    private val activity = activity

    override fun beforeInAppMessageDisplayed(inAppMessage: IInAppMessage): InAppMessageOperation {
        if (inAppMessage.extras.containsKey(KEY_MI_LINK)) {
            // Let the MovableInk SDK handle this.
            // Log the impression to Braze, ask MIClient to show the message, and return .discard to
            // notify the Braze SDK that we don't want it to show anything.
            val movableLink = inAppMessage.extras[KEY_MI_LINK] as String
            MIClient.showInAppBrowser(
                activity,
                movableLink,
                listener = object : MovableInAppClient.OnUrlLoadingListener {
                    override fun onButtonClicked(value: String) {
                        // log button clicks to braze
                        inAppMessage.logButtonClick(value)
                    }
                },
            )
        }
        return InAppMessageOperation.DISCARD
    }
}

Braze doesn't expose the same APIs to ReactNative as their Native implementation to dynamically switch when to show an In App Message or not via the Braze provided UI.

You should follow Braze's documentation for advanced customization alongside our here in the iOS and Android tabs.

Braze doesn't expose the same APIs to Flutter as their Native implementation to dynamically switch when to show an In App Message or not via the Braze provided UI.

You should follow Braze's documentation for advanced customization alongside our here in the iOS and Android tabs.

Braze doesn't expose the same APIs to Cordova as their Native implementation to dynamically switch when to show an In App Message or not via the Braze provided UI.

You should follow Braze's documentation for advanced customization alongside our here in the iOS and Android tabs.

Logging impressions and button clicks back to Braze may take a minute or two to show up on their side.

Salesforce Marketing Cloud

Salesforce Marketing Cloud does not support HTML based In App Messages, so you'll need to use the MovableInk SDK to show the message. You can still use SFMC's Journey Builder to decide when to show the message.

In SFMC Content Builder, create a new Mobile App/In-App Message. In the Content section, set the Title to mi_link:YOUR_LINK, for example, mi_link:https://www.example.com/p/rp/12345.html.

In Journey Builder, create a new Journey to show the In App Message.

Once your in app message is created, you can ask the MovableInk SDK to show it instead of the SFMC SDK. The SFMC SDK has an InAppMessageEventDelegate that you need to conform to in order to receive the sfmc_shouldShow(inAppMessage:) callback.

import MarketingCloudSDK
import MovableInk

class AppDelegate: NSObject, UIApplicationDelegate, InAppMessageEventDelegate {
  ...

  func sfmc_shouldShow(inAppMessage message: [AnyHashable : Any]) -> Bool {
    // Check if the incoming message contains an mi_link
    guard let title = message["title"] as? [AnyHashable: Any],
          let text = title["text"] as? String,
          text.hasPrefix("mi_link:")
    else {
      return true
    }

    let miLink = String(text.dropFirst("mi_link:".count))

    SFMCSdk.requestPushSdk { mp in
      let messageID = mp.messageId(forMessage: message)

      MIClient.showInAppMessage(with: miLink) { [weak self] buttonID in
        self?.logClick(miLink: miLink, buttonID: buttonID, messageID: messageID)
      }
    }

    return false
  }
}
SFMCSdk.requestSdk { sdk ->
  sdk.mp {
    it.inAppMessageManager.setInAppMessageListener(object : InAppMessageManager.EventListener {

      override fun shouldShowMessage(message: InAppMessage): Boolean {
        if (inAppMessage["title"] is Map<*, *>) {
          val title = inAppMessage["title"] as Map<*, *>
          val text = title["text"] as String

          if (text.startsWith("mi_link:")) {
            val miLink = text.drop("mi_link:".length)

            MIClient.showInAppBrowser(
              activity,
              miLink,
              listener = object : MovableInAppClient.OnUrlLoadingListener {
                override fun onButtonClicked(buttonID: String) {
                  // User interacted with a link that has a buttonID
                }
              },
            )

            return false
          }
        }

        return true
      }

      override fun didShowMessage(message: InAppMessage) = Unit
      override fun didCloseMessage(message: InAppMessage) = Unit
    })
  }
}

Don't see your Marketing Cloud Provider?

If we don't support your Marketing Cloud Provider, reach out to your CX team at MovableInk to let us know so that we can see how we can support it.

Our team will do some research to see if we can support your provider. We typically try and see if there's any way to to use the provider's SDK to forward the message to the MovableInk SDK in some fashion.