Proof of opt-in for notifications: model consent by channel
Set up proof of opt-in for notifications by channel, store clear evidence, and handle changes and audits without confusing users or your team.

What consent and proof-of-opt-in really mean
Consent means a person clearly agreed to receive a specific kind of message on a specific channel. That distinction matters because email, SMS, and push notifications aren’t treated the same by users, carriers, app platforms, or privacy laws. Someone might welcome a shipping update by email, but feel blindsided by a marketing text.
Proof is what you can show later when someone asks, “Why did you message me?” Proof-of-opt-in isn’t a screenshot of a form or a vague note like “user subscribed.” It’s a record that lets you trace who agreed, what they agreed to, when it happened, and how it happened.
When records are weak, problems show up quickly. Support can’t explain unexpected messages, refunds increase, and people lose confidence. If a complaint escalates, you may also struggle to show that consent existed at the time you sent the message, which is often the detail that matters most.
Consent is more than a popup
Many teams focus on the UI prompt (checkboxes, toggles, OS permission dialogs) and forget the audit trail behind it. The real goal is trust and traceability. A clear consent model makes it easier to do the right thing every time, even when staff changes, systems migrate, or users change their mind.
A practical consent record answers a few basic questions:
- Who agreed (the user identifier and, if relevant, the destination like an email address or phone number)
- What they agreed to (channel and message type or purpose)
- When it happened (timestamp in UTC, or timestamp plus time zone)
- How it happened (where the request was shown and what the user saw, including version and language)
- Context that helps resolve disputes when appropriate (for example, app/device info or IP address)
Why this builds trust
Users judge you by the moments that feel intrusive: a late-night push, a surprise SMS charge, an email they don’t remember signing up for. If you can pull up the exact consent event and explain it in plain language, you can resolve tickets calmly and consistently.
If you’re building with a platform like AppMaster, it helps to treat consent as first-class data from day one: structured, searchable, and difficult to overwrite by accident.
Kinds of notifications and why the rules differ
Not all notifications are treated the same because they serve different purposes and reach people in different places. If you want reliable proof of opt-in for notifications, start by labeling messages by intent and by channel.
Transactional vs marketing (plain meaning)
Transactional messages are triggered by something the user did, or something they need to know to use the service. Think password resets, receipts, security alerts, or a change in account status. People expect these, and blocking them can break the product experience.
Marketing messages are promotional. Think newsletters, product offers, win-back campaigns, or “here’s what’s new” blasts. These should be optional, and users should be able to say “no” without losing access to core features.
A practical rule: if the message is required to deliver what the user asked for, it’s likely transactional. If it’s meant to increase engagement or sales, it’s marketing.
Channels: email, SMS, push, and in-app
Channels behave differently, so consent and evidence are usually tracked per channel, not as one global checkbox.
Email is often used for both transactional and marketing. Many products treat transactional email as part of basic service delivery, but marketing email generally needs a clear “yes” and a clear way to stop.
SMS feels more intrusive, can cost money in some setups, and comes with strict carrier and provider rules. Treat SMS consent as its own decision.
Push notifications are controlled by the device OS prompt and user settings. Your app might have a device token, but the user can disable push at any time. That changes what you can send.
In-app messages appear inside the product. They often follow product UX rules more than telecom rules, but users still expect control, especially for promotional banners.
Because requirements vary by country and provider policy, many teams choose a simple baseline: clear opt-in for marketing per channel, and careful documentation for anything that could be disputed.
“Soft opt-in” is a common gray area. In practice, it usually means you message someone because of an existing relationship (for example, they became a customer) even if they didn’t tick a dedicated marketing box. Even then, documentation matters: what the relationship was, what you sent, and how the person can opt out.
If you’re building this in AppMaster, keep the model straightforward: a user can opt in to marketing email but opt out of SMS, while still receiving necessary transactional alerts. That separation makes both compliance and trust easier.
How to model opt-ins per channel (data you should store)
If you want reliable proof of opt-in for notifications, your data model has to be specific. “User agreed” isn’t enough. Consent depends on the channel (email, SMS, push) and the purpose (marketing, product updates, security alerts).
A practical approach is to treat consent as its own record, not a checkbox buried inside the user profile. One user can have many consent records, and each record should map to a single channel and a single purpose.
The minimum fields that keep you out of trouble
Start with a Consent record shaped like: user + channel + purpose + status. Then add the details that make the record understandable later.
At a minimum, most products need:
- user_id
- channel (email, sms, push - keep it a fixed list)
- purpose (marketing, product_updates, account_security - keep it a fixed list)
- status (opted_in, opted_out, pending, unknown)
- opted_in_at / opted_out_at
To avoid guessing later, also capture where it happened and when it was last confirmed:
- source (signup_form, settings_page, checkout, support_action)
- last_confirmed_at (useful after policy changes)
Consent text snapshots (so you can prove what they saw)
Consent isn’t just a click. It’s agreement to specific words. Store a consent_text_version (or a short snapshot_id) that points to the exact text shown at the time.
Keep the snapshot simple: the message, the language/locale, and when it was active. If the copy changes, create a new version instead of editing the old one.
A compact example looks like this:
{
"user_id": "u_123",
"channel": "sms",
"purpose": "marketing",
"status": "opted_in",
"opted_in_at": "2026-01-25T10:15:00Z",
"source": "checkout",
"consent_text_version": "sms_mkt_v3",
"last_confirmed_at": "2026-01-25T10:15:00Z"
}
If you build with AppMaster, this maps cleanly to a Data Designer model (Consent plus ConsentTextSnapshot) and simple logic in the Business Process Editor. The main goal is consistency: every channel and purpose follows the same structure so reporting and audits don’t turn into a scramble.
What counts as evidence, and what to capture
“Evidence” is anything that lets you answer two questions later: what the user agreed to, and how they agreed to it. For proof of opt-in for notifications, you want records that are specific, time-stamped, and tied to a real user action (not just “consent = true”).
Start with the action itself. If consent is given by a checkbox, toggle, or double opt-in link, store the interaction and the exact text the user saw (or a version ID for it). Screenshots rarely scale; a consent copy/version label does.
Capture enough detail to make the moment reproducible:
- action type (checked a box, toggled on, clicked a confirmation link)
- timestamp in UTC
- channel and purpose
- consent text version or policy/version identifier
- page or screen name and language
Add technical context carefully. It can help resolve disputes, but it also increases privacy risk. For web, IP and user agent can be useful when appropriate. For mobile, consider a device ID that’s already part of your identity model, but avoid collecting extra identifiers “just in case.”
If you send through providers, keep their identifiers too. Provider message IDs (email, SMS, push) help you show that a specific consent record was used for a specific send when investigating complaints or deliverability issues.
Finally, decide what not to store. Good evidence doesn’t mean collecting everything. Avoid storing full message content if templates are enough, and avoid unrelated device data. If you build this flow in AppMaster, treat evidence fields as sensitive: keep them consistent and limit access so only the right roles can view audit details.
Step-by-step: set up a consent and evidence flow
Start by deciding what you will ask permission for. Consent isn’t just “notifications yes/no.” People often want receipts but not promotions. Write down your notification purposes in plain language, then map each purpose to the channels you plan to use.
Design consent screens by channel rather than one mixed prompt. Each screen should say what will be sent, and how to change it later. Keep wording specific: “Order receipts by email” is clearer than “Transactional messages.”
A practical flow looks like this:
- define purposes and which ones require opt-in (for example, marketing vs receipts)
- ask at the moment it makes sense (checkout for receipts, after onboarding for tips)
- choose safe defaults (off for marketing; on only for what’s needed to deliver the service)
- when a user changes a toggle, write an event record immediately (who, what changed, when, and where)
- put consent checks in the send path so nothing bypasses them, including admin tools and automated jobs
That event record is what proves consent later. Treat it like a bank receipt: append-only, and hard to edit after the fact. In AppMaster, teams often keep a current-state table for fast checks and write consent events through a Business Process so every update produces a matching audit entry.
Before release, test opt-out and edge cases. You want the same result whether the user changes settings on web, mobile, or through an account deletion flow. Pay special attention to:
- opting out on one device while logged in elsewhere
- changing a phone number or email address
- reinstalling the app (push tokens change)
- guest checkout vs logged-in users
- deleted or deactivated accounts (no sends, while keeping evidence as required)
Handling opt-out, changes, and account lifecycle
Opt-in is only half the job. People change their mind, switch devices, lose access to an email, or ask support to fix settings. If opting out is hard, users notice fast, and your “proof” starts to look shaky.
Make opt-out as easy as opt-in. Put it where people expect it: notification settings, the footer of marketing emails, and clear STOP instructions for SMS where required. Don’t hide it behind “contact support” or extra steps before someone can leave.
When you send a confirmation message, keep it minimal and use it only when it’s needed (or legally required). A single “You’re unsubscribed” email can be helpful. Repeated follow-ups like “Are you sure?” can feel like spam. For SMS, a single confirmation after STOP is often expected. For push, a quiet in-app status change is usually enough.
Account lifecycle is where many consent systems break. Plan for these cases early:
- Logged-out users: if someone unsubscribes from an email while not logged in, record it against the email address, not just a session.
- Deleted accounts: honor deletion requests, but keep a minimal do-not-contact record when allowed so they aren’t re-added later by accident.
- Merged accounts: never assume consent carries over; resolve conflicts with the most privacy-friendly choice.
- Device changes: a new phone creates a new push token; treat the token as technical data and the user’s push consent as the controlling rule.
Write down retention rules and apply them consistently. Keep consent logs long enough to answer complaints, audits, or chargebacks, but don’t keep more personal data than you need. If you must delete user data, decide what can be anonymized (for example, hashing an email) while keeping the event history useful.
Support-driven changes need a clear internal process. Limit who can edit consent, require a reason code (like “user requested via chat”), and record who made the change and when. In AppMaster, teams often model this with a small PostgreSQL table for consent events and a second table for support actions, then use a Business Process to apply changes and write an audit entry every time.
Common mistakes that break trust (and your audit trail)
Many teams add a consent toggle, then quietly break it later with shortcuts. The result is predictable: users feel tricked, and when you need proof of opt-in for notifications, the records don’t hold up.
One common trap is treating consent as a single global yes/no. Email, SMS, and push have different expectations and often different legal rules. A single flag like marketing_ok=true makes it too easy to send on a channel the user never agreed to.
Another trust killer is unclear choice design. Pre-checked boxes, tiny disclaimers, or controls that bundle multiple purposes (“Product updates and offers”) lead to confused users. Your database may say “consented,” but your support inbox will tell a different story.
The mistakes that most often break both trust and evidence are:
- saving only the latest consent state and deleting history
- not storing the exact consent text (and version) the user accepted
- logging “user opted in” without channel, timestamp, and source
- allowing manual campaigns that skip consent checks
- “fixing” a wrong setting by editing the database, which erases what happened
A typical failure looks like this: a user enables push during onboarding, later turns off marketing SMS in settings, then complains about an unwanted text. If your system overwrote the old record, you can’t show what they saw or when they opted out. Worse, you can’t prove SMS consent ever existed.
Treat consent like financial history: keep current state for fast checks, but don’t lose the past. Guardrails that help are simple: separate consent per channel and purpose, store a consent text ID and locale, force every send path through the same consent-check step, and write new events instead of editing old ones.
If you’re building in AppMaster, model consent events as their own table and enforce the check in a shared Business Process so automated notifications and operator actions follow the same rules.
Quick checks before you ship
Before sending real notifications, test your consent trail the way you’d test payments. If you can’t explain who opted in, to which channel, and under which wording, you’re betting trust on guesswork.
For each channel (email, SMS, push), make sure you can show the moment the user opted in and where it happened. “Where” should mean a specific screen or form name, plus whether it was signup, settings, checkout, or support.
Also make sure you can replay the consent wording. It’s not enough to store “marketing=true.” Store the text version (or template ID) and the language shown to the user. If copy changes later, you still need the old wording for the people who agreed to it.
A short pre-ship checklist that catches most issues:
- You can show timestamp, source (web, iOS, Android), and the UI location for each opt-in.
- You can retrieve the exact consent wording and language displayed at the time.
- The latest opt-out overrides everything and is reflected everywhere (including queued jobs).
- Support can answer “why did I get this?” in under 2 minutes from a single user view.
- Consent logs can’t be edited, and access is limited to authorized roles.
Run a drill: have a teammate opt in on web, opt out on mobile, then ask support for an explanation. If the answer requires digging through raw tables or multiple tools, users will feel that friction too.
If you build on AppMaster, treat consent evidence as its own data model and process, not a side effect. A dedicated consent log entity plus a simple internal view for support and auditors goes a long way, especially if you lock down who can access it.
Example scenario: one user, three channels, changing choices
Mina creates an account on your website to track orders. During signup, she sees separate choices for email, SMS, and push. She agrees to push notifications for order updates (once she installs the app), keeps marketing email off, and leaves SMS unchecked.
A week later, she installs the mobile app and signs in. The app asks for OS-level permission for push, and she accepts. This is where many teams get confused: OS permission isn’t the same as your business consent. You want both, recorded separately, so your opt-in proof stays clear during an audit.
Here’s how Mina’s story evolves over time, and what support should see as a clean timeline.
Timeline and evidence snapshots
- Web signup (Day 1)
You store what she chose (or didn’t choose) on web, plus the context of the request.
- Mobile install and push permission (Day 8)
You store the device token and the OS permission result, tied to Mina’s account, plus the exact prompt version shown in-app.
- Phone number change (Day 20)
She adds a new phone number for delivery coordination. You treat this as a new SMS destination and require a fresh SMS opt-in. Don’t carry over consent from the old number.
- SMS revoked (Day 35)
She replies STOP or toggles SMS off in settings. You store the opt-out event and stop sending immediately.
What the evidence record can look like
Below is a simplified example of the kind of audit trail support can read when Mina says, “I never agreed to SMS.”
[
{
"ts": "2026-01-02T10:14:22Z",
"user_id": "u_123",
"channel": "email",
"purpose": "marketing",
"action": "no_opt_in",
"capture": {"surface": "web_signup", "form_version": "signup_v3"},
"evidence": {"ip": "203.0.113.10", "user_agent": "Chrome"}
},
{
"ts": "2026-01-09T08:03:11Z",
"user_id": "u_123",
"channel": "push",
"purpose": "order_updates",
"action": "opt_in",
"capture": {"surface": "ios_app", "prompt_version": "push_prompt_v2"},
"evidence": {"device_id": "d_77", "os_permission": "granted", "push_token": "..."}
},
{
"ts": "2026-01-21T16:40:05Z",
"user_id": "u_123",
"channel": "sms",
"purpose": "delivery_updates",
"action": "opt_in",
"capture": {"surface": "account_settings", "form_version": "sms_optin_v1"},
"evidence": {"phone": "+15551234567", "verification": "code_confirmed"}
},
{
"ts": "2026-02-05T09:12:44Z",
"user_id": "u_123",
"channel": "sms",
"purpose": "delivery_updates",
"action": "opt_out",
"capture": {"surface": "sms_reply", "keyword": "STOP"},
"evidence": {"phone": "+15551234567"}
}
]
If you build on a platform like AppMaster, you can model consent events in the Data Designer and append them through Business Process flows whenever a user taps a toggle, confirms a code, or the app receives a permission result. What matters is that support can answer with dates, surfaces, and exact choices, not guesses.
Next steps: build it into your product workflow
Treat consent like a product feature, not a checkbox. The easiest way to keep proof of opt-in for notifications is to build it into the same workflow you use to send messages, handle support tickets, and run audits.
Start with a minimal model that separates current preference from historical evidence. A simple schema that works in most products:
- Users (identity and account status)
- ConsentPreferences (current on/off per channel, and per purpose if needed)
- ConsentEvents (every change with timestamps and context)
- MessageSends (each send attempt, including the consent decision at the time)
Decide who can see what. Consent evidence can include IP address, user agent, phone number, or other sensitive details, so lock it down with role-based access. Support often needs a consent timeline view, but only a smaller group should be able to export it.
Build a small internal view that answers one question fast: “Why did we contact this user?” Search by user, filter by channel, and make it easy to export a timeline when needed.
Then make consent checks automatic. Every send should call the same logic: check the latest ConsentPreferences, confirm there’s a valid ConsentEvent for that channel and purpose, and record the decision in MessageSends even when the send is blocked.
If you’re already using AppMaster, a practical pattern is to keep the consent tables in the Data Designer and put the consent gate into a shared Business Process that runs before any email, SMS, or push action. That way the rules stay consistent across web apps, backend jobs, and native mobile apps.
A simple rule holds up over time: if someone can send a notification without passing through the consent check, a bug will eventually ship.
FAQ
Consent means the person clearly agreed to receive a specific type of message on a specific channel. Proof-of-opt-in is the evidence you can pull up later that shows who agreed, what they agreed to, when it happened, and how it was captured.
Track consent separately for each channel and purpose because expectations and rules differ. A simple model is one consent record per user, per channel, per purpose, with a clear status and timestamps.
A basic consent record should include a user identifier, the destination when relevant (email or phone), the channel, the purpose, the current status, and when the status changed. Add the capture source and a consent text version so you can explain the decision later without guessing.
Store a consent text version or snapshot identifier tied to the exact wording and language shown at the time. When the copy changes, create a new version instead of editing the old one, so older opt-ins remain understandable.
OS permission only shows the device allowed notifications; it does not automatically mean the user agreed to your specific message purposes. Record OS permission as technical state and keep your own business consent for purposes like marketing versus order updates.
Default marketing to off and ask at a moment that makes sense, like after onboarding or in settings, rather than burying it in signup. Keep the explanation plain and specific so users know what they will receive and how to stop it.
Write an opt-out event immediately and make your send path check the latest opt-out before sending anything, including queued jobs. Keep the process simple so users can opt out without contacting support, and so support can see a clear timeline.
Treat a new email address or phone number as a new destination and require fresh consent for channels like SMS. Don’t assume consent carries over from the old value, because the proof needs to match the exact destination you messaged.
Keep a current-state table for fast checks, but also keep an append-only event log that records every change with timestamps and context. Avoid editing or deleting past events, because that’s what breaks your ability to explain “why did you message me?” later.
Model consent as structured data and write every toggle change through a single backend flow that always creates an audit event. In AppMaster, teams typically build Consent, ConsentTextSnapshot, and ConsentEvents in the Data Designer and enforce the consent gate in a shared Business Process before any email, SMS, or push send.


