Skip to content
iMessage APIs

Measurement

Tag every link you send, or the channel gets no credit

Taps from a message arrive with no referrer. Analytics files them as direct traffic — the same bucket as people who typed your domain from memory. The sale gets counted; the message that caused it does not.

Why messaging traffic disappears

When someone clicks a link on a web page, the browser sends a referrer header and your analytics knows where they came from. Taps inside a messaging app do not work that way. The Messages app is not a web page, so there is nothing to refer from — the visit arrives looking exactly like someone who opened a new tab and typed your address.

The consequence is specific and expensive: messaging drives a sale, your analytics credits direct / none, and at the end of the quarter the channel looks like it produced nothing. Businesses cancel lines over this. The channel was working; the reporting was blind.

UTM parameters solve it by putting the attribution data in the URL itself. The customer taps a link that already says where it came from, and every analytics tool in common use — GA4, PostHog, Plausible, Shopify, whatever your ecommerce platform reports with — reads those parameters natively. No integration, no vendor cooperation required.

The five parameters

Three are effectively mandatory. Two are optional and should stay empty unless you have a specific question they answer.

ParameterExampleWhat it holdsHow to use it
utm_sourceRequiredimessageWhere the traffic came from.Use one value for the whole channel. If you send from three different lines, they are all still imessage — put the line in utm_content if you need to split them.
utm_mediumRequiredmessagingThe category of channel.Pick one and never deviate. messaging is the sane choice; sms is misleading if you are sending iMessage, and text groups badly against everything else in your reports.
utm_campaignRequiredappointment-reminderThe specific play or promotion.Name it after the play, not the date. appointment-reminder every month gives you a trend line; appointment-reminder-march-2026 gives you twelve unrelated rows.
utm_contentOptionalvariant-aWhich version of the message.The A/B slot. Also useful for distinguishing two links inside the same message.
utm_termOptionalreturning-customerOriginally paid-search keyword; free for you to repurpose.Audience segment or location works well. Leave it empty rather than filling it with something you will not query.

Tool

Build a tagged link

Presets match the plays on the use cases page, so the campaign names stay consistent between what you read here and what you send.

Tagged link builder

Values are normalized to lowercase with dashes, because Appointment Reminder and appointment-reminder become two separate rows in your reports otherwise.

Start from a play

Where the click came from. Keep this identical across every message you ever send.

The channel category. Use one value for all messaging so it groups in reports.

The specific play. Reuse the same name every time you run it.

Optional. Distinguishes variants — which wording, which link position.

Optional. A free slot — audience segment or location works well here.

Your tagged link

Add campaign
https://example.com/book?utm_source=imessage&utm_medium=messaging

Do this in code, not by hand

A builder is fine for a one-off. For anything recurring, put the tagging in the function that sends the message — see the example below. Hand-built links drift within about two weeks, and one capitalized campaign name splits a report in half.

Naming conventions that survive contact with reality

  • Lowercase, always. Most analytics tools treat UTM values as case-sensitive.
  • Dashes, not spaces or underscores. Spaces become %20 and read as a different value.
  • No dates in campaign names. You want a trend line, not a new row every month.
  • Write the list down. One shared doc of approved campaign names, and everyone picks from it.

In code

Attach the tags where the message is built

One helper function, called by every send. After this, nobody on your team has to remember anything.

The example uses Sendblue’s documented endpoint, but the pattern is provider-agnostic — the tagging happens before the request body is assembled, so it works the same whichever API you send through.

javascript
// One helper, used by every send. Tags stop being something
// anyone has to remember.
function taggedLink(url, campaign, extra = {}) {
  const link = new URL(url);
  link.searchParams.set("utm_source", "imessage");
  link.searchParams.set("utm_medium", "messaging");
  link.searchParams.set("utm_campaign", campaign);
  for (const [key, value] of Object.entries(extra)) {
    link.searchParams.set(`utm_${key}`, value);
  }
  return link.toString();
}

await fetch("https://api.sendblue.co/api/send-message", {
  method: "POST",
  headers: {
    "sb-api-key-id": process.env.SENDBLUE_KEY_ID,
    "sb-api-secret-key": process.env.SENDBLUE_SECRET,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    number: customer.phone,
    content: `Hi ${customer.firstName} — your 2pm Thursday slot is confirmed. ` +
      `Details and directions: ${taggedLink("https://acme.co/visit", "appointment-reminder")}`,
  }),
});

Credentials come from environment variables, never from the client. An API key in browser-shipped code is a key you have given away.

Gotchas

Four things that quietly break attribution

Every one of these has cost somebody a quarter of clean data.

Redirects that drop query strings

If your link goes through a redirect — a short link, a www-to-apex rule, an old URL — check that the UTM parameters survive the hop. Plenty of redirect configurations strip the query string silently. Test the real link on a real phone before you send it to a thousand people.

Link previews inflating your clicks

iMessage fetches a URL to render its preview card. That fetch can show up in your analytics as a visit nobody made. If your numbers look impossibly good, this is usually why — filter out requests with no engagement, or compare against server-side conversions.

Tags on the wrong link

If your message contains two links, tag both, and give them different utm_content values. Otherwise you know the message worked but not which half of it did.

Attribution windows that expire

Someone taps on Tuesday and buys on Friday from a bookmark. Whether that sale credits messaging depends on your analytics attribution window and cookie lifetime — which varies by tool and by browser. Know what yours is before you conclude the channel underperformed.

Set the tagging up before the first send

Attribution cannot be added retroactively. The messages you send in week one are the ones you will most want to evaluate — tag them now, not after somebody asks whether the line is paying for itself.