Skip to content

40 — TestimonialTemplate Model & Resolution Rules (Blueprint §5.4)

Cursor-ready plan for template data model, fallback resolution, channel-specific validation, and send-time rendering contracts.

40 — TestimonialTemplate Model & Resolution Rules (Blueprint §5.4)

Section titled “40 — TestimonialTemplate Model & Resolution Rules (Blueprint §5.4)”

Source: 02-Implementation-Blueprint.md§5.4 New model: TestimonialTemplate.

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

Related: 16 (templates screen), 07/32 (delivery workflow), 39 (request model), 20 (schema migration standards).


Implement TestimonialTemplate as the single source of truth for outreach message content with deterministic fallback rules for campaign/default/channel/template type combinations.


Blueprint §5.4 fields:

  • id
  • shopId
  • campaignId (nullable)
  • channel (email | sms)
  • templateType (initial_request | reminder_1 | reminder_2)
  • subject (email only)
  • body
  • isDefault
  • createdAt
  • updatedAt

Unique:

  • @@unique([shopId, campaignId, channel, templateType])

  • model + migration
  • save/update validation rules
  • runtime template resolution algorithm
  • rendering token contract and fallback behavior
  • rich drag-drop email builder
  • multilingual template variants per locale (future extension)

A template is uniquely identified by:

  1. tenant: shopId
  2. optional campaign scope: campaignId|null
  3. channel: email|sms
  4. message stage: initial_request|reminder_1|reminder_2

No other hidden dimensions should affect resolution in v1.


4) Resolution order (critical runtime contract)

Section titled “4) Resolution order (critical runtime contract)”

When sending a message for (shopId, campaignId, channel, templateType):

  1. campaign-specific template (campaignId = requested campaign)
  2. shop default template (campaignId = null and isDefault = true)
  3. hardcoded system fallback (last resort)

This exact order must be reused in:

  • send worker
  • send-test action
  • preview rendering

Do not allow each surface to implement different resolution logic.


  • body required and non-empty
  • channel in allowed set
  • templateType in allowed set
  • subject required when channel=email
  • recommended subject max length (e.g. 150 chars)
  • subject ignored or null (do not persist stale subject semantics)
  • body length warning threshold (soft warning, not hard fail unless product policy requires)
  • For each (shopId, channel, templateType), only one row should be considered default fallback.
  • Enforce via app logic when toggling isDefault:
    • setting one row default should unset sibling defaults of same tuple.

Supported variables from blueprint screen 4:

  • {{customer_first_name}}
  • {{shop_name}}
  • {{product_name}}
  • {{submission_link}}
  • {{incentive_text}}
  • Unknown tokens:
    • keep literal and return warning in preview/send-test output.
  • Missing values:
    • apply safe fallback (e.g., empty string or friendly placeholder).
  • Strip repeated blank lines after replacement for readable output.
  • Escape/render output according to destination channel (email html/plain rules if needed).
  • Never inject untrusted raw HTML in SMS flow.

Use upsert keyed on unique tuple:

  • (shopId, campaignId, channel, templateType)

If unique conflict occurs in concurrent edits:

  • retry with latest row merge strategy or return conflict message.

Optional: add template change audit table later (not required by blueprint).


TestimonialCampaign.channel dictates which template channel must resolve.

  • reminder_1 and reminder_2 types used according to reminderCount.

When send fails due to template resolution, error should include:

  • missing tuple details (channel, templateType, campaignId)
  • not raw template contents

Migration rollout:

  1. add model
  2. seed minimal default templates per channel/type for active shops (optional but recommended)
  3. deploy templates UI and sender resolution in same release window

Seeding reduces runtime “no template found” failures.


Common reads:

  • fetch campaign-specific + default candidates for tuple
  • list all templates for shop in templates screen grouped by channel/type/scope

Potential index optimization:

  • @@index([shopId, channel, templateType]) if lookup frequency becomes high.

  • Upsert works for all channel/type combinations.
  • Resolution order behaves exactly as defined in section 4.
  • Email requires subject; SMS ignores subject.
  • Token rendering supports all blueprint variables.
  • Missing campaign-specific template falls back to shop default, then system fallback.

12) Suggested implementation order (for Cursor)

Section titled “12) Suggested implementation order (for Cursor)”
  1. Add Prisma model and migration.
  2. Implement template resolver helper (shared).
  3. Implement renderer/token replacement helper.
  4. Wire save/upsert in templates screen.
  5. Wire sender worker to resolver helper.
  6. Add send-test and preview validation parity.

  • 02-Implementation-Blueprint.md — §5.4 and Screen 4 fields
  • 16-templates-screen-4.md
  • 07-email-sms-request-delivery-pipeline.md
  • 32-email-sms-operational-detail-blueprint-section-4.md

This folder already includes 05 through 39 plans. This file is 40-....