Markus Michalski FAQ

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

Deutsche Version

Back to overview

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 /confirm and /unsubscribe is 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 /confirm and /unsubscribe gives 422, an unknown token in the URL of /shop/newsletter-preferences/{token} gives 404.

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-Payload header, 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:

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.