Skip to content

Nieuwsbrief: aanmelden, bevestigen, versturen via Resend

De winkel verzamelt aanmeldingen met double opt-in en zet de bevestigde adressen bij de contacten van Resend (Audience → Contacts). Versturen gebeurt in Resend zelf (Broadcasts). Alles staat achter een schakelaar in de admin die standaard uit staat.

Resend heeft in november 2025 de losse "audiences" vervangen door één contactenlijst per team, met segmenten (groepen) en topics (voorkeuren). Sinds 2026-10-19 gebruikt de code de nieuwe API zonder audience-ID.

Code: backend/apps/users/newsletter.py (aanmelden, bevestigen, afmelden, synchronisatie), newsletter_views.py (endpoints), tasks.py (Celery), templates/email/newsletter_confirm.* (bevestigingsmail), frontend/components/newsletter/NewsletterSignup.tsx (footer), frontend/app/(shop)/checkout/page.tsx (vinkje). Tests: apps/users/tests/test_nieuwsbrief.py.

Eenmalig inrichten (Ivar)

  1. Sleutel met Full access. De synchronisatie gebruikt RESEND_API_KEY, dezelfde als voor de bestelmails, maar de contacten-API weigert een sleutel met alleen Sending access (401 "restricted to only send emails"; staging had zo'n sleutel tot 2026-10-19). Maak in Resend → API Keys een sleutel met Full access, zet die als RESEND_API_KEY op staging en productie en herstart backend en Celery. Admin → Instellingen → Nieuwsbrief toont "Resend niet ingesteld" alleen als de sleutel ontbreekt; een sleutel met te weinig rechten zie je pas in de Celery-log ("Resend weigerde … 401").
  2. Segment (aanbevolen). Resend → Audience → Segments → Create segment, naam "Nieuwsbrief". Open het segment en kopieer de ID uit de adresbalk of via het code-icoon (een reeks als 78261eea-…). Zet RESEND_SEGMENT_ID=<die ID> in het env-bestand van staging en productie en herstart backend en Celery, zoals bij elke secret-wijziging. Elk nieuw bevestigd adres komt dan ook in dat segment, zodat een broadcast alleen naar de nieuwsbrief kan. Zonder segment komen de adressen alleen bij "alle contacten"; dat werkt ook, maar dan gaat een broadcast naar iedereen in Resend.
  3. Controle. Meld een testadres aan, bevestig, en kijk in Resend → Audience → Contacts (en in het segment).
  4. Afzender. De bevestigingsmail komt van DEFAULT_FROM_EMAIL met de gewone Reply-To, net als de bestelmails. Niets extra's nodig.

Aanzetten (Eefje of Ivar)

Admin → Instellingen → Nieuwsbrief → "Aanmelden voor de nieuwsbrief tonen" → Opslaan. Vanaf dan:

  • de footer toont "Blijf op de hoogte" met een e-mailveld en de knop Aanmelden;
  • de checkout toont onder het e-mailadres het vinkje "Houd me op de hoogte van nieuwe producten en acties", standaard uit. Aangevinkt meldt het adres aan bij het afrekenen.

Uitzetten haalt het veld en het vinkje weg. Bevestigen en afmelden via links uit eerdere mails blijven werken.

Wat de klant meemaakt

  1. Aanmelden via footer of checkout. De klant ziet "Bijna klaar: check je mail en bevestig je aanmelding." Ook een adres dat al bekend is krijgt dit antwoord, zodat niemand kan raden welke adressen bestaan.
  2. Bevestigingsmail "Bevestig je aanmelding" met de knop Ja, meld me aan. De link is 7 dagen geldig. Zonder klik gebeurt er niets; het adres staat dan als "wachtend" in de admin en komt niet in Resend.
  3. Na de klik komt de klant op de homepage met "Je aanmelding voor de nieuwsbrief is bevestigd." Het adres gaat direct naar de contacten van Resend.
  4. Afmelden via de link onderaan de bevestigingsmail of onderaan een nieuwsbrief. De klant ziet "Je bent afgemeld voor de nieuwsbrief." en staat in Resend op unsubscribed. Opnieuw aanmelden vraagt opnieuw om bevestiging.

Een verlopen bevestigingslink geeft "Deze bevestigingslink is verlopen. Meld je opnieuw aan."

Een nieuwsbrief versturen (Eefje)

  1. Resend → Broadcasts → Create broadcast.
  2. Kies bij de ontvangers het segment "Nieuwsbrief" (stap 2 van het inrichten), niet alle contacten.
  3. Schrijf de mail in de editor. Zet onderaan altijd een afmeldlink met de samenvoegcode {{{RESEND_UNSUBSCRIBE_URL}}}; Resend vult die per ontvanger in en verwerkt het afmelden zelf bij het contact. (Onze eigen afmeldlink staat alleen in de bevestigingsmail.)
  4. Stuur eerst een test naar jezelf (Send test), controleer de afmeldlink, en verstuur dan.

Richtlijn: af en toe (nieuwe producten, een actie, het seizoen), niet wekelijks. Elke mail die de klant niet verwacht, kost afmeldingen.

Cijfers

Admin → Instellingen → Nieuwsbrief toont: aangemeld (bevestigd en niet afgemeld), wachtend op bevestiging, afgemeld, en het aantal nieuwe bevestigingen in de laatste vier weken. Dezelfde cijfers staan in Resend onder Audience → Contacts; lopen ze uiteen, zie "Storingen".

Achter de schermen

Wat Waar Wanneer
Aanmelding opslaan en bevestigingsmail POST /api/newsletter/subscribe/ direct; 10 per uur per IP; 403 als de schakelaar uit staat
Bevestigen GET /api/newsletter/bevestigen/<token>/ klik in de mail; token 7 dagen geldig
Afmelden GET /api/newsletter/afmelden/<token>/ klik in de mail; token verloopt niet
Contact naar Resend zetten Celery-taak newsletter.sync direct na bevestigen of afmelden
Inhaalslag Celery Beat newsletter.sync_pending, elk uur op :40 alles wat bevestigd is en nog niet (opnieuw) gesynchroniseerd
Cijfers GET /api/admin/newsletter/stats/ bij openen van de instellingen

Resend-API: POST /contacts met {email, unsubscribed} en, als RESEND_SEGMENT_ID is gezet, segments: [{id}]; bestaat het contact al, dan PATCH /contacts/<email> met alleen unsubscribed (een bestaand contact komt dus niet alsnog in het segment; zet het dan met de hand in Resend).

Het nieuwsbriefvinkje in het klantprofiel (UserProfile.newsletter) staat voor nieuwe accounts standaard uit en is een voorkeur, geen aanmelding: het zet niemand in Resend. Klanten die vóór 2026-10-18 een account maakten, hadden dat vinkje ongemerkt aan; zij staan niet in Resend en moeten zich gewoon aanmelden.

Storingen

  • "Resend niet ingesteld" in de admin: RESEND_API_KEY ontbreekt of de container is niet herstart. Aanmeldingen en bevestigingen gaan gewoon door; de inhaalslag zet ze in Resend zodra de sleutel er is.
  • Bevestigd in de admin, niet in Resend: kijk in de Celery-logs naar "Resend weigerde" (401 "restricted to only send emails": de sleutel heeft geen Full access; anders een verkeerde sleutel of segment-ID) of "Resend niet bereikbaar". De uurlijkse inhaalslag probeert het opnieuw; na herstel loopt het vanzelf bij. Meteen inhalen: docker compose exec -T backend python manage.py shell -c "from apps.users.newsletter import sync_pending; print(sync_pending())".
  • Wel bij de contacten, niet in het segment: het contact bestond al toen de synchronisatie liep (bijvoorbeeld via een eerdere mail); voeg het in Resend met de hand aan het segment toe.
  • Geen bevestigingsmail: zelfde controle als bij bestelmails (Resend → Emails, en de spamfolder). De mail gaat alleen bij een geldig adres en met de schakelaar aan.
  • Klant wil eruit zonder link: zet in Resend het contact op unsubscribed. In de admin is er geen knop; de uurlijkse inhaalslag overschrijft de Resend-status niet zolang de aanmelding bij ons niet verandert.