> For the complete documentation index, see [llms.txt](https://docs.podplay.app/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.podplay.app/api/use-cases/use-case-marketing-email-sms-consent.md).

# Use Case: Marketing Email/SMS Consent

Collect and manage customer consent for marketing email and SMS through the PodPlay API while respecting tenant configuration and customer choice.

### Overview

Marketing consent records whether a customer agrees to receive promotional email or SMS messages from a club. These preferences do not control transactional messages such as booking confirmations, receipts, login codes, or password resets.

PodPlay stores email and SMS consent separately:

* `emailMarketingOptIn`
* `smsMarketingOptIn`

Each preference also has a last-changed timestamp. Integrators should collect a separate, explicit choice for each enabled channel and must not silently opt customers in.

### Authentication & Authorization

Server-to-server integrations should authenticate with an API key:

```
x-api-key: <write-capable-api-key>
```

An API key is authenticated as its associated PodPlay user and has the same authorization as that user. It does not grant a special marketing-consent privilege of its own.

* A customer session can read and update that customer's own preferences.
* A write-capable API key whose associated user is a tenant Admin can read and update another customer's preferences.
* A read-only API key cannot create a customer or update preferences; writes return `403 Forbidden`.
* A key associated with a user who has no authority over the target customer also returns `403 Forbidden`.
* Creating or issuing API keys is a PodPlay Admin operation. A tenant Admin can hold a key, but cannot mint one.

{% hint style="warning" %}
Do not assume every integration key can manage every customer. Confirm the key's associated user, role, tenant, and area scope in the sandbox. A tenant Admin key is not the same as a customer session or a read-only key.
{% endhint %}

### Tenant Configuration Discovery

Tenant settings tell an integration **whether** to render a consent choice and **what copy** to show. They do not record customer consent, and they do not supply a default opt-in value for users created through `POST /users`.

Before showing consent choices, read:

```
GET /tenants/current/settings
```

The relevant settings are:

| Setting ID                           | Purpose                                                                                                                                            |
| ------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------- |
| `company.emailMarketingOptIn`        | Determines whether the email marketing consent channel is enabled                                                                                  |
| `company.smsMarketingOptIn`          | Determines whether the SMS marketing consent channel is enabled                                                                                    |
| `company.emailMarketingOptInDefault` | Used only by PodPlay's signup UI to set the initial Email checkbox state; it is not an API consent default and does not establish customer consent |
| `company.emailMarketingOptInText`    | Tenant-configured customer-facing email consent copy                                                                                               |
| `company.smsMarketingOptInText`      | Tenant-configured customer-facing SMS consent copy                                                                                                 |

There is no SMS equivalent of `company.emailMarketingOptInDefault`.

```bash
curl -sS \
  -H "x-api-key: <write-capable-api-key>" \
  "https://<your-site>/apis/v2/tenants/current/settings"
```

The response is a collection whose `items` contain `id` and `value`. Use `company.emailMarketingOptIn` and `company.smsMarketingOptIn` to decide which choices to display, and show `company.emailMarketingOptInText` / `company.smsMarketingOptInText` as the matching customer-facing copy.

{% hint style="info" %}
Do not treat any tenant setting as proof of customer consent. `company.emailMarketingOptInDefault` never applies to API-created users and does not change stored preferences when a marketing field is omitted from `POST /users`.
{% endhint %}

Do not change tenant-wide marketing settings as part of a customer signup or preference-management workflow. Tenant configuration is managed separately by authorized PodPlay staff.

### Recommended Signup Flow

1. Read the tenant settings.
2. Show only enabled consent channels.
3. Display the tenant-configured consent text for each channel.
4. Present separate, explicit choices for email and SMS.
5. Submit the customer's selected booleans when creating the account.
6. Store the returned customer ID.
7. For later changes, let the customer manage their own preferences where possible. Otherwise, update only after an explicit customer request and with authorization valid for that customer record.
8. Read the preferences when displaying or reconciling the current state.

#### Example — Create a Customer With Explicit Choices

```
POST /users
```

Both marketing fields are optional booleans.

Omitting `emailMarketingOptIn` and/or `smsMarketingOptIn` does **not** apply a tenant-level marketing-consent default. PodPlay stores an omitted field as `false`, and the corresponding `emailMarketingOptInUpdatedAt` or `smsMarketingOptInUpdatedAt` timestamp remains unset. Sending `false` explicitly also stores `false`, but records that timestamp because it is a submitted choice. `company.emailMarketingOptInDefault` does not affect API-created users.

```bash
curl -sS -X POST \
  -H "x-api-key: <write-capable-api-key>" \
  -H "Content-Type: application/json" \
  "https://<your-site>/apis/v2/users" \
  -d '{
    "email": "customer@example.test",
    "firstName": "Test",
    "lastName": "Customer",
    "emailMarketingOptIn": true,
    "smsMarketingOptIn": false
  }'
```

When the associated API-key user is authorized to create customers, PodPlay creates the customer and initializes marketing preferences from any **submitted** booleans. Fields that were omitted are stored as `false` without a last-changed timestamp. Use a unique disposable address when testing in a sandbox. Account creation still depends on Firebase user creation; a `422` from `POST /users` can mean a signup-service failure rather than a consent-validation failure.

{% hint style="warning" %}
If a channel is disabled in tenant settings, PodPlay ignores that channel's signup value. It does not treat the submitted boolean as accepted consent. The stored preference remains at its existing or default opted-out state.
{% endhint %}

Customer-app sign-in (password, passwordless, OTP, or social) can also create an account and record consent at that moment. That is a browser/session flow, not this API-key integration, and it is out of scope for this guide. After the customer exists, use `GET`/`PATCH /users/{userId}/preferences` as below.

### Read Current Preferences

```
GET /users/{userId}/preferences
```

```bash
curl -sS \
  -H "x-api-key: <write-capable-api-key>" \
  "https://<your-site>/apis/v2/users/<customer-id>/preferences"
```

Example response:

```json
{
  "id": "0198f177-29f0-7123-8c11-6f6d951da001",
  "showUserNameOnEvents": true,
  "showUserRatingOnEvents": true,
  "chatOptIn": true,
  "emailMarketingOptIn": true,
  "emailMarketingOptInUpdatedAt": "2026-09-02T16:00:00.000Z",
  "smsMarketingOptIn": false,
  "smsMarketingOptInUpdatedAt": "2026-09-02T16:00:00.000Z"
}
```

The `emailMarketingOptInUpdatedAt` and `smsMarketingOptInUpdatedAt` fields record when each value last changed. A timestamp can be absent when that channel has never received an explicit value.

### Update a Preference

```
PATCH /users/{userId}/preferences
```

The marketing fields are optional booleans, so send only the preference the customer asked to change.

#### Example — Opt Out of Email Marketing

```bash
curl -sS -X PATCH \
  -H "x-api-key: <write-capable-api-key>" \
  -H "Content-Type: application/json" \
  "https://<your-site>/apis/v2/users/<customer-id>/preferences" \
  -d '{
    "emailMarketingOptIn": false
  }'
```

A successful request returns `200 OK` with the complete preferences object. When the value changes, PodPlay updates that channel's timestamp and sends `marketing_preferences_updated` to Segment-connected marketing tools.

After a write, read the preferences again when the integration needs to confirm or reconcile the persisted state.

#### Disabled Channel Behavior

If the matching tenant channel is disabled, PodPlay removes that field from the update before saving. The request can still return `200 OK`, but the preference and timestamp remain unchanged. Treat the returned response as the source of truth rather than assuming the submitted value was accepted.

### Phone Verification and SMS Consent

The phone-verification flow can accept `smsMarketingOptIn` at:

```
PUT /users/{userId}/phone-number
```

Use that field only while completing the API's phone-number verification flow. For a later standalone SMS consent change, use:

```
PATCH /users/{userId}/preferences
```

This keeps general preference changes in the endpoint that applies tenant-channel gating, updates the consent timestamp, and emits the marketing preference event.

### Consent Guidance

#### Do

* Read tenant settings before rendering consent choices.
* Show only enabled channels and their configured consent text.
* Keep email and SMS choices separate.
* Record the customer's explicit selection during signup.
* Honor opt-outs immediately.
* Use the returned preference state and timestamps for reconciliation.
* Use sandbox-only customers and credentials while testing.

#### Do Not

* Do not silently opt a customer in.
* Do not interpret tenant settings (including `company.emailMarketingOptInDefault`) as proof of consent or as a default opt-in for API-created users.
* Do not send a consent value for a channel the tenant has disabled.
* Do not update another customer without explicit instruction and valid authorization.
* Do not expose API keys in browser code, logs, examples, or documentation.
* Do not use the phone-verification endpoint as a general-purpose preferences endpoint.

### Practical Integrator Scenario

An external booking or signup experience creates a new customer. Before submission, it fetches tenant settings, conditionally renders separate email and SMS consent checkboxes with the configured text, and creates the customer with the selected values.

Later, the experience reads the customer's preferences to display the current state. It directs the customer to manage changes through their own authenticated preferences whenever possible. If the integration performs a requested change, it uses authorization valid for that customer record, patches only the requested field, and confirms the returned state.

### Troubleshooting

| Result                                         | Meaning and next step                                                                                                                                                                                                                       |
| ---------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `200 OK`, but the submitted value is unchanged | The marketing channel may be disabled. Re-read tenant settings and use the returned preference as the source of truth.                                                                                                                      |
| `403 Forbidden`                                | The API key is read-only, or its associated user lacks authority over the target customer. A tenant Admin write key is expected to succeed; a customer-scoped or read-only key is not. Do not retry with broader credentials automatically. |
| `404 Not Found`                                | The customer ID is invalid, or no preferences record exists for that customer. Create the customer first, or confirm that signup finished creating preferences.                                                                             |
| `422 Unprocessable Entity` during signup       | Review structured validation errors when present. A generic signup failure can also mean the account-creation service is unavailable.                                                                                                       |

### Marketing Preferences API — Technical Specification

For the complete request and response schemas, refer to the API specification:

## GET /users/{userId}/preferences

>

```json
{"openapi":"3.0.0","info":{"title":"PodPlay Inc API","version":"2.0"},"tags":[{"name":"Users","description":"User management, authentication, profiles, and account operations."}],"servers":[{"url":"/apis/v2","description":"Current Server"}],"security":[{"bearer":[]}],"components":{"securitySchemes":{"bearer":{"scheme":"bearer","bearerFormat":"JWT","type":"http"}}},"paths":{"/users/{userId}/preferences":{"get":{"operationId":"UserPreferencesController_getPreferences","parameters":[{"name":"userId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"Get the user preferences"}},"tags":["Users"]}}}}
```

## PATCH /users/{userId}/preferences

>

```json
{"openapi":"3.0.0","info":{"title":"PodPlay Inc API","version":"2.0"},"tags":[{"name":"Users","description":"User management, authentication, profiles, and account operations."}],"servers":[{"url":"/apis/v2","description":"Current Server"}],"security":[{"bearer":[]}],"components":{"securitySchemes":{"bearer":{"scheme":"bearer","bearerFormat":"JWT","type":"http"}},"schemas":{"UserPreferencesPatchableDto":{"type":"object","properties":{"showUserNameOnEvents":{"type":"boolean"},"showUserRatingOnEvents":{"type":"boolean"},"chatOptIn":{"type":"boolean"},"emailMarketingOptIn":{"type":"boolean"},"smsMarketingOptIn":{"type":"boolean"}},"required":["showUserNameOnEvents","showUserRatingOnEvents","chatOptIn","emailMarketingOptIn","smsMarketingOptIn"]}}},"paths":{"/users/{userId}/preferences":{"patch":{"operationId":"UserPreferencesController_patchPreferences","parameters":[{"name":"userId","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserPreferencesPatchableDto"}}}},"responses":{"200":{"description":"Update the user preferences"}},"tags":["Users"]}}}}
```


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.podplay.app/api/use-cases/use-case-marketing-email-sms-consent.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
