Skip to content

SOP-LC-01: Lifecycle Communications

Version: 1.5 Status: Production Ready. All campaigns built in Customer.io (inactive, pending launch activation). All infrastructure wired and end-to-end tested. Created: January 2026 Updated: April 2026 Owner: Protocol Raw Operations Architecture Owner: Anton (Founder) Copy Owner: Protocol Raw Operations (Head of Growth) Related: Email Design System v1.3, SOP-CS-00, SOP-REF-01, SOP-SUB-00, SOP-DLV-01, SOP-EMAIL-01, SOP-MON-01


Key Changes in v1.5

  • Transition Protection sequence added: 5 automated emails (T1-T4, T6) replacing the old Week 1 / Week 2 onboarding check-ins
  • T1 Arrival Guide (Day 0, Utility): freezer storage, thawing, first meal instructions
  • T2 Day 2 Check-in (Day 2, Relational): appetite and acceptance check-in, reply-inviting
  • T3 Day 4 Normalisation (Day 4, Relational): soft stools and meal hesitation normalisation
  • T4 Day 7 Milestone (Day 7, Relational): transition complete, reply-inviting, core Golden Path metric
  • T6 Pre-Box-2 Confidence (~3 days before Box 2 charge, Utility): cadence-aware, routine confidence reinforcement
  • Founder check-in (T5, Day 10) unchanged
  • Old "Onboarding Check-ins" campaign retired in Customer.io (renamed [RETIRED], left inactive)
  • New Customer.io campaigns: "Transition Protection" (trigger: first_box_delivered) and "Pre-Box-2 Confidence" (trigger: pre_box2_confidence)
  • Pre-Box-2 event created by extended create_first_delivery_event with scheduled_for = next_billing_date - 3 days
  • New function fn_reschedule_pre_box2_event_v1 re-schedules or removes pre_box2_confidence event on subscription changes
  • Golden Path metrics remapped in Growth Playbook v2.4

1. Overview

Purpose

This SOP defines all automated customer email communications for Protocol Raw. It consolidates copy, triggers, timing, and technical implementation details into a single reference document.

Philosophy

Emails are not marketing brochures. They are direct communication with someone who already trusts us. Every email should feel like a note from a competent, thoughtful company, not a sales pitch.

Core principles:

  • Calm, expert, reassuring
  • Short, declarative, plain English
  • Factual without being cold
  • Respectful of time

We are: Confident, warm, systematic We are NOT: Salesy, cute, desperate for attention

Email Categories

Category Purpose Headline Sign-off Tone
Transactional Receipts, confirmations, system updates Optional Protocol Raw Factual, simple
Operational Delivery reminders, billing notices Optional Protocol Raw Factual, simple
Lifecycle Onboarding, win-back, reactivation Yes Protocol Raw Warm, affirming, one clear purpose
Lifecycle (Relational) Check-ins, reply-inviting No Persona Conversational, human
Support Replies to customer inquiries No Persona Conversational, human

Personas

For relational emails that invite replies, we use named personas:

Persona Usage
Sophie Support, check-ins
Tom Support, check-ins
Lucy Support, check-ins

Assignment: Deterministic per customer (hash of customer ID). Same customer always gets same persona across all touchpoints. Persona is set on the Customer.io profile via send-lifecycle-events identify call. Missing persona is a data integrity issue; do not mask with template defaults in production.


2. Email Inventory

Implementation Status Summary

Category Total Live Built (inactive)
Transactional (immediate) 12 12 0
Operational (scheduled) 2 2 0
Lifecycle (campaign) 5 0 5
Lifecycle relational (campaign) 4 0 4
Lifecycle founder (manual) 1 0 1
Win-back (campaign) 2 0 2
Abandonment 2 2 0
Total 28 16 12

Master List

# Email Category Trigger Event Timing CIO ID Status
1a Order Confirmation (First) Transactional trg_order_confirmation Immediate 6 Live
1b Order Confirmation (Repeat) Transactional trg_order_confirmation Immediate 7 Live
2 Transition Guide Lifecycle order_created_first +1 day Campaign Built (inactive)
2a Arrival Guide (T1) Lifecycle first_box_delivered +2 hours Campaign Built (inactive)
2b Day 2 Check-in (T2) Lifecycle (Relational) first_box_delivered +2 days Campaign Built (inactive)
2c Day 4 Normalisation (T3) Lifecycle (Relational) first_box_delivered +4 days Campaign Built (inactive)
2d Day 7 Milestone (T4) Lifecycle (Relational) first_box_delivered +7 days Campaign Built (inactive)
2e Pre-Box-2 Confidence (T6) Lifecycle pre_box2_confidence Cadence-aware (~Box 2 - 3 days) Campaign Built (inactive)
3 Founder Check-in (Day 10) Lifecycle (Relational) first_box_delivered +10 days Phase A manual / Customer.io inactive Phase A manual
4 ~~Week 2 Check-in~~ ~~Lifecycle (Relational)~~ ~~first_box_delivered~~ ~~+14 days~~ ~~Campaign~~ Removed (Phase A). Founder email at day 10 replaces both Week 1 and Week 2. Re-evaluate for Phase B.
5 7-Day Delivery Reminder Operational send-delivery-emails -7 days Track event Live
6 48-Hour Lock Operational send-delivery-emails -48 hours Track event Live
7 Dispatch Confirmation Transactional trg_shipment_email On dispatch 17 Live
8 Delivery Confirmation Transactional trg_shipment_email On delivery 18 Live
9 Skip Confirmation Transactional trg_subscription_action_email Immediate 9 Live
10 Reschedule Confirmation Transactional trg_subscription_action_email Immediate 10 Live
11 Pause Confirmation Transactional trg_subscription_action_email Immediate 11 Live
12 Resume Confirmation Transactional trg_subscription_action_email Immediate 12 Live
13 Cancellation Confirmation Transactional trg_subscription_action_email Immediate 13 Live
14 Box Size Change Transactional trg_subscription_action_email Immediate 14 Live
15 Frequency Change Transactional trg_subscription_action_email Immediate 15 Live
16 Address Change Transactional trg_subscription_action_email Immediate 16 Live
17 Win-back 1 Lifecycle subscription_cancelled +30 days Campaign Built (inactive)
18 Win-back 2 Lifecycle subscription_cancelled +60 days Campaign Built (inactive)
A1 Calculator Abandonment 1h Lifecycle send-abandonment-events +1 hour Track event Live
A2 Calculator Abandonment 48h Lifecycle send-abandonment-events +48 hours Track event Live

Emails Documented Elsewhere

Email Source SOP
Referral signup SOP-REF-01
Credit earned SOP-REF-01
Credit applied SOP-REF-01
Credit expiring SOP-REF-01
Card expiring (30 day) SOP-SUB-00
Card expiring (7 day) SOP-SUB-00
Payment failed (Day 0/3/7/10) SOP-SUB-00
Courier exceptions SOP-DLV-01

3. Onboarding Sequence

3.1 Order Confirmation (First)

Customer.io ID: 6 Trigger: AFTER INSERT on raw_ops.orders (first order detected by trigger_order_confirmation) Timing: Immediate Category: Transactional

Subject: Your order is confirmed Preview text: Order #{{order.order_number}}. We'll have it with you soon.


Hi {{customer.first_name}},

We've received your order and it's being prepared.

Order #{{order.order_number}} {{order.box_size}} box, £{{order.total_price}} {{customer.address1}}, {{customer.city}}, {{customer.zip}}

Your box will be matched to a tested batch. It will arrive frozen within 3-5 working days.

You'll receive tracking as soon as it leaves our freezer. Once it arrives, scan the QR code on any pouch to view your batch report.

Tomorrow we'll send {% if dog_name %}{{dog_name}}'s{% else %}your{% endif %} personalised transition guide with a day-by-day feeding schedule.

Any questions, just reply.

Protocol Raw


Also triggers: create_order_created_event inserts order_created_first lifecycle event into lifecycle_events table. This event fires the Transition Guide campaign in Customer.io and syncs profile attributes (first_name, dog_name, persona, transition_url, subscription_status, box_size, timezone).

3.2 Order Confirmation (Repeat)

Customer.io ID: 7 Trigger: AFTER INSERT on raw_ops.orders (repeat order) Timing: Immediate Category: Transactional

Subject: Your order is confirmed Preview text: Order #{{order.order_number}}. We'll have it with you soon.


Hi {{customer.first_name}},

We've received your order and it's being prepared.

Order #{{order.order_number}} {{order.box_size}} box, £{{order.total_price}} {{customer.address1}}, {{customer.city}}, {{customer.zip}}

Your box will be matched to a tested batch. It will arrive frozen within 3-5 working days.

You'll receive tracking as soon as it leaves our freezer.

Any questions, just reply.

Protocol Raw


3.3 Transition Guide

Customer.io Campaign: Transition Guide Trigger event: order_created_first (via lifecycle events pipeline) Timing: +1 day after order Category: Lifecycle From: Protocol Raw / hello@protocolraw.co.uk

Subject: {% if dog_name %}{{dog_name}}'s{% else %}Your{% endif %} transition guide is ready Preview text: A 10-day schedule for switching to raw.


Your 10-day transition plan

Hi {{first_name}},

Your first box is on the way. You've got a clear plan for the switch.

A gradual change gives {% if dog_name %}{{dog_name}}{% else %}your dog{% endif %} time to adapt. The schedule shows the exact gram amounts for each day, based on the feeding plan you calculated.

[CTA: View your schedule -> {{transition_url}}]

Softer stools in the first few days can be normal. This usually settles within a week.

You may also notice {% if dog_name %}{{dog_name}}{% else %}your dog{% endif %} drinking less water. Raw food contains more moisture, so more of their hydration comes from the food itself.

Questions during the transition? Reply here and we'll help.

Protocol Raw


Notes: Single purpose: transition plan. No portal management, no skip/pause instructions. The transition_url is a pre-built deep link to the Customer Portal TransitionView (https://my.protocolraw.co.uk?view=transition). The portal handles authentication.

3.3a Transition Protection Sequence (T1-T4)

Customer.io Campaign: Transition Protection Trigger event: first_box_delivered (via lifecycle events pipeline) Category: Lifecycle (T1, T6: Utility; T2-T4: Relational) From: Protocol Raw / hello@protocolraw.co.uk (T1, T6); {{persona}} at Protocol Raw / hello@protocolraw.co.uk (T2-T4) Status: Built, inactive (pending launch activation)

Campaign workflow:

first_box_delivered
    -> Wait 2 hours -> Send T1 (Arrival Guide)
    -> Wait 2 days -> Branch: subscription_status = active? -> No: Exit / Yes: Send T2 (Day 2 Check-in)
    -> Wait 2 days -> Branch: subscription_status = active? -> No: Exit / Yes: Send T3 (Day 4 Normalisation)
    -> Wait 3 days -> Branch: subscription_status = active? -> No: Exit / Yes: Send T4 (Day 7 Milestone)
    -> Exit

T1: Arrival Guide (Day 0, +2 hours) Layout: Utility

Subject: {% if customer.dog_name %}{{customer.dog_name}}'s{% else %}Your{% endif %} food is here Preview text: Freezer first, then start tomorrow.


{% if customer.dog_name %}{{customer.dog_name}}'s{% else %}Your{% endif %} food is here.

Move the trays to your freezer as soon as you can. The food arrives frozen and should be stored frozen until you're ready to use it.

To prepare a meal, move one tray to the fridge the night before. It will be thawed and ready to serve by morning.

Tomorrow is Day 1 of {% if customer.dog_name %}{{customer.dog_name}}'s{% else %}your{% endif %} transition. Your schedule has the exact amounts for each day.

[CTA: View your transition schedule -> {{transition_url}}]

Any questions, just reply.

Protocol Raw


Notes: One job: bridge from box arrival to first meal. No proof portal, no QR code, no handling hygiene beyond implicit "move to freezer." CTA links to the same transition_url used in the Transition Guide email. Data requirements: dog_name, transition_url (both synced via first_box_delivered identify call).

T2: Day 2 Check-in (Day 2) Layout: Relational

Subject: How's {% if customer.dog_name %}{{customer.dog_name}}{% else %}your dog{% endif %} finding the food? Preview text: Just checking in.


Hi {{first_name}},

{% if customer.dog_name %}{{customer.dog_name}}{% else %}Your dog{% endif %} should be a day or two into the transition now. I just wanted to check how it's going.

Some dogs take to raw food straight away. Others need a day or two to get used to a new texture and temperature.

If {% if customer.dog_name %}{{customer.dog_name}}{% else %}your dog{% endif %} ate it happily, that's great. A bit of hesitation at the bowl or extra sniffing before eating is completely fine too.

If it's going well, or anything feels off, just reply and let me know.

{{persona}} Protocol Raw


Notes: Core Golden Path metric (Steps 7-8: open rate, reply rate). First relational email in the sequence. Reply invitation covers both positive and negative responses for engagement measurement. Does not mention stools (that is Day 4's job). Data requirements: first_name, dog_name, persona.

T3: Day 4 Normalisation (Day 4) Layout: Relational

Subject: How's {% if customer.dog_name %}{{customer.dog_name}}{% else %}your dog{% endif %} settling in? Preview text: A few things that can happen around Day 4.


Hi {{first_name}},

By now {% if customer.dog_name %}{{customer.dog_name}}{% else %}your dog{% endif %} is partway through the transition. Around this point, a couple of things can come up.

Softer stools are the most common. That's a normal part of the adjustment for many dogs, and it usually settles within a few days.

Some dogs slow down at the bowl or seem less interested for a meal or two. That's not rejection. A new texture and temperature can take a little getting used to.

If {% if customer.dog_name %}{{customer.dog_name}}{% else %}your dog{% endif %} is doing fine, you don't need to do anything differently. Stay with the schedule.

If something feels like more than a normal adjustment, reply and tell me what you're seeing. I can help.

{{persona}} Protocol Raw


Notes: Secondary diagnostic metric (open rate, reply rate as friction signal). High reply rates here indicate transition problems, not satisfaction. Names the two most common Day 3-5 symptoms (soft stools, meal hesitation) and normalises both. Does not name vomiting, refusal, or diarrhoea (rarer, more serious. Customers experiencing those will reply regardless). Data requirements: first_name, dog_name, persona.

T4: Day 7 Milestone (Day 7) Layout: Relational

Subject: How's {% if customer.dog_name %}{{customer.dog_name}}{% else %}your dog{% endif %} getting on? Preview text: What to expect from here.


Hi {{first_name}},

{% if customer.dog_name %}{{customer.dog_name}}{% else %}Your dog{% endif %} should be fully on raw food now, or very close to it.

From here, things usually start to settle. You may notice stools getting firmer and smaller. Some dogs also drink a bit less water, which is normal on raw food.

You don't need to change anything. Keep feeding the amount on your schedule and let the routine do the work.

How's it going? I'd genuinely like to know. Just reply.

{{persona}} Protocol Raw


Notes: Core Golden Path metric (Steps 9-10: open rate, reply rate). Marks the transition milestone, then invites a reply. No professional self-identification prompt (kept clean for measurement purity; professional self-ID handled via reply-handling per Growth Playbook v2.4). Data requirements: first_name, dog_name, persona.

3.3b Pre-Box-2 Confidence (T6)

Customer.io Campaign: Pre-Box-2 Confidence Trigger event: pre_box2_confidence (lifecycle event with scheduled_for = next_billing_date - 3 days) Category: Lifecycle From: Protocol Raw / hello@protocolraw.co.uk Layout: Utility Status: Built, inactive (pending launch activation)

Campaign workflow:

pre_box2_confidence
    -> Branch: subscription_status = active? -> No: Exit / Yes: Send T6
    -> Exit

Subject: {% if customer.dog_name %}{{customer.dog_name}}'s{% else %}Your{% endif %} next box is coming soon Preview text: Everything is on track.


{% if customer.dog_name %}{{customer.dog_name}}'s{% else %}Your{% endif %} next box is coming up.

It will be the same recipe, tested before it ships, and delivered on schedule. If you need to skip, reschedule, or change your box size, you can do that from your portal before your billing date.

[CTA: Manage your plan -> {{portal_url}}]

Any questions, just reply.

Protocol Raw


Notes: Secondary diagnostic metric (open rate). Cadence-aware timing via scheduled_for on lifecycle event. Does not repeat billing date, ship date, price, or credit breakdown (the 7-day delivery reminder already covers those). One job: routine confidence reinforcement before second payment. The pre_box2_confidence lifecycle event is created by create_first_delivery_event and rescheduled by fn_reschedule_pre_box2_event_v1 on subscription changes. Edge case: if next_billing_date - 3 days has already passed at first delivery time, the event is not created and the 7-day reminder serves as the pre-Box-2 touch. Data requirements: dog_name, portal_url.

3.4 Founder Check-in (Day 10)

Trigger: first_box_delivered event Timing: +10 days after first delivery Category: Lifecycle (Phase A: manual from Gmail, Phase B: Customer.io campaign) From: Anton at Protocol Raw anton@protocolraw.co.uk Reply-to: anton@protocolraw.co.uk Layout: None (Phase A manual) / Support Layout (Phase B Customer.io)

Subject: How's {% if customer.dog_name %}{{customer.dog_name}}{% else %}the first week{% endif %} going? Preview text: none


Hey {{customer.first_name}},

I'm Anton from Protocol Raw, and I wanted to check in now that {{customer.dog_name}}'s been on the food for about a week.

Every dog adjusts differently. Some take to it straight away, others need a little longer. Both are completely normal.

If anything feels unusual, or if you'd just like to tell me how it's going, just hit reply. I read every message myself.

Thanks, Anton Founder, Protocol Raw


Phase A execution: Manual send from Gmail. Supabase/Metabase query identifies customers whose first delivery was 10 days ago and who haven't cancelled. Log sends in spreadsheet.

Phase B switchover: When manual volume becomes impractical (~50+ customers), activate the Customer.io campaign and deactivate manual process.

3.5 Week 2 Check-in

Removed in v1.4. Replaced by the Transition Protection sequence (T1-T4, Section 3.3a) and Pre-Box-2 Confidence (T6, Section 3.3b) in v1.5. The founder email at Day 10 (T5) remains as the personal check-in touch.


4. Ongoing Subscription

4.1 7-Day Delivery Reminder

Trigger: pg_cron send-delivery-emails (daily 09:00 UTC) Source function: get_pending_delivery_emails() checks subscriptions.next_billing_date - 7 days Category: Operational Status: Live (native Supabase, not Make.com)

Subject: {% if customer.dog_name %}{{customer.dog_name}}'s{% else %}Your{% endif %} next box ships in a week Preview text: Make any changes by {{cutoff_date}}.


Hi {{customer.first_name}},

{% if customer.dog_name %}{{customer.dog_name}}'s{% else %}Your{% endif %} next box ships in about a week.

Next delivery {{box_size}} box Ships: {{ship_date}} Charged: {{billing_date}} {% if has_credit %} {{list_price}} Credit: -{{credit_applied}} You'll pay: {{amount_due}} {% else %} {{list_price}}

Need to skip, reschedule, or change your box size? Make changes by {{cutoff_date}}: {{portal_link}}

Any questions, just reply.

Protocol Raw


Payload fields:

Field Type Example New in v1.4?
first_name string "Sarah" No
box_size string "12kg" No
price string "89" No
billing_date string ISO date No
ship_date string ISO date No
cutoff_date string ISO date No
portal_link string URL with token No
has_credit boolean true Yes
list_price string "£89" Yes
credit_applied string/null "£15.00" Yes
amount_due string "£74.00" Yes

4.2 48-Hour Lock

Trigger: pg_cron send-delivery-emails (daily 09:00 UTC) Source function: get_pending_delivery_emails() checks subscriptions.next_billing_date - 2 days Category: Operational Status: Live (native Supabase, not Make.com)

Subject: {% if customer.dog_name %}{{customer.dog_name}}'s{% else %}Your{% endif %} box ships tomorrow Preview text: Changes are now locked.


Hi {{customer.first_name}},

{% if customer.dog_name %}{{customer.dog_name}}'s{% else %}Your{% endif %} next box ships tomorrow. Changes are now locked for this delivery.

This delivery {{box_size}} box Ships: {{ship_date}} {% if has_credit %} {{list_price}} Credit: -{{credit_applied}} You'll pay: {{amount_due}} {% else %} {{list_price}}

If you need to make urgent changes, reply to this email.

Protocol Raw


Note: The 48-hour event does NOT include cutoff_date (the cutoff has already passed). All other payload fields are the same as the 7-day reminder (see Section 4.1), including the four credit fields (has_credit, list_price, credit_applied, amount_due).

5. Subscription State Changes

5.1 Skip Confirmation

Customer.io ID: 9 Trigger: AFTER INSERT on raw_ops.subscription_actions (action_type = 'skip') Timing: Immediate Category: Transactional

Subject: Delivery skipped Preview text: Your plan is saved and ready whenever you are.


Hi {{customer.first_name}},

You've skipped your next delivery. No problem. Your plan remains active.

Next delivery {{subscription.box_size}} box, £{{subscription.price}} Ships: {{subscription.new_ship_date}} Charged: {{subscription.new_billing_date}}

You can adjust this anytime from your portal.

Manage your subscription

Any questions, just reply.

Protocol Raw


CTA: Manage your subscription -> {{portal_url}}

5.2 Reschedule Confirmation

Customer.io ID: 10 Trigger: AFTER INSERT on raw_ops.subscription_actions (action_type = 'reschedule') Timing: Immediate Category: Transactional

Subject: Delivery rescheduled Preview text: Your new delivery date is confirmed.


Hi {{customer.first_name}},

Your new delivery date is confirmed.

New delivery date {{subscription.box_size}} box, £{{subscription.price}} Ships: {{subscription.new_ship_date}} Charged: {{subscription.new_billing_date}}

Everything else on your plan remains the same. You can adjust your delivery anytime from your portal.

Manage your subscription

Any questions, just reply.

Protocol Raw


CTA: Manage your subscription -> {{portal_url}}

5.3 Pause Confirmation

Customer.io ID: 11 Trigger: AFTER INSERT on raw_ops.subscription_actions (action_type = 'pause') Timing: Immediate Category: Transactional

Subject: Subscription paused Preview text: Your plan is saved and ready whenever you are.


Hi {{customer.first_name}},

Your subscription is now paused. You won't be charged until you resume.

Subscription status {{subscription.box_size}} box, £{{subscription.price}} Status: Paused

Your plan remains saved and ready whenever you are.

When you're ready, you can resume from your portal and we'll schedule your next delivery.

Manage your subscription

Any questions, just reply.

Protocol Raw


CTA: Manage your subscription -> {{portal_url}}

5.4 Resume Confirmation

Customer.io ID: 12 Trigger: AFTER INSERT on raw_ops.subscription_actions (action_type = 'resume') Timing: Immediate Category: Transactional

Subject: Welcome back Preview text: Your subscription is active. Next delivery scheduled.


Hi {{customer.first_name}},

Welcome back. Your subscription is now active.

Next delivery {{subscription.box_size}} box, £{{subscription.price}} Ships: {{subscription.new_ship_date}} Charged: {{subscription.new_billing_date}}

You can adjust your delivery anytime from your portal.

Manage your subscription

Any questions, just reply.

Protocol Raw


CTA: Manage your subscription -> {{portal_url}}

5.5 Cancellation Confirmation

Customer.io ID: 13 Trigger: AFTER INSERT on raw_ops.subscription_actions (action_type = 'cancel') Timing: Immediate Category: Transactional

Subject: Subscription cancelled Preview text: You won't be charged again.


Hi {{customer.first_name}},

Your subscription has been cancelled. You won't be charged again.

If you decide to return in the future, we can help you pick up where you left off at protocolraw.co.uk.

Any questions, just reply.

Protocol Raw


Notes: No CTA button. Door left open with continuity language.

Also triggers: create_cancellation_event inserts subscription_cancelled lifecycle event (fires win-back campaign) and applies £10 win-back credit via create_customer_credit (source_type: cancellation_winback, 10-year expiry, one per customer, idempotent).

5.6 Box Size Change

Customer.io ID: 14 Trigger: AFTER INSERT on raw_ops.subscription_actions (action_type = 'box_change') Timing: Immediate Category: Transactional

Subject: Box size updated Preview text: Now {{subscription.box_size}} box, £{{subscription.price}}.


Hi {{customer.first_name}},

Your box size has been updated.

Your plan {{subscription.box_size}} box, £{{subscription.price}} Next delivery: {{subscription.new_ship_date}}

This applies from your next delivery. You can adjust anytime from your portal.

Manage your subscription

Any questions, just reply.

Protocol Raw


CTA: Manage your subscription -> {{portal_url}}

5.7 Frequency Change

Customer.io ID: 15 Trigger: AFTER INSERT on raw_ops.subscription_actions (action_type = 'frequency_change') Timing: Immediate Category: Transactional

Subject: Delivery schedule updated Preview text: Now every {{subscription.frequency_weeks}} weeks.


Hi {{customer.first_name}},

Your delivery schedule has been updated.

Your plan {{subscription.box_size}} box Every {{subscription.frequency_weeks}} weeks Next delivery: {{subscription.new_ship_date}}

This applies from your next delivery. You can adjust anytime from your portal.

Manage your subscription

Any questions, just reply.

Protocol Raw


CTA: Manage your subscription -> {{portal_url}}

5.8 Address Change

Customer.io ID: 16 Trigger: AFTER INSERT on raw_ops.subscription_actions (action_type = 'address_change') Timing: Immediate Category: Transactional

Subject: Delivery address updated Preview text: All future deliveries will go to your new address.


Hi {{customer.first_name}},

Your delivery address has been updated.

New delivery address {{customer.address1}} {{customer.city}} {{customer.zip}}

All future deliveries will go to this address.

If this wasn't you, please reply straight away.

Manage your subscription

Protocol Raw


CTA: Manage your subscription -> {{portal_url}}

Notes: Security line replaces "Any questions, just reply." Address formatted UK-style (postcode on own line, no comma).


6. Win-Back Sequence

6.1 Win-back 1 (30 Days)

Customer.io Campaign: Win-Back Sequence Trigger event: subscription_cancelled (via lifecycle events pipeline) Timing: +30 days after cancellation Category: Lifecycle From: Protocol Raw / hello@protocolraw.co.uk

Subject: You've got £10 credit on your account Preview text: If you'd like to restart, we've made it simple.


Ready when you are

Hi {{first_name}},

If you'd like to restart, there's £10 credit waiting on your account.

Protocol Raw is still the same simple setup: one complete recipe, the exact amount {% if dog_name %}{{dog_name}}{% else %}your dog{% endif %} needs, delivered on schedule.

[CTA: Restart your plan -> https://protocolraw.co.uk/pages/calculator]

If anything put you off last time, reply and tell us. We'd rather fix the problem than guess.

Protocol Raw


Notes: Lifecycle structure (headline, CTA, brand sign-off). No persona. No promo code. The £10 credit is auto-applied at cancellation via create_cancellation_event (source_type: cancellation_winback). One credit per customer, idempotent. Exit condition: subscription_status = active exits the campaign (customer resubscribed).

6.2 Win-back 2 (60 Days)

Customer.io Campaign: Win-Back Sequence (same campaign as Win-back 1) Trigger event: subscription_cancelled Timing: +60 days after cancellation Category: Lifecycle From: Protocol Raw / hello@protocolraw.co.uk

Subject: Your £10 credit is still there Preview text: It doesn't expire.


Your credit is still here

Hi {{first_name}},

This is the last time we'll reach out about your subscription. The £10 credit on your account doesn't expire.

[CTA: Restart your plan -> https://protocolraw.co.uk/pages/calculator]

If now wasn't the right time, that's okay. We'll be here if things change.

Protocol Raw


Notes: After this email, silence. No win-back 3. No escalation. Same offer, no urgency. The credit genuinely does not expire (10-year TTL). Exit condition: subscription_status = active exits the campaign.


7. Calculator Abandonment Sequence

7.1 Abandonment 1h

Trigger: pg_cron send-abandonment-events-hourly (hourly) Timing: +1-2 hours after calculator completion, no order Category: Lifecycle Status: Live

Subject: {{if dog_name}}{{dog_name}}'s{{else}}Your{{end}} feeding plan Preview text: {{daily_grams}}g per day. Ready when you are.


Hi,

You calculated a feeding plan but didn't place an order. The details are saved.

Your plan {{box_size}} box, £{{discounted_price}} (£20 off) {{daily_grams}}g per day for {{if dog_name}}{{dog_name}}{{else}}your dog{{end}} Delivered every {{delivery_weeks}} weeks

Use code {{discount_code}} at checkout.

Continue your order

Protocol Raw


CTA: Continue your order -> {{product_url}}?token={{token}}

7.2 Abandonment 48h

Trigger: pg_cron send-abandonment-events-hourly (hourly) Timing: +48-50 hours after calculator completion, no order, 1h email sent Category: Lifecycle Status: Live

Subject: Still thinking it over? Preview text: Your plan is still saved.


Hi,

Your feeding plan for {{if dog_name}}{{dog_name}}{{else}}your dog{{end}} is still saved. A few things that might help:

Safety Every batch is tested by an independent UKAS-accredited lab before release. When your box arrives, scan the QR code on any pouch to see the results for your specific batch.

Transition We include a 10-day schedule with exact gram amounts. Most dogs adjust within the first week or two.

Your code {{discount_code}} is still valid for £20 off.

Continue your order

Protocol Raw


CTA: Continue your order -> {{product_url}}?token={{token}}


8. Technical Implementation

8.1 Architecture Overview

Protocol Raw uses three email dispatch patterns:

Pattern A: Immediate transactional emails Triggered by database events, sent via Customer.io Transactional API.

Portal/System action
    |
    v
Database INSERT (orders / subscription_actions / shipments)
    |
    v
AFTER INSERT trigger function (vault-backed credentials, audit logged)
    |
    v
pg_net async HTTP call to send-lifecycle-email Edge Function
    |
    v
Customer.io Transactional API

Pattern B: Scheduled operational emails Queued via pg_cron, sent via Customer.io Track API or outbox.

pg_cron job (e.g. send-delivery-emails, send-abandonment-events)
    |
    v
fn_invoke_edge_function wrapper (vault-backed, no hardcoded credentials)
    |
    v
Edge Function identifies eligible customers, sends Track events or writes to outbox
    |
    v
Customer.io Track API / email_outbox -> process-outbox

Pattern C: Campaign lifecycle emails (new in v1.3) Events queued in lifecycle_events table, processed by pg_cron, trigger Customer.io campaigns.

Database trigger (order/shipment/subscription action)
    |
    v
Event creator function (create_order_created_event / create_first_delivery_event / create_cancellation_event)
    |
    v
create_lifecycle_event (idempotent via idempotency_key, enriched with persona/name/dog)
    |
    v
raw_ops.lifecycle_events table (state: pending)
    |
    v
process-lifecycle-events pg_cron (every minute) via fn_invoke_edge_function
    |
    v
send-lifecycle-events Edge Function
    |
    v
1. Customer.io Identify call (syncs profile attributes: first_name, dog_name, persona, subscription_status, etc.)
2. Customer.io Track event (fires campaign trigger)
    |
    v
Customer.io Campaign workflow (delays, branches, sends)

8.2 Customer.io Configuration

Credentials are stored in Supabase Edge Function secrets and Vault. Never hardcode in documentation or code.

Secret Edge Function Env Var Vault Name Usage
Site ID CUSTOMERIO_SITE_ID -- Track API Basic auth
Track API Key CUSTOMERIO_API_KEY -- Track API Basic auth
App API Key CUSTOMERIO_APP_API_KEY -- Transactional API Bearer token
Service Role Key -- service_role_key Database trigger pg_net calls
Anon Key -- anon_key pg_cron Edge Function calls

8.3 Customer.io Message IDs

Email Customer.io ID Template ID
Order Confirmation (First) 6 order_confirmation_first
Order Confirmation (Repeat) 7 order_confirmation_repeat
Portal Access Magic Link 3 portal_access
Skip Confirmation 9 delivery_skipped
Reschedule Confirmation 10 delivery_rescheduled
Pause Confirmation 11 subscription_paused
Resume Confirmation 12 subscription_resumed
Cancellation Confirmation 13 subscription_cancelled
Box Size Change 14 box_size_changed
Frequency Change 15 frequency_changed
Address Change 16 address_updated
Dispatch Confirmation 17 dispatch_confirmation
Delivery Confirmation 18 delivery_confirmation

8.4 Customer.io Campaigns

Campaign Trigger Event Workflow Status
Transition Guide order_created_first Wait 1 day -> Send transition guide Built, inactive
Transition Protection first_box_delivered Wait 2h -> T1 -> Wait 2d -> Branch (active?) -> T2 -> Wait 2d -> Branch (active?) -> T3 -> Wait 3d -> Branch (active?) -> T4 Built, inactive
Pre-Box-2 Confidence pre_box2_confidence Branch (active?) -> T6 Built, inactive
[RETIRED] Onboarding Check-ins first_box_delivered (Retired. Replaced by Transition Protection.) Retired, inactive
Win-Back Sequence subscription_cancelled Wait 30d -> Win-back 1 -> Wait 30d -> Branch (not active?) -> Win-back 2 Built, inactive

Exit conditions: Transition Protection, Pre-Box-2 Confidence, and Win-Back campaigns branch on subscription_status attribute (synced via identify call in send-lifecycle-events).

8.5 Edge Functions

Function Purpose Trigger Pattern
send-lifecycle-email Send single transactional email Database trigger via pg_net A
send-lifecycle-events Process lifecycle event queue + identify pg_cron (every minute) C
process-outbox Process email outbox queue pg_cron (every minute + every 2 min) B
send-delivery-emails Send 7-day/48-hour reminders. Now includes credit data (has_credit, list_price, credit_applied, amount_due) via LEFT JOIN to customer_credits in get_pending_delivery_emails(). pg_cron (daily 09:00 UTC) B
send-abandonment-events Send calculator abandonment emails pg_cron (hourly) B
request-portal-access Send magic link on demand HTTP (portal login page) A
send-portal-access-emails Proactive portal access for new customers pg_cron (daily 10:00 UTC) B

8.6 Lifecycle Event Creator Functions

Function Trigger Event Name Additional Actions
create_order_created_event trigger_order_lifecycle_event on orders order_created_first / order_created_repeat Enriches with dog_name, box_size, portal_url, transition_url
create_first_delivery_event trigger_shipment_email on DELIVERED first_box_delivered Only fires for first delivery (COUNT = 1). Also creates pre_box2_confidence lifecycle event with scheduled_for = next_billing_date - 3 days (v1.5).
fn_reschedule_pre_box2_event_v1 Called from trigger_subscription_action_email on skip/reschedule/frequency_change/box_change/pause/resume/cancel pre_box2_confidence Updates scheduled_for on pending event, or deletes on pause/cancel.
create_cancellation_event trigger_subscription_action_email on cancel subscription_cancelled Creates £10 win-back credit (idempotent, one per customer)

8.7 Customer.io Profile Attribute Syncing

send-lifecycle-events performs a Customer.io Identify call before every Track event. This syncs profile attributes from the lifecycle event data:

Attribute Source Set by events
first_name customers table All events
dog_name calculator_discounts/dogs All events
persona Deterministic hash All events
portal_url Static All events
transition_url Static deep link order_created_first, first_box_delivered
box_size subscription/order All events
subscription_status Derived from event name order_created -> active, paused -> paused, cancelled -> cancelled, resumed -> active
timezone Default Europe/London All events

8.8 Database Triggers

All triggers use AFTER INSERT (not BEFORE INSERT). All use vault-backed credentials via vault.decrypted_secrets. All log to raw_ops.ops_events.

Trigger Table Events Functions Called
trg_order_confirmation raw_ops.orders AFTER INSERT trigger_order_confirmation -> send-lifecycle-email
trg_order_lifecycle_event raw_ops.orders AFTER INSERT/UPDATE trigger_order_lifecycle_event -> create_order_created_event -> send-lifecycle-events
trg_subscription_action_email raw_ops.subscription_actions AFTER INSERT trigger_subscription_action_email -> send-lifecycle-email + create_cancellation_event (on cancel)
trg_shipment_email raw_ops.shipments AFTER INSERT/UPDATE trigger_shipment_email -> send-lifecycle-email + create_first_delivery_event (on first DELIVERED)

8.9 pg_cron Jobs

All Edge Function calls route through fn_invoke_edge_function (vault-backed, no hardcoded credentials).

Job Schedule Function/Edge Function Purpose
process-lifecycle-events Every minute send-lifecycle-events Process pending lifecycle events
retry-lifecycle-events Every 5 min retry_failed_lifecycle_events() Retry failed events
process-outbox-every-minute Every minute process-outbox Process email outbox
process-outbox-every-2-min Every 2 min process-outbox Secondary outbox processing
send-delivery-emails Daily 09:00 UTC send-delivery-emails 7-day and 48-hour reminders
send-abandonment-events-hourly Hourly send-abandonment-events Calculator abandonment
send-portal-access-emails Daily 10:00 UTC send-portal-access-emails Proactive portal access
fallback-first-delivery-events Daily 07:00 UTC fn_fallback_first_delivery_events_v1() Safety net for missed DPD delivery scans
canary-system-health Every 10 min fn_canary_system_health_v1() Independent system health check

8.10 Portal Integration

Portal Status Flow
Ops Portal Wired Calls log_subscription_action -> AFTER INSERT trigger -> transactional email + lifecycle event
Customer Portal Wired (v1.3) Calls log_subscription_action via Supabase RPC (non-blocking) before Make.com webhook

8.11 Email Outbox (Pattern B)

Table: raw_ops.email_outbox

State machine:

State Meaning Next
pending Queued, not yet sent dispatched on success, stays pending on failure
dispatched Customer.io accepted Terminal success
failed Max attempts (5) exhausted Terminal failure, critical alert

Exponential backoff: Immediate, 5 min, 15 min, 1 hour, 6 hours.

8.12 Lifecycle Events Queue (Pattern C)

Table: raw_ops.lifecycle_events

Key features: idempotency via idempotency_key (unique), status tracking (pending/processing/sent/failed), retry with attempts/max_attempts, scheduled_for for delayed sends, error history, Customer.io delivery tracking.

Writer: create_lifecycle_event() enriches with customer persona/name, generates idempotency key, inserts with ON CONFLICT DO NOTHING.

8.13 Monitoring and Observability

Standard monitors (via SOP-MON-01 pattern):

Monitor Schedule Channel Thresholds
monitor-outbox-health Every 2 min #ops-alerts / #ops-urgent Warning: stuck > 10 or pending > 50. Critical: stuck > 50 or pending > 200
monitor-lifecycle-events Every 15 min #ops-alerts Lifecycle event processing lag

Independent canary monitor (does NOT use fn_invoke_edge_function):

Monitor Schedule Channel Purpose
canary-system-health Every 10 min #ops-urgent Detects if fn_invoke_edge_function or critical pg_cron jobs are down. Own vault lookup, own pg_net call, completely independent alert path.

Canary checks: process-outbox-every-minute, process-lifecycle-events, monitor-outbox-health. If any haven't succeeded in 15 minutes, fires alert to #ops-urgent via independent path. Logs to raw_ops.canary_alerts table.

Business performance metrics:

Metric Target Alert Threshold
Box-2 retention >=70% <65%
Unsubscribe rate <0.5% >1%
Spam complaint rate <0.1% >0.2%
Reply rate (check-ins) >5% <2%

Email performance targets:

Email Type Open Rate Click Rate
Transactional 70%+ N/A
Operational 60%+ 20%+
Lifecycle 50%+ 15%+
Win-back 30%+ 10%+
Abandonment 40%+ 15%+

8.14 Error Handling and Resilience

Pattern A (immediate): pg_net is async and non-blocking. Edge Function failures don't block database commits. Manual resend: call send-lifecycle-email directly. No automatic retry (acceptable at Phase A volumes).

Pattern B (outbox): Retry up to 5 times with exponential backoff. After 5 failures: state -> failed, critical alert to #ops-urgent. Manual replay: UPDATE raw_ops.email_outbox SET state = 'pending', attempts = 0 WHERE id = '<uuid>';

Pattern C (lifecycle events): Retry via retry-lifecycle-events pg_cron job (every 5 min). Idempotency via unique idempotency_key prevents duplicate sends. send-lifecycle-events identify errors are logged but don't fail the track event.

System-level resilience: Independent canary monitor (fn_canary_system_health_v1) detects infrastructure failures within 15 minutes via a completely separate alert path. This prevents silent outages like the 26-hour fn_invoke_edge_function failure.


9. Design Standards

All emails follow the Protocol Raw Email Design System v1.3.

9.1 Quick Reference

Element Standard
Background Cream #F9F7F4 (applies to card area too)
Body text Espresso #2B2523, 16px Inter
Headlines Espresso #2B2523, 24px Montserrat Bold
Links Burnt Sienna #B85C3A
Primary CTA Burnt Sienna #B85C3A background, white text
Info cards Warm Linen #EBE8E3 background
Max width 600px
Header 4px accent line (Burnt Sienna or Forest Green) + "Protocol Raw" wordmark below
Font stacks Montserrat, Trebuchet MS, Arial, sans-serif (headlines). Inter, -apple-system, BlinkMacSystemFont, Segoe UI, Arial, sans-serif (body)
Hidden preheader Required on all emails. Matches preview text field in Customer.io

9.2 Sign-off Rules

Email Type Sign-off Layout
Transactional Protocol Raw Utility
Operational Protocol Raw Utility
Proof/Delivery Protocol Raw Proof
Lifecycle (standard) Protocol Raw Utility
Lifecycle (relational) {{persona}}, Protocol Raw Relational
Lifecycle (founder, Phase A) Anton, Founder, Protocol Raw None / Support
Support (body contains sign-off) Support

9.3 Warmth Pattern

All transactional emails end with "Any questions, just reply." except: - Cancellation (door-open language instead) - Address Change (security line instead)


10. What NOT to Email

Silence is sometimes the stronger move. We do NOT send:

  • Week 3 or Week 4 check-ins (founder email at day 10 is the last onboarding touch)
  • Win-back 3+ (two touches maximum, then silence)
  • "We miss you" or guilt-based messaging
  • Promotional emails or sales
  • Newsletter/content marketing (not a newsletter brand)
  • Survey requests (unless specific research need)
  • NPS scores (doesn't fit brand)
  • Birthday emails (don't collect DOB)
  • Anniversary emails (feels performative)
  • Promo codes in win-back emails (use auto-applied account credit instead)

11. Version History

Version Date Changes
1.0 January 2026 Initial release. Consolidated all lifecycle email copy.
1.1 February 2026 Implemented transactional emails in Customer.io. Added database trigger architecture. Updated copy with warmth pass. Added Box Size, Frequency, Address change emails. Documented Customer.io IDs and Edge Function.
1.2 March 2026 Security: redacted hardcoded Customer.io credentials. Architecture: rewrote Section 8 to document actual production system (outbox pattern, lifecycle events pipeline, monitoring). Fixed BEFORE INSERT trigger to AFTER INSERT. Added monitoring section and error handling section. Corrected document status. Fixed UTF-8 encoding.
1.3 March 2026 All lifecycle campaigns built in Customer.io (Transition Guide, Onboarding Check-ins, Win-Back Sequence). Revised copy: transition guide single-purpose, check-ins delivery-anchored with affirming language, win-back redesigned as lifecycle structure with auto-applied £10 credit (no promo codes). Added Pattern C (lifecycle events pipeline with identify + track). Documented all new infrastructure: create_first_delivery_event, create_cancellation_event, fn_invoke_edge_function wrapper, canary monitor, delivery fallback job. Customer Portal wired to log_subscription_action. Confirmed 7-day/48-hour and dispatch/delivery emails as live. End-to-end pipeline verified.
1.5 April 2026 Transition Protection sequence added (T1-T4, T6), replacing Week 1/Week 2 onboarding check-ins. T1 Arrival Guide (Day 0, Utility), T2 Day 2 Check-in (Day 2, Relational), T3 Day 4 Normalisation (Day 4, Relational), T4 Day 7 Milestone (Day 7, Relational), T6 Pre-Box-2 Confidence (cadence-aware, Utility). Founder check-in (T5, Day 10) unchanged. Old Onboarding Check-ins campaign retired. New campaigns: Transition Protection (trigger: first_box_delivered) and Pre-Box-2 Confidence (trigger: pre_box2_confidence). Pre-Box-2 event created by extended create_first_delivery_event, rescheduled by new fn_reschedule_pre_box2_event_v1. Golden Path metrics remapped in Growth Playbook v2.4.
1.4 April 2026 Email Design System v1.3 migration. All templates moved to Layouts (Utility, Proof, Relational, Support). Founder email replaces Week 1/Week 2 check-ins for Phase A (day 10, manual from Gmail). Week 2 check-in dropped. Credit breakdown added to 7-day and 48-hour delivery reminders. Win-back campaign built (2-email sequence, 30-day spacing). Orphaned campaigns deleted (delivery_reminder_7day, delivery_lock_notification). All CTA buttons replaced with inline portal links except payment, dispatch, tracking, and proof emails.

  • Email Design System v1.3: Visual and copy standards
  • SOP-EMAIL-01: Email architecture and sender configuration
  • SOP-CS-00: Customer Operations System (email matrix, triggers)
  • SOP-REF-01: Referral System (referral/credit emails, credit system)
  • SOP-SUB-00: Subscription State Management (dunning/card expiry emails)
  • SOP-DLV-01: Courier Watchdog (delivery exception emails)
  • SOP-MON-01: Monitoring and Alerting Architecture (outbox health, lifecycle events monitoring)
  • Technical Operations Handbook v1.1: Complete pg_cron job registry and Edge Function inventory
  • Abandonment Recovery System: Technical implementation for calculator abandonment

13. Launch Activation Checklist

Before activating campaigns at launch:

  • [ ] Activate Transition Guide campaign in Customer.io
  • [ ] Activate Onboarding Check-ins campaign in Customer.io
  • [ ] Activate Win-Back Sequence campaign in Customer.io
  • [ ] Verify subscription_status is flowing to Customer.io profiles (test with a subscription action)
  • [ ] Verify persona is set on test customer profiles (not falling back to default)
  • [ ] Confirm canary-system-health pg_cron job is active and has recent successful runs
  • [ ] Confirm fallback-first-delivery-events pg_cron job is active
  • [ ] Send test emails through each campaign to verify rendering in Gmail, Apple Mail, Outlook
  • [ ] Activate Transition Protection campaign in Customer.io
  • [ ] Activate Pre-Box-2 Confidence campaign in Customer.io
  • [ ] Verify fn_reschedule_pre_box2_event_v1 is deployed and tested
  • [ ] Verify pre_box2_confidence event is created on test first delivery
  • [ ] Verify pre_box2_confidence event is rescheduled on test subscription change
  • [ ] Send test emails through Transition Protection campaign (T1-T4) to verify rendering
  • [ ] Send test email through Pre-Box-2 Confidence campaign (T6) to verify rendering

End of SOP-LC-01 v1.5