CRM Feature Plan · crm-v1.3

CRM Email Campaigns — v1.3

Send targeted email campaigns to customer segments directly from the admin app. Campaigns pick up where segments leave off — define your audience in Segments, compose a message, hit Send. This plan covers the three email configuration approaches and recommends the right one for Setpoint.

PlanningRequested by Nestor2026-03-29

Overview

The Problem & Solution

Problem

Facility operators have no way to communicate with customer segments. They can identify high-value customers, at-risk members, and inactive players — but there is no action attached to that insight. The data sits idle.

Solution

Email campaigns inside the admin CRM. Compose once, target a segment (or everyone), send. Results tracked per recipient. Merge tags personalise subject and body. No third-party marketing tool required.

Already Built

  • crm_email_settings table — stores Resend API key, from name, from email per facility
  • crm_campaigns table — name, subject, body, segment_id, status, recipient/sent/failed counts
  • crm_campaign_sends table — per-recipient send log (email, status, error, sent_at)
  • POST /api/admin/crm/campaigns/[id]/send — resolves recipients, applies merge tags, calls Resend, records results
  • Campaigns page UI — create, edit, send, view results, email settings dialog

Unresolved

Where should the Resend API key live — in-app per-facility settings (already wired up), a shared platform env var, or Supabase Vault? Each has different security, UX, and operational trade-offs.

Email Configuration

Three Options — Pick One

The core question: where does the Resend API key live? Each option has different security, UX, and operational implications.

Option A — Per-Facility Resend Key (In-App Settings)

★ Recommended

Each facility enters its own Resend API key inside the admin app

High Security

How it works

Facility admin opens CRM → Campaigns → Settings dialog. They paste their own Resend API key and configure a from-name + from-email. The key is stored in crm_email_settings (server-side only, never returned to frontend). Every send uses that facility's key, so emails come from their own Resend account.

Pros

  • Each facility controls its own sending domain and reputation — their Resend account, their DNS records
  • Setpoint has zero billing exposure — the facility pays Resend directly
  • Already built — crm_email_settings table + API routes exist
  • Facility can revoke the key at any time without touching Setpoint
  • If one facility's key is compromised, others are unaffected
  • Works for multi-tenant SaaS without shared infrastructure concerns

Cons

  • Operator must sign up for Resend and generate a key themselves (5-minute setup)
  • Each facility must configure a sending domain in their Resend account for best deliverability
  • If a facility doesn't configure it, campaigns are blocked (we show a clear banner)

Verdict

Recommended for Setpoint. Multi-tenant SaaS should not share an email sending account across facilities — domain reputation bleeds between them. Per-facility keys are the industry standard for platforms like this (Mailchimp embedded, Klaviyo agency mode, etc.).

Operator setup: RequiredDev setup: None (already built)

Option B — Shared Platform Resend Key (Env Var)

Single Resend API key owned by Setpoint, all facilities share it

Medium Security

How it works

Add RESEND_API_KEY to the admin app's environment variables on Vercel. All campaigns from all facilities use this one key. Emails appear to come from a Setpoint-owned domain (e.g., campaigns@setpoint.app). Facility operators set nothing — it just works.

Pros

  • Zero operator setup — no API key management for facility admins
  • Simpler onboarding — campaigns work immediately
  • Single DNS configuration for the sending domain

Cons

  • All facilities share one email reputation — one facility sending spam affects everyone's deliverability
  • Setpoint absorbs all Resend billing across all facilities
  • Emails come from a generic Setpoint domain, not the facility's brand
  • Scaling risk — as facilities grow, shared quota must be managed
  • GDPR/data concern — Setpoint processes email data for all customers across all facilities under one account
  • Not appropriate for white-label or independent club branding

Verdict

Only suitable for a single-tenant setup or internal demos. Not recommended for a real SaaS with multiple facilities. Shared sending reputation is a liability at scale.

Operator setup: NoneDev setup: Required

Option C — Per-Facility Key in Supabase Vault

Same as Option A but keys encrypted at rest using Supabase Vault (pgsodium)

Very High Security

How it works

Instead of storing the Resend API key as plaintext in crm_email_settings, encrypt it via Supabase Vault before insert. On send, decrypt using vault.decrypted_secrets or pgsodium.crypto_secretbox_open(). The facility admin UX is identical to Option A.

Pros

  • Keys encrypted at rest using pgsodium — compromising the DB does not expose keys
  • Native to Supabase stack — no extra service or dependency
  • Same UX as Option A for facility admins

Cons

  • More implementation complexity — vault key management, decryption in API routes
  • Vault is a Supabase Pro feature — requires Pro plan to use pgsodium
  • Marginal security improvement for keys that are already server-side-only (never returned to client)
  • Adds complexity for a threat model that is already low risk if DB is not compromised

Verdict

Overkill for v1.3. The current approach (Option A) already keeps keys server-side only — they're never returned to the frontend. Vault adds value if the DB itself is at risk, but that's a concern for later. Upgrade path: migrate to Vault in v2.x without changing the operator UX.

Operator setup: RequiredDev setup: Required

Domain Configuration

Where Do Emails Come From?

Resend Shared Domain (onboarding@resend.dev)

Resend provides a shared sending domain for testing. Works immediately, no DNS setup. Not suitable for production — often filtered as spam.

Best for: Local dev and testing only

Facility's Own Domain (e.g., club@padthaipadelclub.com)

Facility verifies their domain in Resend (adds 3 DNS records: SPF, DKIM, DMARC). Best deliverability, fully branded. Resend dashboard guides setup in ~10 minutes.

Best for: Production — recommended

Setpoint Subdomain (e.g., club@mail.setpoint.app)

Setpoint owns a sending subdomain and grants use to facilities. Simpler for operators but ties reputation to Setpoint. Requires Setpoint to manage DNS.

Best for: Optional — if operator cannot do DNS setup

Campaign Flow

Step-by-Step: Create → Send → Results

1

Configure Email Settings

Built

Facility admin enters Resend API key + from name + from email once. Stored server-side. Never shown again — only a hasKey indicator displayed.

Location: CRM → Campaigns → Settings (gear icon)

2

Create Campaign

Built

Name the campaign, pick a target (All Customers or a saved Segment), write subject + body. Merge tags: {{first_name}}, {{full_name}}, {{email}}, {{facility_name}}.

Location: CRM → Campaigns → New Campaign

3

Preview Recipients

Partial — count shown on create, not on pre-send confirmation

Recipient count shown before sending — pulled from the selected segment's live evaluation. Operator knows exactly how many emails will go out.

Location: Campaign card (audience line shows count)

4

Send

Built

Operator clicks Send, confirms in a dialog. Server resolves recipients, applies merge tags per person, calls Resend REST API in a loop, records results.

Location: Campaign card → Send button → Confirm dialog

5

Results

Built (per-recipient drill-in is v1.4)

After sending: total sent, failed count, error details for failed recipients. Campaign status flips to Sent. Drill-in to see per-recipient log.

Location: Campaign card shows sent/failed badges

Personalisation

Merge Tags

Use these in subject and body to personalise per recipient. Unrecognised tags are left as-is.

TagResolves ToExample
{{first_name}}Customer's first nameSomchai
{{full_name}}Customer's full nameSomchai Rattanakorn
{{email}}Customer's email addresssomchai@example.com
{{facility_name}}Facility name from DBPad Thai Padel

For Nestor

Open Questions — Need Answers Before Merging

Which email configuration option do you prefer — A (per-facility Resend key), B (shared platform key), or C (Vault)?

Recommendation: Option A — already built, industry standard for multi-tenant SaaS, zero Setpoint billing exposure.

Impact: Determines if we need to rebuild the email settings flow or proceed as-is.

Should campaigns support HTML email (rich formatting) or plain text only?

Recommendation: Start with plain text — simpler, no template builder needed, high deliverability. Add HTML template editor in v1.4.

Impact: HTML requires a template builder UI and HTML rendering in Resend — significant extra scope.

Do we need scheduled/scheduled campaigns (send at a specific time) or is immediate send enough for v1.3?

Recommendation: Immediate send for v1.3. Scheduled sends via pg_cron in v1.4.

Impact: Scheduled campaigns require a status column update + pg_cron job per campaign.

Should we track email opens and clicks (requires Resend webhook integration)?

Recommendation: Defer to v1.4. Resend webhooks require an inbound endpoint and more DB schema. Sent/failed tracking is enough for v1.3.

Impact: Open/click tracking = Resend webhooks + new DB columns + a new analytics tab.

Bottom Line

Recommendation: Ship Option A as-is for v1.3

The per-facility Resend key approach (Option A) is already fully implemented. It is the correct architecture for multi-tenant SaaS — each facility is isolated, Setpoint has no billing exposure, and domain reputation is per-facility. The operator setup is a 5-minute one-time task (sign up for Resend free plan, add DNS records, paste key). All the core campaign infrastructure is built. The right next step is to apply the migration, test end-to-end with a real Resend key, and ship.

Immediate Actions

  • Apply migration 20260329000003_crm_segments_campaigns.sql to production
  • Test with a real Resend API key and a verified sending domain
  • Confirm Nestor's answers to the 4 open questions above
  • Merge PR #224 and deploy

Deferred to v1.4

  • HTML email template builder
  • Scheduled campaigns (send at a specific time)
  • Open/click tracking via Resend webhooks
  • Per-recipient drill-in view
  • Unsubscribe link + suppression list