Back to blog

qr codes

SMS QR Codes: How to Make One That Actually Opens Messages

Most SMS QR codes are broken on one platform because iOS and Android disagree on the syntax. Here is what to encode so a printed code keeps working.

Noa

An SMS QR code should do one thing: someone points a camera at it, their messaging app opens with your number and a message already typed, and they hit send.

Most of them do not do this. They work on the phone the person testing happened to be holding, get printed on five hundred flyers, and quietly fail for half of everyone who scans them.

Here is why, and what to encode instead.

The sms: URL scheme

The scheme itself is simple:

sms:+15551234567

Scan that and Messages opens, addressed to the number. No message body, but it works everywhere.

The trouble starts when you want to prefill what they send.

The syntax split nobody warns you about

To prefill the body you add a body parameter. And here the two platforms diverge:

iOS:      sms:+15551234567&body=Hi%20there
Android:  sms:+15551234567?body=Hi%20there

Note the separator. iOS has historically wanted &; Android wants ?. A link hard-coded with one is not reliably right on the other.

Worse, the whole feature is undocumented on iOS. Apple's archived URL Scheme Reference states the URL must not include a body at all. It has worked anyway for many years, which is not the same as being guaranteed to keep working.

You will find a widely-circulated hybrid — sms:+1555...?&body= — that claims to satisfy both. It often does. It is also a guess about undocumented behaviour on two independently-changing platforms, which is a poor thing to print onto something you cannot take back.

The real problem: print is permanent

Software has an escape hatch. A broken link on a website is a five-minute fix.

A QR code on a shop window, a receipt roll, a vehicle wrap, or ten thousand flyers has no escape hatch. Whatever you encoded is what it does, for as long as that object exists.

So the rule is: never encode anything device-specific into a printed QR code.

What to encode instead

Encode an HTTPS URL you control, and resolve the platform difference on your server at scan time.

https://go.senderz.com/a7k2np

When that is opened, the server reads the User-Agent, decides which sms: form that device wants, and redirects:

iPhone   → sms:+15551230000&body=Hi!%20%5B%23a7k2np%5D
Android  → sms:+15551230000?body=Hi!%20%5B%23a7k2np%5D
Desktop  → a page showing the number and message to send by hand

Three properties fall out of this that the direct approach cannot offer:

The syntax stays fixable. If Apple changes behaviour in a future iOS, you change one line on the server. Every code already printed keeps working.

You get scan analytics. A direct sms: code is invisible — you have no idea how many people scanned it, only how many completed. With a redirect you see both, and the gap between them tells you whether the problem is placement or copy.

Desktop and in-app browsers stop being dead ends. Instagram and X in-app browsers frequently block sms: handoff entirely. A direct code just fails silently. A redirect can serve a page with the number and message written out.

Use a 302, not a 301. A permanent redirect gets cached by browsers, which re-freezes exactly the thing the indirection exists to keep changeable.

Practical details for the code itself

Keep the payload short. Fewer characters means fewer modules, which means a code that scans reliably from further away and survives being printed small. This is a real argument for a short domain: go.senderz.com/a7k2np is meaningfully easier to scan than a long URL with query parameters.

Error correction level M is right for most uses. L is too fragile for anything that will be handled; H bloats the code for no benefit unless you are overlaying a logo.

Test the physical object, not the screen. Print it at the size you will actually use, then scan from the distance a customer will actually stand. A code that scans perfectly on a monitor can fail on a receipt.

ASCII only in the prefilled message. A non-ASCII character flips an SMS from GSM-7 to UCS-2 encoding, halving the segment size and risking mangling. Emoji in an opener is a real-world failure, not a theoretical one.

Generating one

The API returns the link; render the QR from it client-side rather than generating images on your server.

curl -X POST https://api.senderz.com/v1/invites \
  -H "Authorization: Bearer $SENDERZ_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"label": "Counter sign", "opener": "Hi!"}'
{
  "data": {
    "ref": "a7k2np",
    "link": "https://go.senderz.com/a7k2np",
    "number": "+15551230000",
    "opener": "Hi!"
  }
}

Encode link. That string is what belongs on the poster.

FAQ

Questions

Can I just use a generic QR generator with an sms: link?

You can, and it will work on one platform and be unreliable on the other. For a screen that is a fixable mistake. For anything printed it is not, which is the entire argument for encoding a URL you control instead.

Does the scanner app matter?

Less than it used to. The native camera on both iOS and Android handles sms: and https: links directly. Third-party scanner apps sometimes open links in their own in-app browser, which is another reason an HTTPS URL with a fallback page is safer than a raw sms: URI.

Why does the prefilled message have a code in it?

So the inbound message can be attributed to the right business and the right placement. On a shared sending number it is the only thing distinguishing which account a stranger's first message belongs to.

What size should I print it?

As a rule of thumb the code should be at least a tenth of the scanning distance — 3cm for arm's length, 20cm for across a room. Shorter encoded URLs allow smaller codes, which is why payload length is worth caring about.

Tagged qr codes sms developers how-to

Ready to start sending?

Create your free account and send your first message in minutes.