Handling Sailthru Links
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
. This allows us to track tap-through metrics on emails and measure your success. For example, Sailthru maintains a fake online clothing store, Varick and Vandam, as a demonstrator platform. Links in emails from V+V will look something like this: https://link.varickandvandam.com/click/13071627.1/aHR0cHM.....
, but will redirect to: https://varickandvandam.com/products/...
, referred to here as the "canonical url".
Marigold 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..
Make sure you've followed the steps in the Sailthru Docs before starting this guide - it corresponds to the "Link Parsing and Tracking" section.
Handling Universal Links From a Sailthru Email
Android
Prior to Android 12, apps 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.
However, from Android 12 onwards you must enable Android App Links in your app in order for the link association to be made. For more details on registering the link domain see our GetStarted guide.
We're going to continue to use Varick and Vandam as our example - it's link domain is https://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
. Adding android:autoVerify="true"
to the intent filter will allow the app to automatically verify your app with this link domain when it is installed.
<activity
android:name=".MainActivity">
<intent-filter android:autoVerify="true">
<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.
After that, we'll need to configure our receiving activity - in this case it's MainActivity
- to handle these links, using the handleSailthruLink
method. We recommend calling this from the Activity.onCreate()
method:
// 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 EngageBySailthru().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
}
// 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 = EngageBySailthru().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
}
Note that though 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 the UIApplicationDelegate: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:
- (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 = [[Marigold new] handleSailthruLink:incomingURL];
} else {
routingURL = incomingURL;
}
[self routeToViewForURL:routingURL];
return true;
}
return false;
}
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 = Marigold().handleSailthruLink(incomingURL)
}
self.routeToViewForURL(url: routingURL)
return true;
}
return false;
}
Note that the method 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:
// 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 EngageBySailthru().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");
}
// In class MainActivity
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (isSailthruLinkIntent()) {
val sailthruLinkUri: Uri = intent.data
val canonicalUri: Uri = EngageBySailthru().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.
- (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 = [[EngageBySailthru 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];
}
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 = Marigold().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.
Unity
Unity has a guide for deep link handling here: https://docs.unity3d.com/Manual/deep-linking.html
Once you have access to the link you can provide it to the SDK.
EngageBySailthru.OnUnwrappedLinkReceivedEvent += (object sender, UwrappedLinkReceivedEventArgs args) => {
// Handle unwrapped link
};
new EngageBySailthru().HandleSailthruLink(link);
Updated 6 months ago