---
title: "Update this account's mail preferences"
method: PUT
path: "/api/account/email-preferences"
tags: ["account"]
---

# Update this account's mail preferences

`PUT /api/account/email-preferences`

## Request

- Base URL: `https://app.nerdit.ai` (the document declares no server; this is the origin it was published from)
- URL: `https://app.nerdit.ai/api/account/email-preferences`
- Auth: none declared

Write the submitted fields, with provenance for what changed.

Only a *transition* of the marketing flag writes anything beyond the
row: re-sending the state a page is already showing must not append a
second grant, or the evidence table fills with events nobody caused.
The consent row is appended, never updated — a grant that rewrote the
previous revoke in place would destroy the only proof of what happened
between them.

``esp_state`` is marked ``pending`` on a transition only when a sync is
actually owed against a contact that exists: this deployment knows an
``esp_contact_id`` for the person. With no contact the audience entry
does not exist yet, and a ``pending`` would be a drain forever chasing
nothing — or, once a re-grant restores a contact id, one deleting the
audience member of somebody who just opted in.

No provider call happens here, for the rule the deletion route states
at length: a network hop under a row lock is how one slow vendor
becomes a stalled database.

## Request body

- EmailPreferencesRequest: The fields being changed; an absent field is left exactly as it is. Field-scoped on purpose — and deliberately the opposite of the console's single-flag toggles. With two independent flags, "send the full desired state" makes every tab a writer of *both*: a tab that loaded before the newsletter was turned off elsewhere would faithfully resubmit its stale copy of that flag and silently undo the change. A field that never travels cannot resurrect anything.
  - `marketing_opted_in` boolean, nullable
  - `product_notifications` boolean, nullable

## Response `200`

Successful Response

- EmailPreferencesView: This account's mail settings, and the address they apply to. Three fields, and the omissions matter as much as the fields: the unsubscribe token is a bearer credential and the ESP columns are an internal sync state, so neither is ever serialised here. A console that printed the token would turn every screenshot into a working unsubscribe link for somebody else's account.
  - `marketing_opted_in` boolean, required
  - `product_notifications` boolean, required
  - `email` string, required

## Other responses

- `401`: Unauthorized
- `403`: Forbidden
- `422`: Validation Error

---

[API](https://skmtc.dev/nerdit/apis/nerdit-cloud-web.md) · [All operations](https://skmtc.dev/nerdit/apis/nerdit-cloud-web/llms.txt) · [OpenAPI document](https://skmtc.dev/nerdit/apis/nerdit-cloud-web/revisions/06e3b44b58be?raw)
