Skip to content

17 — Campaigns List (Screen 2, `/app/testimonial-campaigns`)

Cursor-ready plan: campaign management table with status controls, trigger/channel visibility, and quick actions (create, pause, duplicate, archive).

Source: 02-Implementation-Blueprint.mdScreen 2 - Campaigns List (/app/testimonial-campaigns).

Product alignment: 01-Post-Purchase-Video-Testimonial-Collector-Plan.md — campaign-driven post-purchase request automation.

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

Related: 07-email-sms-request-delivery-pipeline.md (campaigns power sending), 16-templates-screen-4.md (message templates), Screen 3 plan (campaign create/edit) should be separate.


Merchants can view and manage all testimonial campaigns in one table, quickly understand each campaign’s trigger/scope/channel, and run operational actions (create, pause/resume, duplicate, archive).


ItemValue
Suggested fileapp/routes/app.testimonial-campaigns.jsx
URL/app/testimonial-campaigns

Auth: authenticate.admin(request); queries/actions always scoped by shopId.


Uses TestimonialCampaign (§5.2) and optionally TestimonialCampaignProduct (§5.3).

Required list columns from blueprint:

  • Name
  • Trigger event (order_paid, order_fulfilled)
  • Delay days
  • Audience scope (all products / collection / product list)
  • Channel (email, sms, both)
  • Status (active, paused)
  • Last sent at
  • Add archivedAt DateTime? or keep status='archived' only.
  • Add lastSentAt DateTime? on campaign if not present; update from sender pipeline (07) whenever a request for that campaign is sent.

  • where: { shopId }
  • Include lightweight aggregates:
    • count of linked products in TestimonialCampaignProduct
    • optional requests sent in last 30 days for quick health signal
  • Sort default: updatedAt desc
  • status=active|paused|archived|all
  • trigger=order_paid|order_fulfilled|all
  • channel=email|sms|both|all
  • q search on campaign name

  • Title: Campaigns
  • Primary action: Create campaign
  • Secondary: optional help link (“How campaign delays work”)

Use IndexTable for sorting + row actions.

Columns:

  1. Campaign name
  2. Trigger event
  3. Delay days
  4. Audience scope (computed label: all / collections / products)
  5. Channel
  6. Status badge
  7. Last sent at
  8. Actions
  • Edit -> /app/testimonial-campaigns/:id (Screen 3)
  • Pause / Resume
  • Duplicate
  • Archive

Use ActionList in overflow menu to keep table clean.


Implement action intents on same route:

  • intent=pause_campaign
  • intent=resume_campaign
  • intent=duplicate_campaign
  • intent=archive_campaign

All intents validate:

  • campaign exists
  • campaign belongs to current shop
  • status transition is legal
  • active -> paused or archived
  • paused -> active or archived
  • archived -> no resume (or allow restore as explicit v2 action; decide and document)

When duplicating:

  • copy all campaign fields except ids/timestamps
  • name suffix: " (Copy)"
  • status default: paused (recommended to avoid accidental sends)
  • clone related TestimonialCampaignProduct rows
  • do not clone runtime metrics (lastSentAt, sent counters)

Campaign table needs clear human-readable scope:

  • allProducts=true -> All products
  • else if product rows count > 0 -> N products
  • else if collection targeting is implemented later -> N collections
  • else -> No target set warning badge

If collection targeting is not implemented yet, keep placeholder label but do not fake data.


  • Show Paused campaigns with warning color.
  • If campaign has invalid config (missing template/channel mismatch), show row-level warning icon and disable resume until fixed.
  • Confirm modals:
    • Archive confirmation required.
    • Duplicate confirmation optional.

Pipeline campaign lookup must only include status='active'.

When request sender dispatches for a campaign:

  • update campaign.lastSentAt = now() (if field exists)

This ensures the list’s “Last sent at” is operationally useful.


  • Table shows only shop-owned campaigns.
  • Pause/resume updates status and is reflected immediately.
  • Duplicate copies targeting rows and starts paused.
  • Archive removes campaign from default “active/paused” view.
  • “Last sent at” updates after sender worker run.
  • Deep link filters (status, trigger, channel) persist across reload.

  1. Build loader query + table data mapping.
  2. Build IndexTable + create button + row actions.
  3. Implement pause/resume/archive actions.
  4. Implement duplicate action with relation cloning.
  5. Add optional filters and badges.
  6. Wire lastSentAt update in sender pipeline.

  • 02-Implementation-Blueprint.md — Screen 2, §5.2, §5.3
  • 07-email-sms-request-delivery-pipeline.md — campaign execution behavior
  • 16-templates-screen-4.md — template dependencies

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