Handling Sailthru Links
Contents
When email is sent from Sailthru Email Manager, we go through the links in the templates, and then replace them with redirects, with domains of your choosing, generally something in the form of http://link.yourdomain.com
.
https://link.varickandvandam.com/click/13071627.1/aHR0cHM.....
, but will redirect to: https://varickandvandam.com/products/...
, referred to here as the "canonical url".
Sailthru Mobile SDK can be configured to handle these links and to open your app whenever a certain type of link is clicked. The SDK methods - called handleSailthruLink
on both platforms - will do two things:
- Take the encoded
link.yourdomain.com/click/...
link and returns its canonical destination - the link that was entered into the template on Sailthru. - Ping the link domain, meaning that tapthrough analytics will be preserved on the Sailthru platform..
Handling Universal Links From a Sailthru Email
Android
Android can be configured to handle links from Sailthru emails without any further configuration on the Sailthru platform, using Android's deep-linking functionality. This means that when clicking on links in emails, users will be prompted which app to open the link with - example below. Note: The Sailthru Mobile platform does not currently support the use of App Links. We're going to continue to use Varick and Vandam as our example - its link domain ishttps://link.varickandvandam.com
. To route these links to our V+V app, we'll need to define an intent filter on our MainActivity
in our AndroidManifest.xml
.
<activity
android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" />
<data android:host="link.varickandvandam.com" /> <!-- Change this one to your link domain, leaving the others as they are -->
<data android:pathPrefix="/click/" />
</intent-filter>
</activity>
This intent filter will listen on links being clicked that:
- Have the scheme https
- Are hosted on
link.varickandvandam.com
, and - Are prefixed by the string
/click
. This is important, as all Sailthru links are prefixed byclick
, and we don't want our intent filter to capture the wrong link by accident.
MainActivity
- to handle these links, using the handleSailthruLink
method. We recommend calling this from the Activity.onCreate()
method:
Java
// In class MainActivity
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Intent intent = getActivity().getIntent();
if (isSailthruLinkIntent(intent)) {
Uri sailthruLinkUri = intent.getData();
Uri canonicalUri = new SailthruMobile().handleSailthruLink(sailthruLinkUri, null);
doStuffWithUri(canonicalUri);
} else {
// Handle other sorts of intents
}
}
private Boolean isSailthruLinkIntent(Intent intent) {
return intent.getAction().equals(Intent.ACTION_VIEW) &&
intent.getScheme() != null && intent.getScheme().equals("https") && // It's a web link
intent.getData().getHost().equals("link.varickandvandam.com");
}
private void doStuffWithUri(Uri uri) {
// ... start the right activity for this sort of link, or display the right fragment, etc
}
Kotlin
// In class MainActivity
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val intent: Intent = getActivity().getIntent()
if (isSailthruLinkIntent(intent)) {
val sailthruLinkUri: Uri = intent.data
val canonicalUri: Uri = SailthruMobile().handleSailthruLink(sailthruLinkUri, null)
doStuffWithUri(canonicalUri)
} else {
// Handle other sorts of intents
}
}
private fun isSailthruLinkIntent(intent: Intent): Boolean {
return intent.action == Intent.ACTION_VIEW && intent.scheme != null && intent.scheme == "https" && intent.data.host == "link.varickandvandam.com"
}
private fun doStuffWithUri(uri: Uri) {
// ... start the right activity for this sort of link, or display the right fragment, etc
}
handleSailthruLink
can take a completion handler, we've just set it as null, as it's mostly only useful for debugging.
iOS
To properly handle Universal Links, your app will need to implement theUIApplicationDelegate:application:continueUserActivity:restorationHandler:
method in its AppDelegate
. When an app is opened with a Universal Link, this method is called with an instance of NSUserActivity
, which will have a the URL the user clicked on as its webpageURL
property. With this in mind, we can implement this method:
AppDelegate.m
- (BOOL)application:(UIApplication *)application continueUserActivity:(NSUserActivity *)userActivity restorationHandler:(void (^)(NSArray *restorableObjects))restorationHandler {
if ([userActivity webpageURL] != nil) {
NSURL *routingURL = nil;
NSURL *incomingURL = [userActivity webpageURL];
if ([[incomingURL host] isEqualToString:@"link.yourdomain.com"]) {
routingURL = [[SailthruMobile new] handleSailthruLink:incomingURL];
} else {
routingURL = incomingURL;
}
[self routeToViewForURL:routingURL];
return true;
}
return false;
}
AppDelegate.swift
func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool {
if let incomingURL = userActivity.webpageURL {
var routingURL = incomingURL as URL?
if incomingURL.host == "link.yourdomain.com" {
routingURL = SailthruMobile().handleSailthruLink(incomingURL)
}
self.routeToViewForURL(url: routingURL)
return true;
}
return false;
}
routeToViewForURL
is just a placeholder, as different apps will have different ways of parsing web URLs into App views.
Note: Your app must register the link domain in its associated domains list (e.g. applinks:link.domain.com). This will allow the app to register the correct domain and so that it will be passed the universal link rather than a browser.
React Native
In order to handle Universal Links in React Native, they first need to be handled natively. This process is very similar to the setup outline above, but the decoded link needs to be set in the original NSUserActivity/Intent so that it will be passed to the React Native component.Android
The intent filter should be added to the AndroidManifest file as detailed above, and the Intent should be parsed in a similar manner. However, once the link has been decoded it should be set as the data field of the Intent to provide it to the React Native component:Java
// In class MainActivity
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Intent intent = getActivity().getIntent();
if (isSailthruLinkIntent(intent)) {
Uri sailthruLinkUri = intent.getData();
Uri canonicalUri = new SailthruMobile().handleSailthruLink(sailthruLinkUri, null);
// set decoded link here to provide to React Native
intent.setData(canonicalUri);
} else {
// Handle other sorts of intents
}
}
private Boolean isSailthruLinkIntent(Intent intent) {
return intent.getAction().equals(Intent.ACTION_VIEW) &&
intent.getScheme() != null && intent.getScheme().equals("https") && // It's a web link
intent.getData().getHost().equals("link.varickandvandam.com");
}
Kotlin
// In class MainActivity
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (isSailthruLinkIntent()) {
val sailthruLinkUri: Uri = intent.data
val canonicalUri: Uri = SailthruMobile().handleSailthruLink(sailthruLinkUri, null)
// set decoded link here to provide to React Native
intent.data = canonicalUri
} else {
// Handle other sorts of intents
}
}
private fun isSailthruLinkIntent(): Boolean {
return intent.action == Intent.ACTION_VIEW && intent.scheme != null && intent.scheme == "https" && intent.data.host == "link.varickandvandam.com"
}
iOS
Once the link has been decoded it should be set as the NSUserActivity's webpageURL field. Then, the RCTLinkingManager should be called to process the link and provide it to the React Native component.AppDelegate.m
- (BOOL)application:(UIApplication *)application continueUserActivity:(NSUserActivity *)userActivity restorationHandler:(void (^)(NSArray *restorableObjects))restorationHandler {
if ([userActivity webpageURL] != nil) {
NSURL *routingURL = nil;
NSURL *incomingURL = [userActivity webpageURL];
if ([[incomingURL host] isEqualToString:@"link.yourdomain.com"]) {
routingURL = [[SailthruMobile new] handleSailthruLink:incomingURL];
} else {
routingURL = incomingURL;
}
// set decoded link here to provide to React Native
userActivity.webpageURL = routingURL;
}
// pass to React Native Linking Manager
return [RCTLinkingManager application:application
continueUserActivity:userActivity
restorationHandler:restorationHandler];
}
AppDelegate.swift
func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([Any]?) -> Void) -> Bool {
if let incomingURL = userActivity.webpageURL {
let canonicalUrl : URL?;
if incomingURL.host! == "link.yourdomain.com" {
canonicalUrl = SailthruMobile().handleSailthruLink(incomingURL)
} else {
canonicalUrl = incomingURL
}
// set decoded link here to provide to React Native
userActivity.webpageURL = canonicalUrl
}
// pass to React Native Linking Manager
return RCTLinkingManager.application(application, continue: userActivity, restorationHandler: restorationHandler as? ([Any]?) -> Void)
}
React Native
Once the native handling is in place, the decoded link can be accessed in the React Native component like so:componentDidMount() {
// the inital URL is used to handle android universal links, and links that start closed iOS apps
Linking.getInitialURL().then((url) => {
if(url) {
// handle URL here
}
}).catch((e) => {
console.error(url);
});
// This listener will handle links passed to iOS apps running in the background
Linking.addEventListener('url', this.handleOpenURL);
}
componentWillUnmount() {
Linking.removeEventListener('url', this.handleOpenURL);
}
handleOpenURL(event) {
// handle event.url here
}
Additional information about handling universal links in React Native can be found here.