AstroBaaS

Customers & accounts

Customer Preferences

Free — GPL coresize Mplanned, not built

Generated from docs/plan/core/customer-preferences/PLAN.md in the AstroBaaS repository. Nothing described below is implemented — it is the written plan for it.

Customer Preferences lets customers manage email frequency, communication channels (email/SMS), and content types (promotions, product updates, transactional) from their account profile or via email unsubscribe links.

The problem

Customers can’t control communication frequency; merchants email them daily and they unsubscribe.

What it does

  • Email opt-in/opt-out
  • SMS opt-in/opt-out
  • Preference categories: promotions, product updates, transactional
  • Frequency setting: daily digest, weekly, immediate (per category)
  • Preference center: self-serve page (accessible from email footer)
  • One-click unsubscribe: link in email, no login needed
  • Subscription management: re-subscribe after unsubscribe
  • Audit log: preference changes
  • Admin override: staff can mark unsubscribed (e.g., bounced)
  • Transactional emails: never gated (always sent)

What it deliberately does NOT do

Each boundary carries its reason. A boundary without a reason gets crossed by the next person who reads this.

  • Preference sync to email provider — email-marketing-automation-segmentation scope
  • Per-email-type unsubscribe (just this digest) — categories + frequency only
  • Preference history timeline — audit log shows current state
  • Preference prediction (likely to unsubscribe) — analytics/ML
  • List import/export with external tools — send-only

Data model

CustomerPreference: customer_id, email_opted_in, sms_opted_in, prefer_promotions, prefer_updates, prefer_transactional (always true), frequency, updated_at, opted_in_at, opted_out_at. UnsubscribeToken: customer_id, token_hash, created_at, expires_at. Migration: add preferences and unsubscribe_tokens tables.

API

  • GET /storefront/preferences
  • PATCH /storefront/preferences
  • GET /storefront/unsubscribe/{token}
  • POST /storefront/preferences/resubscribe
  • GET /admin/customers/{id}/preferences
  • PATCH /admin/customers/{id}/preferences

Every route added here must also appear in src/pages/openapi.json.ts — a test fails the build if it does not.

Admin

Customer detail: Communication Preferences card (email on/off, SMS, categories, frequency, last updated). Override button: mark unsubscribed or force re-subscribe (with audit).

The seam — why this is core

Core owns: CustomerPreference table, preference UI, unsubscribe token generation. Paid module owns: email-marketing-automation-segmentation reads preferences when sending campaigns.

Core owns the interface + honest preference manager; customer control is infrastructure, not a support commitment or credential.

Dependencies

  • customer-profiles
  • email-marketing-automation-segmentation (optional)

Acceptance checks

Each of these must be able to fail. Before claiming this is done, break the code deliberately and watch each one go red.

  • Unchecking Promotions causes email_opted_in=true but prefer_promotions=false
  • Sending promotion to all; email delivery skips prefer_promotions=false
  • Transactional (order confirmation) always sent even if email_opted_in=false
  • Clicking unsubscribe in email (one-click, token) lands on ‘Unsubscribed successfully’, no login
  • Clicking resubscribe in follow-up succeeds
  • Accessing preference center without login but with valid unsubscribe token; saving marks opted-in again
  • Frequency=weekly stored; email module batches sends
  • Staff marks customer bounced (admin override) causes email_opted_in=false; audit shows who did it

Risks

Unsubscribe token never expires. Token guessed. Wrong customer if token mismatch. Transactional flag configurable causes order emails not sent. Audit lacks field-level changes. Re-subscribe restores all preferences.

Commercial context

Suggested pricefree (core)
Rival anchorShopify: included; Magento: included

The anchor is what the nearest equivalent charges on Shopify or Magento today. It is context for a pricing decision, not the decision.


Generated from the commerce plan. See docs/COMMERCE-PLAN.md for the full catalogue and ../../AI-GUIDE.md for how to work on this repository.