user_id and Courier resolves the addresses.
That record is the profile. It holds email, phone number, device tokens, locale, timezone, and custom attributes, so you never repeat contact details on a request. Preferences, lists, and audiences attach to it.
Save a user
Creating and updating are the same call, so this is safe to run whenever your own record changes:user_id is yours to choose. Use whatever your own system already calls that person, so you never keep a mapping table.
What each channel needs
One field per channel, and Courier picks the right one per send:
A profile holds every field at once. Save
email and phone_number together and one template can reach either channel without a second write.
Each category introduction carries its own providers’ fields: , , , and .
Send by user id
Once the profile exists, a send names the person and nothing else:Address someone you have not saved
An address in theto object sends with no stored profile. It reaches the recipient and leaves nothing behind, so there is no profile to update later and no preferences to respect. A saved user stays the norm, because preferences, lists, tenants, and the inbox all key off one.
Each channel takes its own field or object:
- Email
- SMS
- Push
- Slack
- Microsoft Teams
- Discord
- Webhook
- Inbox
The two combine. Courier merges an inline value over the saved profile for that message only, so this sends to the override address and never changes the profile:
When to write a profile
On signup, in the same handler that creates the user in your own database. , , , and each show that call in place. Whenever an address changes.POST merges, so writing one field leaves the rest alone.
Not on every send. A profile write per send costs a round trip and buys nothing, because the profile is already there.
The profile object
A profile accepts three kinds of field:- OpenID Connect standard claims.
email,phone_number,name,given_name,family_name,locale,zoneinfo,birthdate, and more. custom. A free-form object for anything specific to your app.- Provider-specific keys.
apn,airship, and others, for inline channel tokens.
{profile.name} and custom data as {profile.custom.company}.
Merge, replace, or patch
The Profiles API has four operations on/profiles/{user_id}. They differ in what happens to fields you leave out:
Use
POST for everyday updates so you never drop data. Use PUT only for a deliberate full overwrite. The Node SDK names these profiles.create, profiles.replace, profiles.update for patch, and profiles.delete.
Device tokens
Push reaches a device, not an address, so a user carries a device token per device they sign in on. The Courier mobile SDKs register and refresh those tokens for you, on iOS, Android, Flutter, and React Native. walks the whole thing: a provider, the SDK, and a test push to your own device. Tokens attach to theuser_id rather than to the profile body, so Courier expires a dead one without touching the profile. A backend that manages tokens itself writes them with the .
Limits & behavior
- A send resolves an existing profile, it does not create one. To reach a user by
user_id, save the profile first or include inline contact details on that send. In-app recipients are the exception. Signing a user in to the Inbox registers them, so you can send to a user who exists only through . - No endpoint lists every user. You retrieve profiles one at a time by known
user_id. Keep your own system of record as the source of truth. customis free-form, standard claims are typed. Store app-specific data undercustom. Courier accepts top-level keys outside the OIDC claim set, but they are clearest incustom.- No documented rate limit on the Profiles API. The , but profile reads and writes have no published per-second cap. For a large sync, throttle your loop or import a CSV in the console.
- Device tokens are not part of the profile body. Writing a profile with
PUTdoes not remove stored device tokens.
FAQ
What should I use for the user_id?
What should I use for the user_id?
The id your own system already uses for that person. Courier accepts any string, so matching your database means no mapping table and no second lookup.
Does sending to a new user_id create the profile?
Does sending to a new user_id create the profile?
A send looks up an existing profile, it does not persist one from the
to object. Create the profile with first, or pass inline contact details for a one-off. The Inbox is the exception. Authenticating a user registers them.Does saving a profile twice create two users?
Does saving a profile twice create two users?
user_id is the key, so a second write to the same id updates that user. is an upsert.Can one user hold email, phone, and a Slack token at once?
Can one user hold email, phone, and a Slack token at once?
One profile holds every address at once, which is the point. Save every address you have and the same send reaches whichever channel the template and pick.
Where do push device tokens get saved?
Where do push device tokens get saved?
The mobile SDKs register them on
signIn, or your backend writes them with . Either way they attach to the user_id rather than to the profile body. See .Can I export or dump all of my users?
Can I export or dump all of my users?
Courier has no endpoint to list every
user_id, export all profiles, or return a total user count. Retrieve profiles one at a time with . List the users in one tenant with .Is 'recipient' the same as 'user'?
Is 'recipient' the same as 'user'?
Courier uses them interchangeably. A recipient identified by
user_id is a stored user profile.What is the difference between POST and PUT on a profile?
What is the difference between POST and PUT on a profile?
POST merges, so fields you omit are preserved. PUT replaces, so the profile becomes exactly the object you send and omitted fields are removed. Use POST for partial updates, PUT only for a deliberate full overwrite.How do I store data that is not an OIDC claim?
How do I store data that is not an OIDC claim?
Put it under
custom. It is free-form, and every key is available in templates as {profile.custom.plan}.