Skip to content

18 — Campaign Create/Edit (Screen 3, `/app/testimonial-campaigns/:id`)

Cursor-ready plan: create/update testimonial campaigns with trigger, delay, audience targeting, channel, reminders, and incentives.

Source: 02-Implementation-Blueprint.mdScreen 3 - Campaign Create/Edit (/app/testimonial-campaigns/:id).

Product alignment: 01-Post-Purchase-Video-Testimonial-Collector-Plan.md — post-purchase automation rules with reminders and optional incentives.

This document is a build spec only. No code changes are implied until a task references this file.

Related: 17-campaigns-list-screen-2.md (table + row actions), 16-templates-screen-4.md (message content), 07-email-sms-request-delivery-pipeline.md (campaign execution), 12-moderation-settings-screen-9.md (content gate rules are separate).


Merchants can create or edit a campaign that defines when to request testimonials, who receives requests, how (email/sms/both), reminder policy, and incentive details, with strong validation before activation.


ItemValue
Suggested fileapp/routes/app.testimonial-campaign.$id.jsx
URLs/app/testimonial-campaigns/new (create) and /app/testimonial-campaigns/:id (edit)

You can implement create mode with :id = "new" or separate route; keep URL semantics easy for deep links from Screen 2.

Auth: authenticate.admin(request); scope every read/write by shopId.


From Screen 3:

  • Campaign name (text)
  • Active toggle (boolean)
  • Trigger event (select)
  • Delay days (int, min 0, max 60)
  • Audience:
    • All products toggle
    • Collection selector (multi)
    • Product selector (multi)
  • Channel (email, sms, both)
  • Reminder rules:
    • Enable reminder (boolean)
    • Reminder delay days (int)
    • Max reminders (int)
  • Incentive:
    • Enabled (boolean)
    • Incentive type (discount_code, gift_card, none)
    • Incentive value/code

Primary model: TestimonialCampaign (§5.2).

Targeting linkage:

  • TestimonialCampaignProduct (§5.3) for product targeting.

Blueprint includes collection targeting, but only product mapping table is specified in §5.3.

Choose one path and document:

  1. Add TestimonialCampaignCollection model now, or
  2. Store collection ids in JSON field on campaign for v1, migrate later.

Do not fake collection support in UI if persistence path is not implemented.


  • Return default form values.
  • Provide product/collection options lists for selectors.
  • Fetch campaign by id + shopId.
  • Fetch related product mappings (+ collection mappings if implemented).
  • Map DB values to form defaults.
  • Product options:
    • Preferred: local Product mirror table.
    • Fallback: Shopify Admin API search if mirror unavailable.
  • Collection options:
    • Mirror/API depending on what exists in app.

5) Validation rules (server-side required)

Section titled “5) Validation rules (server-side required)”
  • name: required, trimmed, max length (e.g. 120).
  • triggerEvent: allowed values only (order_paid, order_fulfilled).
  • delayDays: integer between 0 and 60.
  • channel: one of email, sms, both.
  • If allProducts=true, ignore product/collection selections.
  • If allProducts=false, require at least one product or collection target.
  • Prevent duplicate product ids.
  • If reminderEnabled=false, force maxReminders=0 (or keep as entered but ignored—pick one and document).
  • If enabled:
    • reminderDelayDays >= 1
    • maxReminders within safe range (e.g. 1..3 v1)
  • If incentiveEnabled=false, set type/value null or none.
  • If enabled:
    • type required (discount_code or gift_card)
    • value/code required

If merchant sets campaign active, block activation with clear error when:

  • no valid audience,
  • channel/template mismatch (e.g. SMS channel but no SMS template fallback from Screen 4),
  • invalid reminder config.

Use single route action with intents:

  • intent=create_campaign
  • intent=update_campaign
  • intent=toggle_active
  • optional intent=delete_campaign (if you want hard delete; blueprint suggests archive from list)

For create/update:

  • Upsert campaign row
  • Replace target mapping rows atomically:
    • Delete existing mappings for campaign
    • Insert current selection set

Use Prisma $transaction for consistency.

Blueprint has status (active, paused, archived) plus “Active toggle”. Map active toggle to:

  • active=true => status='active'
  • active=false => status='paused'

Do not allow toggling archived campaign back to active from this screen unless explicitly supported.


Suggested section cards:

  1. Basic info
    • Name, active toggle
  2. Trigger & schedule
    • Trigger event, delay days
  3. Audience
    • All products checkbox
    • Product/collection multi-select (enabled when allProducts=false)
  4. Channel
    • Email/SMS/Both
  5. Reminders
    • Enable, delay, max reminders
  6. Incentive
    • Enable + type + value
  7. Save actions
    • Save draft, Save & activate (optional split action)
  • Show helper text examples for trigger/delay.
  • Inline validation near each field.
  • Warning banner if active campaign has missing template coverage.

Before allowing status=active, verify available template resolution:

  • If channel includes email: initial_request email template must exist via fallback chain.
  • If reminders enabled: reminder template(s) exist or system fallback exists.
  • If channel includes sms: sms templates available or explicit fallback.

Reference resolver from 16-templates-screen-4.md (campaign-specific -> shop default -> system fallback).


07 campaign selection should assume this screen enforces valid config.

Required contract from this screen to pipeline:

  • triggerEvent
  • delayDays
  • status
  • channel
  • allProducts + mapped targets
  • reminderEnabled, reminderDelayDays, maxReminders
  • incentive fields for template variables ({{incentive_text}})

  • Create campaign with minimal valid fields (allProducts=true).
  • Create campaign with product targeting and saved mapping rows.
  • Validation blocks activation when audience empty.
  • Reminder constraints enforced (delay/max).
  • Incentive fields enforced when enabled.
  • Editing campaign updates mappings atomically (no stale targets).
  • Cross-shop campaign id access blocked.
  • Archived campaign cannot be accidentally reactivated.

  1. Build loader for create/edit modes.
  2. Build Polaris form sections and state mapping.
  3. Add server validation and create/update actions.
  4. Add mapping replace logic in transaction.
  5. Add activation guardrail checks (templates + audience).
  6. Connect navigation back to Screen 2 list.

  • 02-Implementation-Blueprint.md — Screen 3, §5.2, §5.3
  • 17-campaigns-list-screen-2.md — status model and list actions
  • 16-templates-screen-4.md — template fallback and send-test expectations
  • 07-email-sms-request-delivery-pipeline.md — runtime execution

This folder already includes 05 through 17 plans. This file is 18-....