Skip to content
iMessage APIs
Measurement8 min read

UTM tracking for messaging: the setup that survives a year

Attribution does not usually fail dramatically. It fails through drift — one capitalized campaign name, one redirect, one person building a link by hand.

Taps from a messaging app arrive with no referrer. Analytics files them as direct traffic — the same bucket as someone who typed your domain from memory. The sale gets counted and the message that caused it does not, 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. The mechanics of fixing it are on the UTM tracking guide, including a link builder. This article is about the part that goes wrong *after* you have set it up correctly.

Drift is the real enemy

A tagging scheme rarely breaks all at once. It degrades:

  • Somebody builds a link by hand and types Appointment Reminder instead of appointment-reminder. Most analytics tools treat these as different values, and your report now has two rows for one play.
  • A second person picks appt-reminder because it seemed clearer.
  • Somebody adds -march to the campaign name, so month-over-month comparison quietly stops existing.
  • A new play launches with utm_medium=sms because that is what the person knew, and it groups separately from everything else.

Six months later the reporting is unusable and nobody can say when it stopped being useful. This is not a discipline problem you can solve by asking people to be careful.

The structural fix

Put the tagging in the function that sends the message. If a human never types a UTM value, a human never mistypes one. Everything else — shared docs, naming guidelines, spreadsheets of approved campaigns — is a mitigation for having got this wrong.

The conventions worth enforcing

ParameterValueRule
utm_sourceimessageOne value forever. Never varies.
utm_mediummessagingOne value forever. Not 'sms', not 'text' — those group badly.
utm_campaignappointment-reminderNamed after the play. Never contains a date.
utm_contentvariant-aVariants and multiple links in one message. Optional.
utm_termreturning-customerSegment or location. Optional.

Lowercase, dashes, no spaces. If you run several lines, they are all still utm_source=imessage — put the line identity in utm_content if you genuinely need to split them. Resist the urge to encode everything; a parameter you will never query is noise.

The four failure modes

1. Redirects that drop the query string

A short link, a www-to-apex rule, an old URL that forwards — plenty of redirect configurations strip query parameters silently. The link works, the page loads, the attribution vanishes. Test the real link on a real phone before it goes to a thousand people, and re-test whenever anyone touches your DNS or redirect rules.

2. Link previews inflating your numbers

iMessage fetches a URL to render its preview card. That fetch can appear in analytics as a visit nobody made. If your click numbers look impossibly good, this is usually why. Compare against server-side conversions rather than trusting page views, and be suspicious of a click-through rate that would be a record for any channel.

3. Two links, one tag

If a message contains two links, tag both with different utm_content values. Otherwise you know the message worked and not which half of it did — which is exactly the information you need to improve it.

4. Attribution windows you never checked

Someone taps on Tuesday and buys on Friday from a bookmark. Whether that credits messaging depends on your analytics attribution window and cookie lifetime, which vary by tool and are increasingly constrained by browsers. Know what yours is *before* you conclude the channel underperformed — this single unchecked setting has killed more messaging budgets than bad copy.

What good looks like

text
Source / Medium: imessage / messaging          Last 90 days

campaign                sessions   conv.   revenue
appointment-reminder       1,204     312   $56,160
review-request               486      91        —
waitlist-fill                203      74   $10,360
rebooking                    177      52   $ 9,360
lead-response                141      19   $   n/a

5 campaigns. Same names as 90 days ago.
No case variants, no dated duplicates.

If your report looks like this after a year, the scheme held.

Five rows, stable names, comparable across periods. That is the whole goal, and it is achievable by making it impossible to do otherwise. Build the tagging into the send — here is what that looks like in code.

UTMattributionanalytics