Skip to content

SOP-TEST-01: Testimonials System v1.0

Status: Production Ready (Phase 1)
Last Updated: 2026-02-07
Owner: Protocol Raw Operations


Overview

Collect, approve, and display customer testimonials with live tenure data. Direct testimonials with full attribution, no third-party review platforms.

Philosophy: Protocol Raw's differentiator is verifiable proof. Third-party review platforms suggest the brand needs external validation because internal evidence isn't sufficient. Testimonials display alongside batch test results - the context does the credibility work.


Workflow

1. Customer sends positive feedback (Week 2 check-in reply, support email)
2. Support agent clicks "⭐ Flag Testimonial" in ticket panel
3. Testimonial appears in Ops Portal → Testimonials tab (status: flagged)
4. Review and edit proposed quote
5. Send permission request email (status: requested)
6. Customer replies "yes" → Approve (status: approved)
7. Testimonial displays on website with live data

Database Schema

Table: raw_ops.testimonials

CREATE TABLE raw_ops.testimonials (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  customer_id UUID NOT NULL REFERENCES raw_ops.customers(id),

  -- Source tracking
  source_ticket_id UUID REFERENCES raw_ops.support_tickets(id),
  source_type TEXT NOT NULL DEFAULT 'email_reply' 
    CHECK (source_type IN ('email_reply', 'chat', 'manual')),
  original_text TEXT NOT NULL,

  -- Approved display version
  approved_quote TEXT,
  display_city TEXT,

  -- Approval workflow
  status TEXT NOT NULL DEFAULT 'flagged' 
    CHECK (status IN ('flagged', 'requested', 'approved', 'declined', 'withdrawn')),
  flagged_at TIMESTAMPTZ DEFAULT NOW(),
  flagged_by TEXT,

  -- Permission request
  request_sent_at TIMESTAMPTZ,
  request_email_delivery_id TEXT,

  -- Approval
  approved_at TIMESTAMPTZ,
  approval_method TEXT CHECK (approval_method IN ('email_reply', 'portal')),

  -- Display control
  is_featured BOOLEAN DEFAULT FALSE,
  display_order INTEGER,

  created_at TIMESTAMPTZ DEFAULT NOW(),
  updated_at TIMESTAMPTZ DEFAULT NOW()
);

CREATE INDEX idx_testimonials_status ON raw_ops.testimonials(status);
CREATE INDEX idx_testimonials_customer ON raw_ops.testimonials(customer_id);
CREATE INDEX idx_testimonials_featured ON raw_ops.testimonials(is_featured) 
  WHERE status = 'approved' AND is_featured = TRUE;

View: public.v_testimonials_display

Returns approved testimonials with live customer data:

CREATE OR REPLACE VIEW public.v_testimonials_display AS
SELECT 
  t.id,
  t.approved_quote,
  c.first_name,
  t.display_city,
  TO_CHAR(c.created_at, 'Month YYYY') AS customer_since,
  COUNT(o.id) AS order_count,
  t.is_featured,
  t.display_order
FROM raw_ops.testimonials t
JOIN raw_ops.customers c ON c.id = t.customer_id
LEFT JOIN raw_ops.orders o ON o.customer_id = c.id 
  AND o.export_state = 'EXPORTED'
WHERE t.status = 'approved'
GROUP BY t.id, t.approved_quote, c.first_name, t.display_city, 
         c.created_at, t.is_featured, t.display_order
ORDER BY t.is_featured DESC, t.display_order ASC, t.approved_at DESC;

GRANT SELECT ON public.v_testimonials_display TO anon;

Key feature: order_count updates automatically as customers continue ordering. A testimonial quoted at "8 deliveries" will show "15 deliveries" after 7 more orders without manual maintenance.


RPC Functions

public.flag_testimonial

Called from Support Workstation when flagging positive feedback.

flag_testimonial(
  p_customer_id UUID,
  p_original_text TEXT,
  p_source_ticket_id UUID DEFAULT NULL,
  p_source_type TEXT DEFAULT 'email_reply',
  p_flagged_by TEXT DEFAULT 'system'
) RETURNS UUID

public.get_testimonials_queue

Returns flagged and requested testimonials for ops portal.

get_testimonials_queue() RETURNS TABLE (
  id UUID,
  customer_name TEXT,
  customer_email TEXT,
  customer_city TEXT,
  customer_since TEXT,
  order_count BIGINT,
  original_text TEXT,
  approved_quote TEXT,
  status TEXT,
  flagged_at TIMESTAMPTZ,
  flagged_by TEXT,
  request_sent_at TIMESTAMPTZ
)

public.update_testimonial

Updates testimonial status, quote, or display settings.

update_testimonial(
  p_testimonial_id UUID,
  p_status TEXT DEFAULT NULL,
  p_approved_quote TEXT DEFAULT NULL,
  p_display_city TEXT DEFAULT NULL,
  p_is_featured BOOLEAN DEFAULT NULL
) RETURNS BOOLEAN

Ops Portal Integration

Support Workstation

Location: Ticket response panel, action buttons row

Button: "⭐ Flag Testimonial"

Behaviour: - Extracts first inbound message from conversation thread - Calls flag_testimonial() with customer_id and message text - Shows confirmation alert

Testimonials Tab

Location: Ops Portal → ⭐ Testimonials

Features: - Queue of flagged/requested testimonials - Customer context (name, city, tenure, order count) - Original message display - Editable proposed quote field - "Send Request" button (triggers permission email) - "Approve" button (manual approval) - "Decline" button (removes from pipeline) - Badge showing pending count


Permission Request Email

Template ID: testimonial_request (Customer.io)

Subject: Thank you

Body:

Hi {{customer.first_name}},

This really made me smile - thank you for taking the time to write.

If you're happy for us to share it on our site, here's how it would appear:

---

"{{proposed_quote}}"

{{customer.first_name}}, {{customer.city}} · Customer since {{customer_since}} · {{order_count}} deliveries

---

Reply "yes" if that works, or let me know if you'd prefer different wording.

{{persona}}
Protocol Raw

Status: Template not yet created in Customer.io


Website Display

Endpoint: Query public.v_testimonials_display directly or via Edge Function

Response format:

[
  {
    "approved_quote": "First raw food I felt comfortable feeding with kids in the house.",
    "first_name": "Sarah",
    "display_city": "Bristol",
    "customer_since": "March 2024",
    "order_count": 12,
    "is_featured": true
  }
]

Display format:

"First raw food I felt comfortable feeding with kids in the house."

Sarah, Bristol · Customer since March 2024 · 12 deliveries


Google Reviews (Passive)

Separate from the testimonials system:

  1. Create Google Business Profile for Protocol Raw
  2. Verify as service-area business
  3. Do not actively promote or request reviews
  4. Let reviews accrue organically from customers who seek it out
  5. No widgets, no emphasis, no review request campaigns

Outstanding Items

Item Status Priority
Make.com scenario for permission email Not built Low (manual until volume)
Customer.io testimonial_request template Not built Low
Website display component Not built Low
Inbound reply parsing for auto-approval Not built Future

File Locations

Component Location
Database table raw_ops.testimonials
Display view public.v_testimonials_display
RPC functions public.flag_testimonial, public.get_testimonials_queue, public.update_testimonial
Ops Portal module js/modules/testimonials.js
Support integration js/modules/support.jsflagAsTestimonial()
HTML tab index.html#testimonialsContent

Version History

Version Date Changes
1.0 2026-02-07 Initial implementation - database, ops portal module, support integration

Document Owner: Protocol Raw Operations
Next Review: When first testimonial is collected