sally docs
This page is rendered from the canonical GitHub docs. Edit on GitHub.

Sally Marketing architecture notes

Sally Marketing is intentionally a small native Sally package, not a Mautic clone.

Product scope

Sally should own the marketing control plane:

  • contacts and stable identity
  • custom contact field definitions and bounded profile snapshots
  • append-only submissions and events
  • GDPR consent and double opt-in
  • audiences and materialized segment membership
  • newsletter/message approvals
  • sender configuration for SMTP/AWS SES
  • delivery ledger and worker-driven sends
  • content gate access policies
  • simple campaign sequences
  • CRM handoff

Sally should not become:

  • a WordPress form builder
  • a full email service provider/MTA
  • a Mautic adapter
  • a Postal deployment wrapper
  • a schema-mutating custom-field CRM
  • a high-volume analytics warehouse inside the primary OLTP tables

Scalable profile model

Core contact identity lives on MarketingContact. Custom fields are validated by MarketingContactFieldDefinition and stored as a bounded JSON profile snapshot on MarketingContact.fields. Raw submitted values remain append-only in MarketingSubmission.payload/fields style data so profile state can be audited or rebuilt.

Segment rules are materialized into MarketingAudienceMember for MVP usability. Sally does not automatically index every custom JSON key. Future high-volume segmentation should add a controlled derived attribute index only for fields marked isSegmentable.

Worker model

The web/API process is not the sending plane. Marketing workers materialize approved sends, execute campaign runs, and claim queued deliveries with a guarded QUEUED -> SENDING transition. Delivery rows track attempts, last attempt, provider id, and failure reason. This keeps synchronous API requests small and protects against duplicate sends from concurrent workers.

Future messaging channels

Email is the first supported channel through SMTP and Amazon SES. SMS, WhatsApp, and Telegram should be modeled as future provider connectors behind the existing message/channel/delivery ledger. Consent purpose, sender identity, rate limits, opt-out handling, and delivery events must be channel-specific before enabling production sends.

Do not add channel-specific tables until the connector needs them. Prefer provider-neutral delivery/event records first, with small provider-specific metadata payloads and explicit consent/opt-out policy per channel.