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

Sally Marketing package

Sally Marketing is a native Sally add-on for simple, API-first, GDPR-first marketing automation.

It is intentionally not a Mautic adapter and not a Mautic clone. The package focuses on the 20% of marketing automation people actually use: lead capture, contacts, configurable contact fields, consent, audiences, newsletters, content gates, and simple campaigns.

Edition availability

Marketing is included in Community by default via feature key:

marketing.core

Paid Sally editions are only unlocked with licenses minted by the Sally license server. Environment variables do not unlock Pro, Business, or Enterprise features.

Product principles

  • No Mautic adapter in the core product.
  • No Postal dependency.
  • Email delivery should use provider connectors such as SMTP and Amazon SES. Sender setup distinguishes configuration checks from real test emails so admins can validate delivery before newsletter approvals.
  • Websites own form rendering. Sally receives submissions via API.
  • WordPress integrations should prefer existing form plugins such as Contact Form 7.
  • Content access is separate from marketing consent.
  • Mass sends are approval-oriented and worker-driven.
  • Email authoring opens as a full-section workspace from the message list rather than a modal. The UX follows the Easy Email/GrapesJS mental model without depending on either product: a cleaned-up native editor with one explicit Save email action next to Back to messages, message settings, block palette/inspector, one final-email canvas/presentation surface for preview/drop/order/reorder, hover/selection reorder handles, and code/MJML escape hatch. Changes are local draft state until the user clicks Save email, so leaving the editor can discard unsaved changes. It supports debounced live preview rendering, plain text, raw HTML, a small MJML-compatible subset, native blocks (text, button, image, divider, spacer, custom MJML), starter/hero/newsletter/re-engagement templates, preview sample variables, test sends, and personalization variables like {{firstName}}. The text block embeds the same slash-command TipTap editor used for task descriptions, so headings, lists, quotes, links, bold/italic, and similar formatting live inside one flexible text element. When text is highlighted in email text blocks, a floating formatting toolbar appears with bold, italic, underline, font size, font family, and color controls. Non-text blocks expose matching inspector controls for width, margin, padding, border radius, border weight/color, alignment, foreground/background color, and height where relevant. Spacing controls are split into top, bottom, left, and right fields for margin and padding. Numeric-only values are interpreted as pixels automatically (1212px), while responsive width defaults stay percentage-based (100%) unless a fixed numeric width is entered. Emails also support styled sections with nested blocks, a global body background that can be set to transparent/no background, a responsive email width, and desktop/tablet/mobile canvas preview modes. The builder and renderer read Sally CI defaults for body background, email width, text color, button colors, and heading/text/button font stacks when a message or block has no local override. Email image uploads use the same browser-side compression flow as task/project description images, capped to a 1000px longest side for compact email assets, before storing the optimized asset under Sally-managed uploads. Marketing also includes a Media library for uploading, browsing, copying URLs, and renaming managed assets. Email-builder values remain explicitly overridable per message/block. The root email container is layout-only and has no visual design; all visible background, padding, borders, and radius belong to sections or blocks. Canvas edits are captured from the editable preview frame and committed back into the message HTML for saving.
  • API and MCP tools are first-class surfaces, not UI automation shortcuts.

Initial web surfaces

The package adds:

/marketing
/marketing/overview
/marketing/contacts
/marketing/fields
/marketing/audiences
/marketing/messages
/marketing/campaigns
/marketing/senders
/marketing/gates
/marketing/approvals

The UI follows the current Sally shell/theme and uses the left sidebar to reveal Marketing islands.

API foundation

Current foundation routes:

GET  /marketing
GET  /marketing/overview

GET  /marketing/website-tokens
POST /marketing/website-tokens
POST /marketing/website-tokens/:tokenId/revoke

GET  /marketing/fields
POST /marketing/fields
PATCH /marketing/fields/:fieldId

GET  /marketing/contacts
POST /marketing/contacts
GET  /marketing/contacts/:contactId/timeline

POST /marketing/submissions
POST /marketing/embed/submissions

GET  /marketing/audiences
POST /marketing/audiences
PATCH /marketing/audiences/:audienceId
POST /marketing/audiences/:audienceId/sync

GET  /marketing/senders
POST /marketing/senders
DELETE /marketing/senders/:senderId

GET  /marketing/messages
POST /marketing/messages
PATCH /marketing/messages/:messageId
POST /marketing/messages/:messageId/preview
POST /marketing/messages/:messageId/test
POST /marketing/messages/:messageId/propose-send

GET  /marketing/campaigns
POST /marketing/campaigns
PATCH /marketing/campaigns/:campaignId

GET  /marketing/content-gates
POST /marketing/content-gates
POST /marketing/content-gates/:gateKey/check
POST /marketing/embed/content-gates/:gateKey/check

GET  /marketing/deliveries
GET  /marketing/deliveries/:deliveryId
POST /marketing/deliveries/:deliveryId/retry

GET  /marketing/approvals
POST /marketing/approvals/:approvalId/approve

POST /marketing/webhooks/aws-ses
GET  /marketing/open/:deliveryId.gif
GET  /marketing/unsubscribe?token=...

Content gates and GDPR

Content gates must not blindly bundle page access with newsletter consent.

Sally separates:

MarketingConsent
Content gate access policy

Supported unlock policy foundation:

  • after submission
  • after email verification
  • confirmed marketing consent
  • audience member
  • manual/API grant

Submissions to a gate with AFTER_SUBMISSION can return a signed opaque accessToken. The website can store it as a first-party session/cookie or keep it request-local. Newsletter consent must remain explicit and separately recorded.

When a submission creates pending consent, Sally generates a 14-day double-opt-in token and returns confirmationUrl. If an enabled SMTP/AWS SES Marketing sender exists, Sally also sends a confirmation email by default. Pass consent.sendConfirmation: false to suppress automatic DOI email sending, or consent.senderId to force a specific sender.

Custom contact fields

Sally avoids Mautic-style dynamic contact columns. The contact table keeps only stable identity fields. Custom marketing profile data is stored as a bounded JSON profile snapshot on MarketingContact.fields, while MarketingSubmission.fields remains append-only history for traceability and rebuilding derived profile state.

Admins can define workspace-scoped field definitions with these types:

TEXT
TEXTAREA
NUMBER
BOOLEAN
DATE
EMAIL
PHONE
URL
COUNTRY
LANGUAGE
SINGLE_SELECT
MULTI_SELECT

Guardrails:

50 active custom fields per workspace
100 total custom fields including archived fields
100 select options per field
100 characters per option label
1,000 characters per text-like value
5,000 characters per textarea value
32 KB total custom field JSON per contact payload

Field definitions can be marked isSegmentable. Sally does not automatically index every custom JSON key. Audience segment rules can be saved and manually synced into materialized audience membership for simple MVP use cases. Future high-volume segmentation should use a controlled derived attribute index only for selected segmentable fields, instead of creating arbitrary JSON indexes for every form field.

Infrastructure direction

Sally web/API is the control plane. High-volume sending, event processing, tracking, and campaign execution should run through separate workers and queues. The foundation schema includes delivery, event, campaign run, sender, approval, and content gate models so this can scale without putting millions of sends or clicks through synchronous web requests.

Marketing workers claim queued deliveries with a guarded QUEUED -> SENDING update so concurrent workers do not send the same delivery. Delivery rows track attemptCount, lastAttemptAt, failure reason, and provider message id. Transient send errors are re-queued with bounded backoff until MARKETING_WORKER_MAX_ATTEMPTS is reached; failed/cancelled deliveries can be manually queued again from the API/UI.

Run once:

pnpm --filter api marketing:worker

Campaigns can be started manually for a contact from the UI/API. The campaign builder opens as a full-section workflow workspace from the campaign list rather than a modal, similar to n8n's list → workflow editing model. It uses an n8n-inspired node canvas and saves graph nodes such as trigger, wait, send email, optional decision, action, and end. Builder templates include welcome drip, lead nurture, and re-engagement flows.

Run continuously:

pnpm --filter api marketing:worker -- --loop

Tune with MARKETING_WORKER_INTERVAL_MS, MARKETING_WORKER_SEND_BATCH, and MARKETING_WORKER_MAX_ATTEMPTS.

AWS SES feedback can be ingested via POST /marketing/webhooks/aws-ses to update delivery status for delivered, bounced, complained, rejected, opened, and clicked events. See marketing-aws-ses-feedback.md.

Initial worker command:

pnpm --filter api marketing:worker

The first worker materializes approved newsletter sends into MarketingDelivery rows and processes queued SMTP/Amazon SES deliveries. Campaign runs are processed one simple graph step at a time for MVP nodes such as wait, send email/message, passthrough, and end. Provider connectors must not move send work into normal request/response routes.

WordPress direction

For websites, use the platform-neutral Website Integration Kit first. See marketing-website-integration.md. WordPress owners can still use existing form plugins; see marketing-wordpress.md. Custom Marketing contact fields such as interests can be defined in Sally and then mapped from any website submission via the fields object.

MCP foundation

When marketing.core is enabled, hosted MCP can expose initial marketing.* tools for contacts, submissions, audiences, messages, campaigns, senders, and approvals. Dangerous mass-send behavior is modeled as send proposals/approvals, not direct agent sends.