Movable Ink
AIRSHIP MAINTAINED INTEGRATION
This integration is maintained by Airship. Please contact Airship for support.Overview and Setup
Movable Ink helps you deliver highly relevant, personalized push notifications, Message Center messages, and in-app automations.
Contact alliances@airship.com to get started with Movable Ink in Airship. Airship will help you set up your integration and create Message Center templates so you can use your Movable Ink campaigns in Airship.
To use Movable Ink with Airship, you must use your audience’s channel_id
or named_user_id
identifiers as Movable Ink merge tags — the mi_u
query parameter in Movable Ink. This helps ensure that each member of your audience receives personalized content.
To take advantage of Movable Ink, you’ll insert blocks of content or Creative Tags from Movable Ink into your Airship messages. A Creative Tag is a snippet of HTML that includes business logic to render personalized, intelligent content in your Airship message.
Use Cases
Movable Ink helps you personalize the look and feel of your message to suit each individual in your audience.
Merge sale-specific information in your messages: Promote product-based messaging in your app after a user views content in a website or an email.
Target your audience contextually: Send time-targeted messages to make sure your audience knows about upcoming events, weather, time-targeted offers, etc.
Offer behavioral recommendations: Let your audience know about product substitutes or events tangential to their current interests to keep them engaged.
Movable Ink Support in Airship
Movable Ink dynamically generates creative content in real-time for your mobile app using Airship. This table lists many of the Movable Ink capabilities available for Airship apps.
If a Movable Ink capability is not listed in this table, then it is fully supported for mobile messaging with Airship.
Movable Ink capability | Rich push notification | In-App Automation/Message Center | Notes | |
---|---|---|---|---|
Creative Optimizer | Display A/B content | ✓ | ||
Optimize | ✓ | Use the Channel ID to optimize. Must deep link so Movable Ink can capture clicks and optimize the content. Cannot optimize based on conversions or revenue. | ||
Targeting Rules | Date | ✓* | ✓* | *Supported but not recommended because push notifications are cached upon receipt and do not refresh. |
Day of Week | ✓* | ✓ | *Supported but not recommended because push notifications are cached upon receipt and do not refresh. | |
Query Parameters | ✓* | ✓** | *If using Airship with Salesforce Marketing Cloud, your identifiers in Airship (`channel_id` or `named_user`) must match Salesforce’s UUID and be passed through their connector. **Can only use the UUID ( mi_u ) parameter. | |
Time of Day | ✓* | ✓ | *Supported but not recommended because push notifications are cached upon receipt and do not refresh | |
Stories/Behavioral Activity | ✓ | Airship’s channel or named user identifiers must be linked to your ESP’s UUID, which Movable Ink uses for Stories user profiles. | ||
Deep linking in the app | ✓* | ✓** | *Must deep link with Branch.io. **Must use Airship’s custom deep linking solution to track click analytics. | |
Apps | Bar Chart | ✓* | As long as the UUID can be used in other data providers to generate content. | |
Barcode Generator | ✓ | ✓ | Requires a custom solution and only UUIDs can be merged. Can be used with APIs that accept the UUIDs. | |
Countdown Timer | ✓* | ✓ | *Supported but not recommended because push notifications are cached upon receipt and do not refresh. | |
Data Sources | ✓ | ✓ | ||
Facebook Share Button | ✓ | ✓ | Redirects to the web, then to Facebook’s website to post content. Deep linking to Facebook’s app is not possible. | |
Image Personalization | ✓ | Airship stores limited data about app activity, such as last login date, that may be used. | ||
Polling | ✓ | After voting, will leave the app to a mobile landing page as long as the UUID is working. | ||
QR Code Generator | ✓ | Requires a custom solution, and only UUIDs can be merged. Can be used with APIs that accept the UUIDs. | ||
Scratch Off | ✓ | ✓ | On click, will either leave the app for the Scratch off experience or to a custom landing webpage built within the app. | |
Social Sharing | ✓* | ✓ | *Will be directed to the original app, then the web browser, and finally to the desired destination. | |
Video | ✓ | ✓ | Animated GIFs only. |
Send a Push Notification with Movable Ink
You can insert an image source from a Movable Ink Creative Tag into an Airship message to deliver personalized media to your audience.
Go to your campaign in Movable Ink and click Code.
Click Push and copy the image source URL (the
src
of theimg
tag) from the creative tag.Go to the Content step of your message in Airship.
Paste the URL in the Media section in the Content step of your message. Verify that the message preview shows your media from Movable Ink.
Complete the remaining composer steps and send your message.
Send a Message Center message with Movable Ink
Airship Professional Services will create Message Center templates supporting Movable Ink for you. When you compose messages in Airship, you will select a template supporting Movable Ink, then add different parts of your Creative Tags from your Movable Ink campaign to your message.
In the Content step of a message in Airship:
Choose how you want to present the message:
- OR
Add the message to the Message Center inbox only.
Select Message Center and click Add Content.
Select the Visual editor.
Add the message to the Message Center inbox AND link to the message from a push notification and/or in-app message.
Select Message Center, combine with Push Notification and/or In-App Message, and click Add Content.
Select the Visual editor in the Actions section. Message Center is automatically the selected action.
Click Create.
Select your Movable Ink template under Custom Templates.
Go to the Content tab for the template.
Enter your Movable Ink URL.
- Go to your campaign in Movable Ink, click Code, and select Mobile Inbox.
- Copy the URL in your Creative Tag. This is the value of the
href
attribute of thea
tag. - Click the Movable Ink URL field in Airship and paste the URL.
Enter your Movable Ink Image.
- Go to your campaign in Movable Ink and copy the image source URL (the
src
of theimg
tag) from the creative tag. - Click the Movable Ink Image field in Airship and paste the image source.
- Go to your campaign in Movable Ink and copy the image source URL (the
Set up the remainder of your message.
Click Save & Exit.
Complete the remaining composer steps and send your message.
Set Up In-App Automation with Movable Ink
To take advantage of Movable Ink in your in-app automation, you must create a custom HTML message containing your Movable Ink Creative Tags.
Go to your campaign in Movable Ink and click Code.
Click In-App Messaging and copy the Creative Tags that you want to add to your automation.
Paste your Creative Tags into the appropriate places in your Custom HTML message.
In the Airship dashboard, click and select In App Automation.
For Style, select Custom HTML.
In the Content step, upload your Custom HTML file or provide the URL where your content is hosted.
Complete the remaining composer steps and send your message.
Send an Email with Movable Ink
When setting up an email from Airship, you must add a Campaign Code to your email. If your email links your users to a website, you must add a Conversion Tracking script to your site to track conversions from your email.
To use Movable Ink Creative Tags in an email, you must copy the contents of your creative tags into your email. You can either copy your creative tags directly into your own, custom HTML email, or you can create HTML blocks in the email WYSIWYG editor.
In the Content step of your email:
Go to your campaign in Movable Ink and click Code.
Click Email and copy your Creative Tags.
Paste your Creative Tags somewhere in the HTML Body of your email or create reusable HTML blocks in the WYSIWYG editor.
Use your own custom HTML
Paste your Creative Tags into your custom HTML.
Click Add for HTML Body.
Upload or paste HTML. Your HTML must contain the Campaign Code you copied in earlier steps.
Create reusable custom HTML blocks in the WYSIWYG editor
Select a default or saved layout, or select Blank Layout to design your own.
Drag an HTML content element into your email.
Select the HTML element and paste your Creative Tags into the block.
When you finish setting up your email, click Done.
Complete the remaining composer steps and send your message.
Deep Linking Using Movable Ink
Movable Ink deep links allow you to use dynamic in-app click through locations in your creative tags. They also enable you to track clicks within Movable Ink from your in-app campaigns. When using Movable Ink deep links you will need to do a few extra steps to make sure they integrate properly.
First, add a new Movable Ink deep link in the Airship dashboard. Set the template value to {url}
to allow using any Movable Ink URL as
a deep link.
Next, you will need to do a few minor app updates to handle Movable Ink deep links within a mobile app in order to resolve the actual deep link value at runtime.
Android Example
Set up a method or class that is able to resolve a Movable Ink deep link to the actual deep link:
class MovableInkUrl(private val url: String) {
private data class Result(val url: String, val locationHeader: String?, val statusCode: Int)
fun asFlow() = flow {
emit(resolveDeepLink())
}.flowOn(Dispatchers.IO)
fun asLiveData(): LiveData<String?> {
return asFlow()
.catch {
emit(null)
}
.asLiveData()
}
private fun resolveDeepLink(): String? {
var result: Result = resolveUrl(url)
while (true) {
if (result.statusCode == 200) {
return result.url
}
if (result.statusCode == 302) {
val nextUrl = nextUrl(result.url, requireNotNull(result.locationHeader))
if (shouldResolve(nextUrl)) {
result = resolveUrl(nextUrl)
continue
} else {
return nextUrl
}
}
return null
}
return null
}
private fun nextUrl(url: String, location: String): String {
return if (Uri.parse(location).isRelative) {
Uri.parse(url).buildUpon()
.encodedPath(location)
.build()
.toString()
} else {
location
}
}
private fun shouldResolve(url: String): Boolean {
if (url == this.url) {
return true
}
return URLUtil.isHttpsUrl(url)
}
private fun resolveUrl(url: String): Result {
return with(URL(url).openConnection() as HttpURLConnection) {
this.instanceFollowRedirects = false
this.requestMethod = "HEAD"
val l = getHeaderField("Location")
disconnect()
Result(url, l, responseCode)
}
}
}
Resolving deep links might take a few seconds depending on the connection, so you may need to show some sort of UI state while the app is determining the next navigation. In this example, we will show a progress activity that displays while the deep link is being resolved.
class MovableInkActivity : AppCompatActivity() {
internal lateinit var viewModel: UrlViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Autopilot.automaticTakeOff(this)
val url = intent.getStringExtra("url")
val fallbackUrl = intent.getStringExtra("fallback")
if (url == null) {
finish()
return
}
setContentView(createLayout())
viewModel = ViewModelProvider(this, ViewModelFactory(url, fallbackUrl)).get(
UrlViewModel::class.java
)
viewModel.result.observe(this) { result ->
if (result != null) {
UAirship.shared().deepLink(result)
} else {
// no deep link or fallback state
// Toast.makeText(this, "Failed to resolve movable ink URL", Toast.LENGTH_SHORT).show()
}
finish()
}
}
private fun createLayout(): View {
val layout = FrameLayout(this)
val progressBar = ProgressBar(this, null, android.R.attr.progressBarStyleLarge)
progressBar.isIndeterminate = true
val params: LinearLayout.LayoutParams =
LinearLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT)
layout.addView(progressBar, params)
return layout
}
companion object {
fun launch(context: Context, url: String, fallbackUrl: String?) {
val intent = Intent()
.setClass(context, MovableInkActivity::class.java)
.putExtra("url", url)
.putExtra("fallback", fallbackUrl)
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
context.startActivity(intent)
}
}
internal class UrlViewModel(private val url: String, private val fallbackUrl: String?) :
ViewModel() {
val result = MovableInkUrl(url).asFlow()
.catch {
emit(fallbackUrl)
}
.map {
it ?: fallbackUrl
}
.asLiveData()
}
internal class ViewModelFactory(private val url: String, private val fallbackUrl: String?) :
ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
@Suppress("UNCHECKED_CAST") return UrlViewModel(url, fallbackUrl) as T
}
}
}
To handle Movable Ink deep links through Airship you will need to either add a new deep link listener on Airship, or modify your existing deep link listener to process the deep links:
airship.deepLinkListener = object : DeepLinkListener {
override fun onDeepLink(deepLink: String): Boolean {
if (deepLink.startsWith("https://mi.<MYDOMAIN>")) {
MovableInkActivity.launch(context, deepLink, null)
return true
}
...
}
}
iOS Example
Set up a method or class that is able to resolve a Movable Ink deep link to the actual deep link:
}
import Foundation
class MovableInkURL {
private let url: URL
private let delegate: TaskDelegate = TaskDelegate()
private lazy var session : URLSession = {
let config = URLSessionConfiguration.default
return URLSession(configuration: config,
delegate: self.delegate,
delegateQueue: OperationQueue())
}()
init(url: URL) {
self.url = url
}
public func resolve() async throws -> URL {
var request = URLRequest(url: url)
request.httpMethod = "HEAD"
return try await withCheckedThrowingContinuation { continuation in
let task = self.session.dataTask(with: request, completionHandler: { data, response, error in
if let response = response as? HTTPURLResponse,
let locationHeader = response.value(forHTTPHeaderField: "Location"),
let url = URL(string: locationHeader) {
return continuation.resume(returning: url)
} else if let url = response?.url {
return continuation.resume(returning: url)
} else {
return continuation.resume(throwing: error ?? URLError(.badServerResponse))
}
})
task.resume()
}
}
private class TaskDelegate: NSObject, URLSessionTaskDelegate {
func urlSession(_ session: URLSession,
task: URLSessionTask,
willPerformHTTPRedirection response:
HTTPURLResponse, newRequest request: URLRequest) async -> URLRequest? {
guard let url = request.url,
url.absoluteString.lowercased().starts(with: "https")
else {
return nil
}
return request
}
}
}
Resolving deep links might take a few seconds depending on the connection, so you may need to show some sort of UI state while the app is determining the next navigation. In this example, we will show a progress window with a progress view while it resolves.
import Foundation
import SwiftUI
import UIKit
class MovableInkAlert {
static func openForURL(_ url: URL,
scene: UIWindowScene,
onResult: @escaping (URL?) -> Void) {
var window: UIWindow?
let view = MovableLoadingView(url: url) { url in
window?.windowLevel = .normal
window?.isHidden = true
window = nil
onResult(url)
}
let viewController = UIHostingController(rootView: view)
viewController.view.backgroundColor = .clear
viewController.modalPresentationStyle = .currentContext
window = UIWindow(windowScene: scene)
window?.rootViewController = viewController
window?.windowLevel = .alert
window?.makeKeyAndVisible()
}
fileprivate struct MovableLoadingView: View {
let url: URL
let onResult: ((URL?) -> Void)
@ViewBuilder
var body: some View {
VStack {
ProgressView()
.onAppear {
Task {
let url = try? await MovableInkURL(url: self.url).resolve()
await MainActor.run {
self.onResult(url)
}
}
}
.padding()
.background(Color(UIColor.systemBackground))
.cornerRadius(10)
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(Color.gray.opacity(0.2))
}
}
}
To handle Movable Ink deep links through Airship you will need to either add a new deep link delegate on Airship, or modify your existing deep link delegate to process the deep links:
func receivedDeepLink(_ url: URL, completionHandler: @escaping () -> ()) {
if (url.absoluteString.starts(with: "https://mi.<MYDOMAIN>")) {
MovableInkAlert.openForURL(url, scene: try! Utils.findWindowScene()) { resolvedURL in
// Navigate to resolvedURL or a fallbackURL
completionHandler()
}
return
}
...
}
Categories