Email campaign links that open your app
After this guide, every button in your campaign email is a Strait link that opens the right screen in your app, and each tap is counted under your email channel and campaign.
On this page 6
- Time
- About 15 minutes
- You need
- A Strait secret key in your server's environment as SECRET_KEY, and the email tool you send campaigns with
- Works on
- Any server. Code for curl and Node 18+. The links work for Android and iPhone users and on computers.
An email campaign link needs two things: the destination, with your UTM tags on it, and a short link that opens the app. Strait reads utm_source and utm_campaign from the destination to label each tap, and sends the same URL, tags included, to your website or app. You don't add anything to the email itself.
1. Make the campaign link with UTM tags on the destination
Put the tags on longUrl, not on the short link. The destination is what your website analytics and your app see:
curl https://strait.link/v1/links \
-H "Authorization: Bearer $SECRET_KEY" \
-H "Content-Type: application/json" \
-d '{
"slug": "diwali-email",
"longUrl": "https://shop.example.com/sale/diwali?utm_source=newsletter&utm_medium=email&utm_campaign=diwali-2026",
"title": "Diwali sale"
}'The same with the Node SDK, plus the dry run from step 2:
import { StraitClient } from '@straitlink/node';
const strait = new StraitClient({ apiKey: process.env.SECRET_KEY, baseUrl: 'https://strait.link' });
const link = await strait.createLink({
slug: 'diwali-email',
longUrl: 'https://shop.example.com/sale/diwali?utm_source=newsletter&utm_medium=email&utm_campaign=diwali-2026',
title: 'Diwali sale',
});
console.log(link.shortUrl, link.campaign);
const tap = await strait.simulateTap({ url: link.shortUrl, ua: 'android' });
console.log(tap.decision, tap.sentTo);You don't need to send campaign: it defaults to the destination's utm_campaign. To compare two buttons in one email, make one link per button and give each its own utm_content.
You should see: "shortUrl": "https://hilltop.strait.link/diwali-email" and "campaign": "diwali-2026" in the answer. The Node script prints https://hilltop.strait.link/diwali-email diwali-2026, then redirect app_or_store.
2. Check what a tap does before you send
GET /v1/simulate runs the same decision as a real tap, without counting a tap or firing a webhook. Try an Android phone and an iPhone:
curl "https://strait.link/v1/simulate?url=https%3A%2F%2Fhilltop.strait.link%2Fdiwali-email&ua=android" \
-H "Authorization: Bearer $SECRET_KEY"
curl "https://strait.link/v1/simulate?url=https%3A%2F%2Fhilltop.strait.link%2Fdiwali-email&ua=iphone" \
-H "Authorization: Bearer $SECRET_KEY"The other presets are ipad, desktop, instagram-ios, instagram-android, facebook-android, whatsapp-android and bot, or pass any raw user agent.
You should see: for Android, "decision": "redirect", "sentTo": "app_or_store" and a location that starts intent://shop.example.com/sale/diwali?utm_source=newsletter&utm_medium=email&utm_campaign=diwali-2026&strait_click=…, so the tags reach the app. For iPhone, sentTo follows your iPhone settings: a workspace with no App Store ID answers "sentTo": "website" with the destination, tags included, as location.
3. Put the link in the email
Paste https://hilltop.strait.link/diwali-email as the button's link in your email tool. If the tool adds its own UTM tags to every link, check what it adds: a utm_source on the tapped URL wins over the one on the destination for the tap's channel, and it isn't copied to the destination.
You should see: in a test send to yourself, the button's link goes to https://hilltop.strait.link/diwali-email, and tapping it on a phone with your app opens the sale screen.
4. Read the taps per channel
curl "https://strait.link/v1/analytics?link=diwali-email&group=channel" \
-H "Authorization: Bearer $SECRET_KEY"import { StraitClient } from '@straitlink/node';
const strait = new StraitClient({ apiKey: process.env.SECRET_KEY, baseUrl: 'https://strait.link' });
const report = await strait.getAnalytics({ link: 'diwali-email', group: 'channel' });
console.log(report.rows);group also takes day, link, platform or none. Dashboard → Analytics shows the same numbers, with diwali-2026 in the Campaign filter.
You should see: after your test tap, "rows": [{ "key": "newsletter", "taps": 1, "billableTaps": 1, … }]. App opens and installs join the same row once people open the app from the link.
Common errors
409"slug already exists in this workspace". Each campaign or button needs its own slug, likediwali-email-heroanddiwali-email-footer.- Taps show up as
directorgmail, notnewsletter. The destination has noutm_source. Change it withPATCH /v1/links/{slug}; the short link in the email stays the same. - Your web analytics sees no UTM tags. The tags were added to the short link (
https://hilltop.strait.link/diwali-email?utm_source=…). Strait uses a tag there to label the tap, but doesn't copy it to the destination. Put the tags onlongUrl.
Next steps
- Create links in bulk from your backend: one link per segment or per button, up to 500 per call.
- Analytics: how we count: what counts as a tap, an install and an open.