Journeys consent reinstatement from your app
When someone who unsubscribed from your organisation's email opts back in through your app (ticks "email me" in your CRM, say), your app can record that consent with Journeys, and Journeys may mail them again.
It is narrow on purpose. It only lifts an opt-out the person made, only with their express consent, and only when that consent is newer than every opt-out and reinstatement they have had. It never lifts a spam complaint or a permanent bounce.
What an admin is trusting. The person must be on a Journeys canvas your app writes to, but your app can put any address on such a canvas. So an app holding both grants can reinstate any eligible address in the organisation it presents newer consent for. That is why both grants are named, added only by an org admin, and why every reinstatement is recorded in Journeys' history against your app, with its note. Give them only to an app that collects consent itself.
The grant
journeys:consent:reinstate, by name. Only an org admin can add it. * does not include it, and no other
grant does.
The app also needs journeys:audience:write:<canvas id> for the canvases its people are on: a person can only be
reinstated if your app has them on one of those canvases (any status). This guards against mistakes; it is not
the boundary (see above).
waymaker host apps update <app-id> --commander-permissions "team_ids,journeys:audience:write:<canvas id>,journeys:consent:reinstate"
From an agent, use the MCP tool host_app_update with the same names in commander_permissions. The list
replaces what the app had, so include the names it already holds. Removing the grant does not need an admin;
the next call after removal is refused.
Calling it
The app's own context key only. A signed-in person's token is refused (app_key_required).
POST https://apps.waymakerone.com/functions/v1/host-context-api:
{ "action": "journeys_consent_reinstate", "data": { "members": [
{ "email": "pat@example.com", "basis": "express_opt_in", "consented_at": "2026-11-02T09:14:00Z",
"note": "ticked 'email me' on the CRM preference page" }
] } }
Returns { results: [{ email, status, reason? }], counts }, one result per member, in order.
Each member
| Field | |
|---|---|
email | Required. Lower-cased and trimmed. |
basis | Required, and must be express_opt_in. No other basis lifts an opt-out. |
consented_at | Required. When the person gave the consent: an ISO 8601 time with an offset or Z (2026-11-02T09:14:00Z, 2026-11-02T19:14:00+10:00). A time without an offset, or a word such as now, is invalid. Up to 5 minutes ahead of Journeys' clock is accepted. |
note | Required, up to 200 characters: how the person opted in. Keep it to what you would show an auditor; do not put the address in it. |
What it does
status | Meaning |
|---|---|
reinstated | Journeys may mail the person again. The suppression is lifted (recorded as reinstated by your app, with your note, in Journeys' history), the person is subscribed again, and your app's own memberships that were recorded as not mailable because they had opted out are back in your audience (or stay archived, if your app had archived them). |
unchanged | Nothing to lift: the person has not opted out. Nothing was written. A retry of a call that answered reinstated usually answers this, but not_lifted if the person opted out again in between. |
not_lifted | Not lifted, and nothing written. Either consented_at is not later than the person's latest opt-out, reinstatement or earlier consent your app recorded (equal times count as not later), or what stopped their mail is not yours to lift (a spam complaint, a permanent bounce, a suppression made by a person or another app, or the person is not on a canvas your app can write to). The answer does not say which. Do not retry with a later time: send a new consent only when the person gives one. |
invalid | Not written. reason: not_an_email, basis_must_be_express_opt_in, note_required, note_too_long, no_consented_at, bad_consented_at, consent_in_future. |
What the answers reveal. unchanged and not_lifted differ, so an app holding both grants can tell a person who
has not opted out from one who has (or is not its to lift). It cannot tell why, or when. That is an accepted limit of
a strong grant that only an admin can add.
What a reinstatement can lift: an unsubscribe, a suppression after repeated temporary bounces, and a suppression your own app made. Only your app's memberships on canvases it can write to change; a membership the person unsubscribed from, or that was cleaned or is pending, is never changed, nor is anyone else's.
A reinstatement restores consent, not deliverability: an address that has been bouncing still counts its bounces, and Journeys stops again if it keeps bouncing.
What other apps see
- The suppression list shows the address as
reinstated, withsourceapp:<your app id>. Your note is not in the feed or in pushed events; it is kept in Journeys' history. - If your organisation's apps subscribe to suppression changes, the reinstatement is pushed to every one of them, your app included.
Limits and errors
- At most 100 members a call (
too_many_members) and 1,000 per app per rolling hour. A call over the hour is refused whole with 429rate_limitedand aRetry-Afterheader. Nothing is written, so send the same batch again later. - 403
missing_grantnames the grant to ask for. 403app_key_required: use the context key. - 503: nothing was written. Retry the same call.
Error messages never contain an address.