Newsletter Management: Shop API
Shop API of the newsletter plugin for Sylius - use signup, confirmation, unsubscribe, preferences and categories headless, with curl examples, parameters and error format
Newsletter Management: Shop API
This documentation always describes the latest published version.
The plugin is dual-mode: signing up, confirming, unsubscribing and managing a subscription work in the Twig storefront and headless through the Shop API, and both paths run through the same domain service (NewsletterSubscriptionManager). The API code contains no business logic of its own.
graph LR
A["Storefront form"] --> C["NewsletterSubscriptionManager"]
B["Shop API request"] --> C
C --> D{"Newsletter enabled for the channel?"}
D -->|"No"| E["404"]
D -->|"Yes"| F["PRE_SUBSCRIBE event"]
F -->|"rejected"| G["422"]
F -->|"accepted"| H["Subscription + confirmation mail"]
Endpoints
All paths sit under your Sylius project's Shop API prefix (default: /api/v2), for example /api/v2/shop/newsletter-subscriptions. Requests with a body (POST, PUT) send Content-Type: application/json.
| Method | Path | Description |
|---|---|---|
POST |
/shop/newsletter-subscriptions |
Sign up. Answers 202 with no body, whether the address is new, still unconfirmed or already confirmed |
POST |
/shop/newsletter-subscriptions/confirm |
Confirms by token (double opt-in). 204 on success, 404 for an unknown or already unsubscribed token |
POST |
/shop/newsletter-subscriptions/unsubscribe |
Unsubscribes by token. 204 on success (also if already unsubscribed), 404 for an unknown token |
GET |
/shop/newsletter-preferences/{token} |
Reads name, categories and tracking consent of a confirmed subscription |
PUT |
/shop/newsletter-preferences/{token} |
Changes name, categories and tracking consent. The e-mail address can never be changed |
GET |
/shop/newsletter-categories |
The categories the current channel offers, ordered by position |
GET |
/shop/newsletter-categories/{id} |
A single category (404 if disabled or restricted to another channel) |
The token in
/confirmand/unsubscribeis the 64-character token from the link in the respective mail. For/shop/newsletter-preferences/{token}it is the unsubscribe token that every newsletter carries in its footer. A token of another length in the body of/confirmand/unsubscribegives422, an unknown token in the URL of/shop/newsletter-preferences/{token}gives404.
Channel
The endpoints always work for the channel Sylius resolves for the request (usually from the hostname). If the newsletter is not enabled for that channel, signing up answers 404, and the category list is limited to that channel's categories regardless. You set this up under Configuration.
Signing up
Body
| Field | Type | Required | Description |
|---|---|---|---|
email |
string | yes | Valid e-mail address, at most 255 characters. Stored in lowercase |
consent |
boolean | yes | Must be true, otherwise 422 |
firstName |
string | no | At most 100 characters |
lastName |
string | no | At most 100 characters |
categories |
string | no | Category codes. Empty means the general newsletter, i.e. everything. Unknown, disabled or codes the channel does not offer are dropped |
trackingConsent |
boolean | no | Consent to personal tracking, default false. Only counts if the channel's tracking mode is Personal |
Example
curl -X POST https://shop.example.com/api/v2/shop/newsletter-subscriptions \
-H "Content-Type: application/json" \
-d '{
"email": "anna.example@example.com",
"firstName": "Anna",
"lastName": "Example",
"categories": ["new-arrivals", "tutorials"],
"consent": true
}'
Response: 202 Accepted with no body. With double opt-in enabled (the default) the address now receives the confirmation mail. If the address is already confirmed, the request changes nothing about the subscription and the address gets the "already subscribed" mail instead. That way the API cannot be used to find out who is on the list.
Mails triggered by a public form are throttled per address: at most one such mail goes to the same address within 10 minutes. The response is still
202.
Confirming and unsubscribing
The mail links to the storefront page. A headless shop reads the token from that link (path /newsletter/confirm/{token} or /newsletter/unsubscribe/{token}) and sends it to the API:
curl -X POST https://shop.example.com/api/v2/shop/newsletter-subscriptions/confirm \
-H "Content-Type: application/json" \
-d '{"token": "3f9a1c0e5b7d42a8c6e1f0b9d8a7c6e5f4b3a29180716253443526170809a0b1"}'
Response: 204 No Content. Unsubscribing uses the same call against /shop/newsletter-subscriptions/unsubscribe.
Subscriber preferences
curl https://shop.example.com/api/v2/shop/newsletter-preferences/TOKEN \
-H "Accept: application/json"
The response contains email (read-only), firstName, lastName, trackingConsent and categories (a list of codes). The token itself never appears in the response. For unknown tokens and for subscriptions that are not confirmed yet the API answers 404.
curl -X PUT https://shop.example.com/api/v2/shop/newsletter-preferences/TOKEN \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{
"firstName": "Anna",
"lastName": "Example",
"categories": ["tutorials"],
"trackingConsent": false
}'
The response shows what was actually stored: unknown or disabled category codes are missing from it. An email field in the request is ignored.
| Field | Behavior on PUT |
|---|---|
firstName, lastName |
Replaced (at most 100 characters). A missing field clears the name |
categories |
Replaces the previous selection. An empty list means the general newsletter |
trackingConsent |
Omitted (null) leaves the consent unchanged. false withdraws it and deletes the personal tracking data. true grants it, but only if the channel is in Personal mode |
Categories
curl https://shop.example.com/api/v2/shop/newsletter-categories \
-H "Accept: application/json"
Each category contains id, code, position, name and description; name and description come in the request's language. Only enabled categories appear, and only those offered either to all channels or to the current channel.
Error format
| Status | When |
|---|---|
202 |
Signup accepted (says nothing about the state of the address) |
204 |
Confirmation or unsubscribe succeeded |
404 |
Newsletter not enabled for the channel, unknown token or category not available |
422 |
Validation error (invalid e-mail address, consent not true, token not 64 characters, fields too long) or rejection by a PRE_SUBSCRIBE listener |
A rejection by a spam-protection plugin also answers 422; the message is the reason the listener passed in (a translation key or the default text "Signup rejected."). How to write such a listener is covered under Extending.
The ALTCHA plugin protects signing up through the API as well. The proof then goes into the
X-Altcha-Payloadheader, see ALTCHA: Shop API.
Not available through the API
The Shop API only covers what a customer does themselves. The following exists in the admin (Twig) only:
- Creating, editing, sending, scheduling and duplicating newsletters
- The delivery log and the statistics
- Subscriber management and the CSV export
The confirmation mail links to the storefront page. A purely headless shop has to rewrite that link in the mail template to point to its own frontend and send the token from there to the API.