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:

  1. Take the encoded link.yourdomain.com/click/... link and returns its canonical destination - the link that was entered into the template on Sailthru.
  2. 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.

500

Source: https://developer.android.com/training/app-links/verify-site-associations

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 by click, 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 = SailthruMobile().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 = [[Marigold 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 = 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.


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);