# How to Add Marketing Automation to Your App — Copy-Paste Prompts Inside

- Tool: App Features
- Last updated: July 2026

## TL;DR

Marketing automation needs five pieces: a workflow definition stored as JSONB, a trigger engine watching your Supabase tables, a job queue with pg_cron scheduling, email via Resend and push via Firebase Cloud Messaging, and a visual builder canvas. With Lovable or V0 you can ship a working drip sequence in 6–12 hours. Supabase Pro ($25/mo) is required for pg_cron; Resend free tier covers 3,000 emails/month.

## What Marketing Automation Actually Is

Marketing automation is a system that watches what users do in your app and responds with timed, targeted actions — a welcome email sequence when someone signs up, a push notification when they've been inactive for 7 days, a discount when they complete their first purchase. The mechanics are: triggers (events that start a workflow), conditions (IF user.plan = 'free'), delay steps (wait 3 days), and actions (send email, send push, update a database field). Unlike a simple scheduled email, automation is user-specific and event-driven. The product decisions are which triggers matter for your business, how complex the branching needs to be, and whether you need a visual builder your team can operate without engineering support.

## Anatomy of the Feature

Seven components. The workflow builder canvas and the job queue are the hardest for AI tools to generate reliably — the canvas because React Flow node persistence is often stubbed, the queue because pg_cron and SELECT FOR UPDATE SKIP LOCKED are uncommon patterns that AI tools frequently get wrong.

- **Workflow Builder Canvas** (ui): A visual node graph built with React Flow (web, MIT license, zero cost) where each node represents a trigger, condition (IF/ELSE branch), delay step, or action. Nodes connect via edges to form the execution DAG. Drag to reposition, click to configure each node via a sidebar form. Serializes to JSONB on save via a Server Action.
- **Trigger Engine** (backend): A Supabase Edge Function subscribed to Supabase Realtime INSERT/UPDATE events on key tables (users, orders, sessions) or called by a pg_cron schedule. Evaluates the trigger_config JSONB from the workflow against the event payload — e.g. 'user.plan = free AND event = signed_up'. When conditions match, inserts a row into automation_jobs with scheduled_at = now() + delay.
- **Job Queue Table** (data): The automation_jobs table acts as a durable task queue. Each row represents one pending action for one user in one workflow: workflow_id, user_id, step_index, scheduled_at, status (pending/running/completed/failed), attempts, and last_error. A pg_cron job checks every minute for rows WHERE scheduled_at <= now() AND status = 'pending' and invokes the Edge Function processor.
- **Email Action** (service): The Edge Function reads the email step's template and recipient from the workflow context and calls Resend's API (resend.com). Resend free tier: 3,000 emails/month. Open tracking via a 1-pixel tracking URL that must resolve to a stable public endpoint (not a preview URL). Click tracking via a redirect URL logged to analytics_events.
- **Push Notification Action** (service): Firebase Cloud Messaging (FCM) for native mobile apps (Flutter, React Native) and web push. The Edge Function fetches the user's device token from a user_devices table and calls the FCM API with the notification payload. FCM is free with no volume limit. For Expo-based apps, use Expo Push Notifications API (free tier: 1,000 notifications/month; paid from $29/month approx).
- **Audience Segment Builder** (ui): A filter UI built with shadcn/ui components (or Flutter custom widgets for mobile admin panels) allowing AND/OR conditions on user attributes: plan, country, signup_date, last_active_at. Each condition is a row: attribute, operator (equals, greater than, contains), value. The segment definition is stored as JSONB and evaluated by a Supabase RPC function (evaluate_segment) at trigger time.
- **Workflow Analytics** (data): The analytics_events table records every automation event: trigger_fired, email_sent, email_opened (pixel callback), link_clicked (redirect callback), push_delivered, push_opened, and conversion. Aggregated per workflow for the analytics dashboard showing open rate, click rate, and conversion funnel. Supabase Realtime enables live counters on the workflow detail page.

## Data model

Three tables: workflow definitions, the job queue, and analytics events. Run this in the Supabase SQL Editor — it includes the table structure, RLS policies, and the index that makes the job queue performant:

```sql
-- Workflow definitions
CREATE TABLE public.automation_workflows (
  id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
  name text NOT NULL,
  trigger_config jsonb NOT NULL DEFAULT '{}',
  steps jsonb NOT NULL DEFAULT '[]',
  is_active bool DEFAULT false NOT NULL,
  created_by uuid REFERENCES auth.users(id),
  created_at timestamptz DEFAULT now() NOT NULL,
  updated_at timestamptz DEFAULT now() NOT NULL
);

ALTER TABLE public.automation_workflows ENABLE ROW LEVEL SECURITY;

CREATE POLICY "Workflow owners can manage their workflows"
  ON public.automation_workflows
  USING (created_by = auth.uid());

-- Job queue
CREATE TABLE public.automation_jobs (
  id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
  workflow_id uuid REFERENCES public.automation_workflows(id) ON DELETE CASCADE NOT NULL,
  user_id uuid REFERENCES auth.users(id) NOT NULL,
  step_index int DEFAULT 0 NOT NULL,
  scheduled_at timestamptz NOT NULL,
  status text CHECK (status IN ('pending', 'running', 'completed', 'failed')) DEFAULT 'pending' NOT NULL,
  attempts int DEFAULT 0 NOT NULL,
  last_error text,
  created_at timestamptz DEFAULT now() NOT NULL
);

-- Critical index: pg_cron polls this constantly
CREATE INDEX automation_jobs_due_idx
  ON public.automation_jobs (scheduled_at, status)
  WHERE status = 'pending';

ALTER TABLE public.automation_jobs ENABLE ROW LEVEL SECURITY;

CREATE POLICY "Service role manages jobs"
  ON public.automation_jobs
  USING (auth.role() = 'service_role');

CREATE POLICY "Workflow owners can view jobs"
  ON public.automation_jobs FOR SELECT
  USING (
    EXISTS (
      SELECT 1 FROM public.automation_workflows w
      WHERE w.id = workflow_id
        AND w.created_by = auth.uid()
    )
  );

-- Analytics events
CREATE TABLE public.automation_analytics (
  id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
  workflow_id uuid REFERENCES public.automation_workflows(id) ON DELETE CASCADE NOT NULL,
  job_id uuid REFERENCES public.automation_jobs(id),
  event_type text NOT NULL,
  user_id uuid REFERENCES auth.users(id),
  metadata jsonb DEFAULT '{}',
  occurred_at timestamptz DEFAULT now() NOT NULL
);

CREATE INDEX automation_analytics_workflow_idx
  ON public.automation_analytics (workflow_id, occurred_at DESC);

ALTER TABLE public.automation_analytics ENABLE ROW LEVEL SECURITY;

CREATE POLICY "Workflow owners can view analytics"
  ON public.automation_analytics FOR SELECT
  USING (
    EXISTS (
      SELECT 1 FROM public.automation_workflows w
      WHERE w.id = workflow_id
        AND w.created_by = auth.uid()
    )
  );

CREATE POLICY "Service role writes analytics"
  ON public.automation_analytics FOR INSERT
  WITH CHECK (auth.role() = 'service_role');

-- User email preferences (unsubscribe tracking)
CREATE TABLE public.user_email_preferences (
  user_id uuid PRIMARY KEY REFERENCES auth.users(id) ON DELETE CASCADE,
  unsubscribed bool DEFAULT false NOT NULL,
  unsubscribed_at timestamptz
);

ALTER TABLE public.user_email_preferences ENABLE ROW LEVEL SECURITY;

CREATE POLICY "Users manage their own preferences"
  ON public.user_email_preferences
  USING (user_id = auth.uid());
```

The partial index on automation_jobs WHERE status = 'pending' keeps pg_cron's every-minute poll fast even with thousands of historical completed jobs. The user_email_preferences table is required before going live — any automation sending email must check this table before calling Resend to stay compliant with CAN-SPAM.

## Build paths

### Lovable — fit 3/10, 8–12 hours

Lovable builds the workflow list UI, job status dashboard, and email Edge Function reliably; the React Flow canvas is complex enough to cause looping — consider building it in V0 first and importing.

1. Run the SQL schema from this page in Supabase SQL Editor to create the three tables and RLS policies before prompting Lovable
2. Paste the prompt below in Lovable Agent Mode; it will scaffold the workflow list, job monitor dashboard, and the Supabase Edge Functions for email and push actions
3. For the React Flow builder canvas, open a separate V0 session, generate the canvas component, export it from GitHub, and import it into your Lovable project via the GitHub connection — this avoids the looping issue Lovable has with complex node graph wiring
4. In the Lovable Cloud tab under Secrets, add RESEND_API_KEY and FIREBASE_SERVER_KEY so the Edge Functions can call external services
5. In Supabase Dashboard under Database → Extensions, enable pg_cron; then in the SQL Editor add: SELECT cron.schedule('process-automation-jobs', '* * * * *', $$SELECT net.http_post(url:='https://<project>.supabase.co/functions/v1/process-jobs', headers:='{"Authorization": "Bearer <service_role_key>"}') AS request_id$$);
6. Test the full flow on the published Lovable URL: create a 'User signs up' trigger workflow with a 1-minute delay and a test email action; create a new account and monitor the automation_jobs table in Supabase to confirm the job is created and processed

Starter prompt:

```
Build a marketing automation system. The Supabase database already has these tables: automation_workflows (id, name, trigger_config jsonb, steps jsonb, is_active, created_by, created_at), automation_jobs (id, workflow_id, user_id, step_index, scheduled_at, status, attempts, last_error, created_at), automation_analytics (id, workflow_id, job_id, event_type, user_id, metadata, occurred_at), and user_email_preferences (user_id, unsubscribed, unsubscribed_at). Do not recreate these tables.

Build these features:

1. Workflow list page (/automations): table of all workflows with name, is_active toggle, emails sent count, open rate percentage, and a Create New button. Each row links to the workflow detail page.

2. Workflow detail page (/automations/[id]): shows the workflow name, trigger type (user_signed_up | user_inactive | user_purchased), steps JSONB rendered as a readable step-by-step list (not a canvas — display-only), analytics cards (jobs created, emails sent, opens, clicks), and a job log table showing the 20 most recent automation_jobs for this workflow with status badges.

3. Three Supabase Edge Functions:
   a. process-jobs: fetches the next pending job (SELECT ... FOR UPDATE SKIP LOCKED WHERE scheduled_at <= now() AND status = pending LIMIT 1), sets status = running, executes the step at step_index from the workflow's steps JSONB. For email steps: check user_email_preferences.unsubscribed — if true, mark job completed and log automation_analytics event_type=email_skipped_unsubscribed. If not unsubscribed, call Resend API (API key from Supabase Secrets as RESEND_API_KEY) with the email template and recipient. Log automation_analytics event_type=email_sent. For push steps: call FCM API (key from FIREBASE_SERVER_KEY) with the user's device token from user_devices table. If next step exists in workflow, insert new automation_jobs row with scheduled_at = now() + delay_hours. Mark current job completed. On any error: increment attempts, set last_error, if attempts >= 3 set status = failed, else set status = pending with scheduled_at = now() + exponential_backoff (2^attempts minutes).
   b. trigger-workflow: called when a user event occurs. Accepts {event_type, user_id, metadata} in the request body. Finds all active workflows WHERE trigger_config->>'type' = event_type. For each matching workflow, inserts an automation_jobs row with step_index=0 and scheduled_at=now().
   c. track-email-open: public endpoint (no auth required) at /functions/v1/track-email-open?job_id={id}. Inserts automation_analytics row with event_type=email_opened. Returns a 1x1 transparent PNG response so it works as an email tracking pixel.

4. Unsubscribe page (/unsubscribe?user_id={id}): updates user_email_preferences SET unsubscribed=true, unsubscribed_at=now(). Shows a confirmation message. No auth required (linked from email footer).

Edge cases: if the workflow is deactivated while jobs are pending, the process-jobs function should skip jobs for inactive workflows and mark them as cancelled. If RESEND_API_KEY is missing from Secrets, the function should fail fast with a clear error in last_error rather than a generic 500. If user has no device token in user_devices, skip push step and log event_type=push_skipped_no_token.
```

Limitations:

- Lovable frequently enters a looping state when asked to generate a React Flow visual workflow canvas — build the canvas in V0 and import it rather than prompting Lovable for it
- Lovable preview cannot receive inbound HTTP requests, so email open tracking pixel and Resend webhook callbacks will not fire until deployed to the published URL
- The pg_cron setup and cron.schedule() SQL must be executed in Supabase Dashboard manually — Lovable has no interface for configuring pg_cron extensions

### V0 — fit 4/10, 6–10 hours

V0 excels at the React Flow workflow builder canvas and the shadcn/ui segment filter builder; Next.js API routes handle email sends via Resend; pg_cron and Supabase triggers require manual setup in the Supabase Dashboard.

1. Run the SQL schema from this page in Supabase SQL Editor, then add your Supabase credentials in V0's Vars panel (NEXT_PUBLIC_SUPABASE_URL, SUPABASE_SERVICE_ROLE_KEY, RESEND_API_KEY, FIREBASE_SERVER_KEY)
2. Paste the prompt below in V0 to generate the React Flow canvas, the segment filter builder, the workflow analytics dashboard, and the Next.js API routes for process-jobs, trigger-workflow, and track-email-open
3. Publish to Vercel so the API routes become public serverless functions — the email open tracking pixel requires a stable HTTPS URL
4. In Supabase Dashboard, go to Database → Extensions, enable pg_cron, then run: SELECT cron.schedule('process-automation-jobs', '* * * * *', $$SELECT net.http_post(url:='https://your-app.vercel.app/api/automation/process-jobs', headers:='{"Authorization": "Bearer <secret>"}') AS id$$); — replace the URL with your Vercel deployment URL
5. Test end-to-end: create a workflow with a trigger-workflow API call from your app's user signup handler, verify the job appears in automation_jobs, wait one minute for pg_cron, confirm email arrives and the open pixel resolves

Starter prompt:

```
Build a marketing automation system in Next.js App Router with Supabase. The database schema is already created with tables: automation_workflows, automation_jobs, automation_analytics, user_email_preferences (see schema in Supabase).

1. WorkflowCanvas component (app/automations/[id]/canvas/page.tsx): React Flow canvas with four node types — TriggerNode (green, shows trigger type and config), DelayNode (yellow, shows duration in hours/days), EmailNode (blue, shows subject line and preview text), PushNode (purple, shows title and body). Nodes connect via edges. Toolbar at top with Add Trigger, Add Delay, Add Email, Add Push buttons. Save button that calls reactFlowInstance.toObject() and writes {nodes, edges} as the steps JSONB column to automation_workflows via a Server Action. Load existing steps from the workflow row on mount and initialize React Flow with them. Show unsaved changes indicator when nodes have been moved or added without saving.

2. SegmentBuilder component: shadcn/ui-based AND/OR condition builder. Each condition row has an attribute dropdown (plan | country | signup_date | days_inactive), operator dropdown (equals | not_equals | greater_than | less_than | contains), and value input. Add Condition and Add Group buttons. Serializes to the trigger_config JSONB field. Preview text showing 'This workflow will trigger for users where: plan equals free AND days_inactive greater than 7'.

3. Analytics dashboard (app/automations/[id]/page.tsx): server component fetching automation_analytics grouped by event_type for this workflow. Cards showing: Jobs Created, Emails Sent, Open Rate (email_opened / email_sent as %), Click Rate, Conversions. Line chart (Recharts) showing emails sent per day for the last 30 days. Job log table: 20 most recent automation_jobs with status badges (pending=gray, running=blue, completed=green, failed=red), user email, step_index, scheduled_at, last_error on hover.

4. API routes (app/api/automation/):
   a. process-jobs/route.ts (POST, requires Authorization header with service role key): SELECT automation_jobs WHERE scheduled_at <= now() AND status = pending FOR UPDATE SKIP LOCKED LIMIT 1. Set status = running. Fetch workflow steps JSONB. Execute step at step_index. Email step: check user_email_preferences.unsubscribed; if true log email_skipped_unsubscribed and advance to next step. If not unsubscribed, POST to Resend API (use RESEND_API_KEY from env). Push step: fetch device token from user_devices for user_id; if none log push_skipped_no_token. Retry logic: on error, if attempts < 3 set scheduled_at = now() + interval (2^attempts minutes), status = pending; else status = failed. On success: if another step exists insert next job with scheduled_at = now() + delay; mark current job completed.
   b. trigger-workflow/route.ts (POST): accepts {event_type, user_id, metadata}. Fetches active workflows WHERE trigger_config->>'type' = event_type. For each, evaluates segment conditions against the user row. Inserts automation_jobs for matching workflows.
   c. track-email-open/route.ts (GET, no auth): accepts ?job_id=. Inserts automation_analytics event_type=email_opened. Returns Response with 1x1 transparent PNG (Content-Type: image/png).

Edge cases: deactivated workflows — skip job processing and set status=cancelled. Missing RESEND_API_KEY — throw descriptive error stored in last_error. Invalid React Flow state on load (corrupted steps JSONB) — initialize with empty canvas rather than crashing. Workflow pause toggle on list page should set is_active=false and NOT cancel in-flight running jobs, only stop new jobs from being enqueued.
```

Limitations:

- V0 does not auto-provision pg_cron or Supabase database extensions — set up the cron job in Supabase Dashboard manually after deploying to Vercel
- V0 has no awareness of long-running background jobs; the pg_cron scheduler exists entirely outside V0's scope and must be configured separately
- V0 occasional shadcn registry mismatches with React Flow may require a follow-up 'fix the React Flow imports' prompt after initial generation

### Flutterflow — fit 2/10, 10–14 hours

FlutterFlow can build a mobile admin panel for monitoring workflow status and viewing analytics, but cannot build the workflow canvas itself — the execution engine must live entirely in Supabase Edge Functions.

1. Build the workflow execution engine (all three Edge Functions: process-jobs, trigger-workflow, track-email-open) in Supabase before starting FlutterFlow
2. In FlutterFlow, create a WorkflowList page using a Backend Query against automation_workflows filtered by created_by = currentUser.uid; display name, is_active toggle, and job count
3. Create a JobMonitor page with a Backend Query against automation_jobs filtered by workflow_id; use a ListView to display each job with status, user email, and scheduled_at columns
4. Add an Analytics page querying automation_analytics grouped by event_type using a Supabase RPC function that returns aggregated counts per workflow
5. Wire the is_active toggle to a Supabase UPDATE action on the automation_workflows row; no workflow canvas — direct your team to the web app built with V0 for canvas editing

Limitations:

- React Flow has no Flutter equivalent visual canvas builder; the workflow definition canvas must be built as a web app (V0) and cannot be replicated in FlutterFlow
- FlutterFlow is restricted to a monitoring and admin UI for this feature — it cannot host the workflow execution engine, the pg_cron jobs, or the email tracking infrastructure
- The full automation system requires a web-deployed backend regardless of whether a FlutterFlow mobile app is built for management
- Complex segment evaluation with AND/OR conditions requires a Supabase RPC function — FlutterFlow's query builder cannot express this logic without a custom Action

### Custom — fit 5/10, 2–6 weeks

Full custom development is the right path when you need A/B test nodes, CAN-SPAM/GDPR compliance infrastructure, white-label automation sold to your own customers, or custom behavioral event triggers beyond standard DB events.

1. Implement A/B test nodes in the React Flow canvas: a split node that routes a configurable percentage of the audience to variant A vs variant B email templates; track conversions per variant in analytics_events
2. Build CAN-SPAM/GDPR compliance infrastructure: suppression lists, double-opt-in confirmation flows, automatic unsubscribe handling from Resend's webhook, and 30-day user data deletion workflows triggered by account deletion
3. Implement custom behavioral event tracking for in-app events not captured in standard DB changes: video watched 80% of duration, feature used 3 times, upgrade modal dismissed twice — requires a client-side event SDK calling trigger-workflow
4. Build multi-tenant workflow isolation for white-label scenarios where each of your customers manages their own automation rules with separate template libraries and analytics
5. Set up custom email sending domain with Resend (DKIM, SPF, DMARC DNS records) for deliverability — takes 1–2 days and requires DNS access to your sending domain

Limitations:

- Deliverability setup (custom domain, DKIM/SPF/DMARC DNS records) takes 1–2 days alone and requires DNS access separate from the application code
- Multi-tenant workflow isolation adds significant schema complexity — each customer needs isolated workflow definitions, job queues, analytics, and suppression lists with strict RLS boundaries

## Gotchas

- **Email open pixel fires but the analytics event is never recorded** — The tracking pixel URL is generated as a path on the Lovable preview domain or V0 sandbox URL, neither of which can receive inbound HTTP requests from email clients. When a user's email client loads the pixel, the request hits a dead URL and silently fails — open rate shows 0% for all workflows. Fix: Deploy to the published Lovable URL or Vercel before enabling open tracking. The track-email-open endpoint must be a stable HTTPS URL accessible from the public internet. Test by sending yourself a test email, opening it, and checking the automation_analytics table in Supabase for the email_opened event within 30 seconds.
- **pg_cron fires but automation_jobs are not processed — Edge Function returns 500** — pg_cron calls the Edge Function via an HTTP request. If the Edge Function URL is wrong, the service role key in the cron SQL is stale, or the Edge Function itself has an unhandled runtime error, pg_cron silently marks the invocation as failed. The job queue fills up with pending jobs while workflows appear active. Fix: Check SELECT * FROM cron.job_run_details ORDER BY start_time DESC LIMIT 10 in the Supabase SQL Editor to see pg_cron invocation errors. Verify the Edge Function URL format is https://<project-ref>.supabase.co/functions/v1/<function-name> and the Authorization header uses the current service role key from Supabase Dashboard → Project Settings → API.
- **Users receive duplicate emails from the same automation step** — When pg_cron fires every minute, if the process-jobs Edge Function takes longer than 60 seconds to process a batch, the next cron invocation picks up the same 'pending' job before the first invocation has marked it as 'running'. Both invocations execute the email step, sending the same email twice to the same user. Fix: Use SELECT ... FOR UPDATE SKIP LOCKED when fetching the next job from automation_jobs. This PostgreSQL pattern locks the row for the first invocation and causes the second invocation to skip it entirely. Verify the SQL contains FOR UPDATE SKIP LOCKED in your Edge Function — this is the single most important correctness requirement for a job queue.
- **React Flow canvas looks correct but workflow node connections don't save to Supabase** — V0 generates a React Flow canvas that correctly renders nodes and edges in the browser, but stores them only in React component state (reactFlowInstance internal state). Navigating away from the canvas page loses all node positions and connections. The steps JSONB column in automation_workflows never receives a write. Fix: Add an explicit Save button that calls reactFlowInstance.toObject() to get the current {nodes, edges} state and writes it to the automation_workflows.steps column via a Server Action or Supabase client UPDATE. On page load, initialize React Flow with the stored steps value using defaultNodes and defaultEdges props. The prompt must explicitly request this persistence step — V0 frequently omits it.
- **Automation continues sending emails to users who unsubscribed** — The process-jobs Edge Function calls Resend without checking whether the recipient has unsubscribed. Users who clicked the unsubscribe link in a previous email continue receiving future automation steps, creating a CAN-SPAM compliance violation and degrading deliverability reputation. Fix: Add a user_email_preferences table with an unsubscribed boolean column. Before every Resend API call in the Edge Function, query this table for the recipient's user_id. If unsubscribed = true, skip the send, log an automation_analytics event with event_type = email_skipped_unsubscribed, and advance to the next step. Also register Resend's unsubscribe webhook to auto-update this table when users click Resend's native unsubscribe link.

## Best practices

- Always use SELECT ... FOR UPDATE SKIP LOCKED in your job queue processor — it is the only correct pattern for preventing duplicate email sends from concurrent pg_cron invocations
- Check user_email_preferences.unsubscribed before every email send in the Edge Function, not only at workflow enrollment — users can unsubscribe mid-sequence
- Set up a dedicated sending domain in Resend with DKIM and SPF records before going live with more than 500 emails/month — shared-domain sending damages deliverability reputation
- Initialize React Flow from the stored steps JSONB on canvas load and add an explicit Save button — never rely on React component state as the source of truth for workflow definitions
- Test automation workflows with a 1-minute delay step before going live so you can verify the end-to-end flow in a few minutes rather than waiting hours
- Implement exponential backoff for failed jobs (2^attempts minutes, max 3 attempts) to avoid hammering a temporarily unavailable email provider and filling pg_cron logs with errors
- Add a workflow deactivation check in process-jobs so disabling a workflow cancels all pending jobs rather than letting the queue drain over hours
- Log every automation action (email sent, push delivered, step skipped) to automation_analytics from the start — retroactively adding analytics to a running automation system requires schema migrations and data backfills

## Frequently asked questions

### What's the difference between a trigger and an action in a workflow?

A trigger is the event that starts the workflow — 'user signed up', 'user made first purchase', 'user inactive for 7 days'. An action is what the workflow does in response — 'send email', 'send push notification', 'update user attribute'. Between them you can add conditions (if user.plan = free) and delay steps (wait 3 days). A workflow always starts with exactly one trigger and can have multiple actions in sequence.

### Can I pause a workflow without deleting it?

Yes — set is_active = false on the automation_workflows row. This stops the trigger engine from enqueuing new jobs for that workflow. Jobs already in the queue (status = pending) should also be cancelled when you pause — add a workflow_active check in your process-jobs Edge Function that marks pending jobs as cancelled rather than executing them. This prevents emails from being sent from a workflow you've paused.

### How do I handle users who unsubscribe?

Store unsubscribe status in a user_email_preferences table with an unsubscribed boolean and unsubscribed_at timestamp. Check this table before every email send in your Edge Function — if unsubscribed = true, skip the send and log the skip event in analytics. Register Resend's unsubscribe webhook to auto-update this table when users click Resend's native footer unsubscribe link. The unsubscribe page itself is a simple route that requires only the user_id in the URL — no authentication needed.

### How do I test an automation workflow before going live?

Create a test workflow with a short delay step (1 minute instead of 3 days). Use a test email address you control as the recipient. Deploy to your staging environment (Vercel preview URL or Lovable published URL), enroll your test user account in the workflow by calling trigger-workflow directly, and wait for pg_cron to pick up the job. Check automation_jobs in Supabase to confirm the job moves from pending to running to completed. Only then change the delay back to the production value and activate for real users.

### Can I send SMS messages as well as email and push?

SMS is not covered in this build kit because it requires Twilio at $0.0079/message — at 1,000 users receiving 5 automation SMS messages each, that's $39.50 per campaign. Add SMS as a separate action type in your workflow steps JSONB and an Edge Function that calls the Twilio Messages API with the user's phone number from your users table. Make sure you collect explicit SMS marketing consent separately from email consent.

### How do I track whether an email was opened?

Embed a 1x1 transparent PNG image in the email body at a URL like https://yourapp.com/api/automation/track-email-open?job_id={job_id}. When the email client loads the image, your API route logs an email_opened event in automation_analytics. Important caveat: Apple Mail Privacy Protection (iOS 15+) pre-fetches all images, inflating open rates by 40–60%. Treat email open rate as a directional engagement signal, not a precise count. Click-through rate on real links is a more reliable conversion metric.

### What happens if an email fails to send?

The process-jobs Edge Function increments the attempts counter on the automation_jobs row and sets scheduled_at to now() + exponential backoff (2 minutes after first failure, 4 after second, 8 after third). After 3 failed attempts, the job status is set to 'failed' and the last_error column stores the error message from Resend. The workflow dashboard should surface failed jobs so you can investigate — common causes are expired API keys, invalid recipient email addresses, and Resend rate limits.

### Can I segment users by their in-app behavior?

Yes, but behavioral segmentation requires logging those behaviors to your database first. Standard database events (user signed up, order created) are captured automatically by Supabase Realtime. Custom behaviors (feature used 3 times, video watched 80%) need explicit event logging — your app code calls trigger-workflow or writes to a user_events table when the behavior occurs. Once in the database, these values become queryable attributes in your segment filter builder.

---

Source: https://www.rapidevelopers.com/app-features/marketing-automation
© RapidDev — https://www.rapidevelopers.com/app-features/marketing-automation
