A mailing list belongs to an inbox and holds the people who agreed to hear from you. Visitors join with a form on your site, your backend adds people it already has consent for, and every change is written to a consent log you can export.
A list’s confirmation and welcome mails go out through the inbox’s e-mail channel, from its address and SMTP account. Yuva sends nothing for a list that has no e-mail channel.
1. Set the sender identity
Mail with marketing content must say who sends it and how to reach them. In the panel: Settings → the inbox → Sender identity. Fill in the legal name, a postal address, a registration (a MERSİS number, trade register or VAT id) and a contact (an e-mail address, phone number or URL). Yuva puts these in the footer of the inbox’s list mail.
Over the API: PATCH /v1/inboxes/{inboxId} with a sender object.
2. Create a list
In the panel: Broadcasts, then New list. Pick the inbox and the e-mail channel that sends its mail.
| Setting (API field) | Meaning |
|---|---|
Name (name) |
Up to 100 characters, unique in the inbox. |
Description (description) |
Up to 500 characters, shown on forms and the preference page. |
Double opt-in (double_opt_in) |
On by default. Form subscribers confirm by e-mail before they are subscribed. |
Public (public) |
On by default. Offered on forms and the preference page. Private lists are filled by the API and your team only. |
Commercial (commercial) |
On by default. Marks marketing content; it is part of the consent export. |
Welcome (welcome) |
On by default. Sends a receipt with an unsubscribe link when someone subscribes. |
Sending channel (channel_id) |
An e-mail channel of the same inbox with an SMTP account. Without it, forms send nothing and subscriptions stay pending. |
Over the API: POST /v1/lists with inbox_id and name.
3. Add the form to your site
The form uses a chat channel of the same inbox: its public key and allowed origins decide where the form may appear. The list’s page in the panel has a Form tab with these snippets filled in for you.
<script src="https://support.example.com/yuva-subscribe.js" defer></script>
<yuva-subscribe
channel="yuva_pk_xxxxxxxxxxxxxxxx"
lists="LIST-ID"
privacy-url="https://www.example.com/privacy"
></yuva-subscribe>
Leave lists out to offer every public list of the inbox, with a checkbox each. The element asks for
an e-mail address and shows an unticked consent box. The visitor cannot subscribe without ticking
it, and the exact sentence they saw is stored as proof.
In a bundler project, run npm install useyuva and use one of these:
| Stack | Code |
|---|---|
| Any bundler | import "useyuva/subscribe", then the element above with a server attribute. |
| React | import { YuvaSubscribe } from "useyuva/react" and <YuvaSubscribe channel server lists={["LIST-ID"]} />. |
| Nuxt | modules: ["useyuva/nuxt"] and yuva: { server, channel } in nuxt.config, then <YuvaSubscribe :lists="['LIST-ID']" />. |
| shadcn/ui | npx shadcn@latest add https://useyuva.com/r/yuva-subscribe.json, then <YuvaSubscribe server channel lists={["LIST-ID"]} />. |
If your site sends a Content Security Policy, allow your Yuva server in script-src and
connect-src. For signed-in users, give the element an identity token with
setIdentityToken: it then shows a switch per list instead of the e-mail field, and subscribes at
once when the user’s address is verified.
4. What subscribers see
- They fill in the form. The answer is always the same, whether the address is new, already subscribed, unsubscribed or unknown, so the form tells nobody who is on your list.
- With double opt-in they get a mail with a link to
/s/…. Opening the page changes nothing; the button on it confirms. Then the welcome mail follows, if the list sends one. - Every list mail has a link to
/m/…, where the person unsubscribes from a list, from everything, or picks lists. Opening it changes nothing either. Mail programs that support one-click unsubscribe (RFC 8058) unsubscribe the list with a single request.
The pages and mails come in English, Turkish and German. Only the person can subscribe again after
unsubscribing: your team and your keys cannot, and an address that unsubscribed from everything,
reported a mail as spam or was erased after objecting is refused (409 opted_out). Pending
subscriptions are deleted after 30 days; unsubscribed ones after three years.
5. Add subscribers from your backend
Use this for people who agreed somewhere else, such as at sign-up. You declare how they agreed, and
Yuva stores it as proof. The key needs the lists:write scope.
curl -X POST https://support.example.com/v1/lists/LIST-ID/subscriptions \
-H "Authorization: Bearer $YUVA_API_KEY" \
-H "Idempotency-Key: signup-1234" \
-H "Content-Type: application/json" \
-d '{
"email": "ada@example.com",
"name": "Ada",
"consent": {
"confirmed": true,
"obtained_at": "2026-10-01T09:30:00Z",
"text": "Send me product news by e-mail.",
"page_url": "https://www.example.com/signup",
"reference": "order-1234"
}
}'
With confirmed: true the subscription is subscribed at once and the welcome mail goes out. With
confirmed: false it is pending and the person gets a confirmation mail first; use that when you
are not sure they agreed. 201 is a new subscription, 200 an address that was already on the list.
6. Export consent records
Open the list, then Export, or call GET /v1/lists/{listId}/consent-export (format=csv or json,
since for newer events). Each row is one event with the address, state, time, source, IP address,
page and the words the person saw. GET /v1/contacts/{contactId}/consent returns one person’s
records across lists, for an access request under GDPR Art. 15 or KVKK Art. 11. Cells that start
with =, +, - or @ are prefixed so spreadsheets do not run them.
Türkiye: İYS
Yuva does not connect to İYS. Export the list’s consent records and upload them to İYS yourself; registering as a sender there is your task too. This page is not legal advice.
Troubleshooting
- Form shows no list: the list is private or archived, or
listsnames an id that is not a public list of the channel’s inbox. - The form shows an error: the page’s origin is not one of the chat channel’s allowed origins.
- No confirmation mail: the list has no sending channel (the panel marks it), or the address already got 3 confirmations today.
409 email_channel_required: the channel is not an e-mail channel of the list’s inbox with an SMTP account.
Reference
<yuva-subscribe> attributes
| Attribute | Meaning |
|---|---|
channel |
The chat channel’s public key. Required. |
server |
Yuva server URL. Defaults to where yuva-subscribe.js came from. |
lists |
Comma-separated list ids. Empty: every public list. |
layout |
stacked (default) or inline. |
name |
off (default), optional or required: adds a name field. |
button-text |
The button’s label. |
consent |
Your own consent sentence. It is stored exactly as shown. |
privacy-url |
Link to your privacy notice, shown next to the box. |
locale, dir |
en or tr, and ltr or rtl. |
identity-token |
A token string; setIdentityToken is usually better. |
Style it with --yuva-accent, --yuva-on-accent, --yuva-radius, --yuva-font and
::part(field|button|consent|status).
Events
| Event | detail |
|---|---|
yuva-subscribe |
{ lists, confirmation }: the ids offered and whether a confirmation mail is expected. |
yuva-subscribe-error |
{ code }. |
React and shadcn: onSubscribe, onError. Nuxt: @subscribe, @error.
Endpoints
| Endpoint | Use |
|---|---|
GET, POST /v1/lists |
List and create lists (archived=true lists archived ones). |
GET, PATCH, DELETE /v1/lists/{listId} |
Read, change, archive (archived: true) or delete a list. |
GET /v1/lists/{listId}/growth |
New, confirmed and left per day, days=30 or 90. |
GET, POST /v1/lists/{listId}/subscriptions |
List (state, q) and add subscribers. |
GET, PATCH, DELETE /v1/lists/{listId}/subscriptions/{subscriptionId} |
Read with consent events, unsubscribe (state: unsubscribed), delete. |
POST /v1/lists/{listId}/subscriptions/{subscriptionId}/confirmation |
Mail the confirmation again. |
GET /v1/lists/{listId}/consent-export |
Consent records of a list. |
GET /v1/contacts/{contactId}/subscriptions |
A contact’s subscriptions. |
GET /v1/contacts/{contactId}/consent |
A contact’s consent records. |
GET /client/v1/channels/{channel_key}/lists |
Public lists and a form token, for the form. |
POST /client/v1/channels/{channel_key}/subscriptions |
Subscribe from a form. |
GET /client/v1/subscriptions, PUT, DELETE /client/v1/subscriptions/{listId} |
A signed-in user’s lists, subscribe, unsubscribe. |
Full request and response shapes are in the API contract. For your own
forms, useyuva has lists(), subscribe(), subscriptions(), subscribeList() and
unsubscribeList() on the headless client.
Scopes and events
| Name | Meaning |
|---|---|
lists:read |
Read lists, subscriptions, growth and consent exports. |
lists:write |
Create, change and delete lists; add, unsubscribe and delete subscribers; resend confirmations. |
subscription.created |
Webhook event: someone subscribed, in any state. |
subscription.updated |
Webhook event: the state changed; data.previous_state holds the old one. |
Keys made before these scopes existed do not get them.
Limits
| Limit | Value |
|---|---|
| Lists offered by one form | 10 |
| Form requests per IP address | 10 per hour |
| Form requests per channel | 600 per hour |
| Confirmation mails per address | 3 per day, and 1 per list in 10 minutes |
| Confirmation mails per channel | 1,000 per day |
| Confirmations resent from the panel or API | 3 per subscription and day, 10 in all |
| Form token | Valid from 2 seconds to 2 hours after it was issued |