mango-lollipop 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +69 -0
- package/LICENSE +21 -0
- package/README.md +264 -0
- package/bin/mango-lollipop.js +385 -0
- package/dist/excel.d.ts +4 -0
- package/dist/excel.js +342 -0
- package/dist/html.d.ts +4 -0
- package/dist/html.js +938 -0
- package/dist/schema.d.ts +120 -0
- package/dist/schema.js +211 -0
- package/lib/excel.ts +433 -0
- package/lib/html.ts +993 -0
- package/lib/schema.ts +394 -0
- package/package.json +44 -0
- package/skills/audit/SKILL.md +248 -0
- package/skills/dev-handoff/SKILL.md +295 -0
- package/skills/generate-dashboard/SKILL.md +195 -0
- package/skills/generate-matrix/SKILL.md +374 -0
- package/skills/generate-messages/SKILL.md +262 -0
- package/skills/iterate/SKILL.md +242 -0
- package/skills/start/SKILL.md +310 -0
- package/templates/copywriting-guide.md +155 -0
- package/templates/dashboard.html +522 -0
- package/templates/events/saas-collaboration.yaml +50 -0
- package/templates/events/saas-document.yaml +44 -0
- package/templates/events/saas-general.yaml +38 -0
- package/templates/events/saas-marketplace.yaml +48 -0
- package/templates/overview.html +598 -0
- package/templates/saas-matrix.json +172 -0
|
@@ -0,0 +1,374 @@
|
|
|
1
|
+
# Generate Lifecycle Matrix
|
|
2
|
+
|
|
3
|
+
You are a lifecycle messaging architect. Your job is to build the complete messaging matrix from a business analysis, producing a structured JSON file that contains every message the business needs -- both transactional and lifecycle.
|
|
4
|
+
|
|
5
|
+
## CRITICAL RULE: One Channel Per Message
|
|
6
|
+
|
|
7
|
+
**Every message entry must target exactly ONE channel.** Email and in-app are different mediums with different copy, length, and format. Never combine them.
|
|
8
|
+
|
|
9
|
+
If a logical message should go out on both email and in-app, create **two separate entries** with:
|
|
10
|
+
- Different IDs (e.g., `AQ-01` for email, `AQ-02` for in-app)
|
|
11
|
+
- The same trigger and wait
|
|
12
|
+
- `"channel": "email"` or `"channel": "in-app"` (singular, not an array)
|
|
13
|
+
- Different names that reflect the channel (e.g., "Welcome Email" vs "Welcome In-App")
|
|
14
|
+
|
|
15
|
+
This applies to all stages. Sequential IDs are assigned per stage across all channels -- do not use suffixes like `AQ-01a`.
|
|
16
|
+
|
|
17
|
+
## Input
|
|
18
|
+
|
|
19
|
+
Read `analysis.json` from the project output directory. This file was produced by the `start` skill and contains the company profile, channels, voice, events, tags, and (for PATH B) existing messages.
|
|
20
|
+
|
|
21
|
+
Also read `templates/copywriting-guide.md` for proven sequence patterns. Use the "Applying Patterns to AARRR Stages" section to choose the best pattern for each stage based on the product type. For example, use the Guided Training pattern for activation if the product has a clear step-by-step workflow, or the Progress Milestones pattern if the product has a checklist-style onboarding.
|
|
22
|
+
|
|
23
|
+
Locate the project output directory by:
|
|
24
|
+
1. Checking the current working directory for `analysis.json`
|
|
25
|
+
2. Checking `output/*/analysis.json`
|
|
26
|
+
3. If not found, ask the user where their project directory is
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## Step 1: Generate Transactional Messages (TX) -- Always
|
|
31
|
+
|
|
32
|
+
Transactional messages are non-negotiable. Every SaaS product needs them. Generate these regardless of path (fresh or existing):
|
|
33
|
+
|
|
34
|
+
### TX-01: Email Verification
|
|
35
|
+
- **trigger:** `{ "event": "user.signed_up", "type": "event" }`
|
|
36
|
+
- **wait:** `"P0D"` (instant)
|
|
37
|
+
- **channel:** `"email"` (always email, even if user didn't select email -- verification requires it)
|
|
38
|
+
- **tags:** `["type:transactional"]`
|
|
39
|
+
- **guards:** none
|
|
40
|
+
- **suppressions:** none
|
|
41
|
+
- **classification:** `"transactional"`
|
|
42
|
+
- **from:** Team / system
|
|
43
|
+
- **format:** `"plain"`
|
|
44
|
+
|
|
45
|
+
### TX-02: Password Reset
|
|
46
|
+
- **trigger:** `{ "event": "user.password_reset_requested", "type": "event" }`
|
|
47
|
+
- **wait:** `"P0D"`
|
|
48
|
+
- **channel:** `"email"`
|
|
49
|
+
- **tags:** `["type:transactional"]`
|
|
50
|
+
- **classification:** `"transactional"`
|
|
51
|
+
|
|
52
|
+
### TX-03: Payment Receipt
|
|
53
|
+
- **trigger:** `{ "event": "subscription.payment_processed", "type": "event" }`
|
|
54
|
+
- **wait:** `"P0D"`
|
|
55
|
+
- **channel:** `"email"`
|
|
56
|
+
- **tags:** `["type:transactional"]`
|
|
57
|
+
- **classification:** `"transactional"`
|
|
58
|
+
|
|
59
|
+
### TX-04: Plan Change Confirmation
|
|
60
|
+
- **trigger:** `{ "event": "subscription.changed", "type": "event" }`
|
|
61
|
+
- **wait:** `"P0D"`
|
|
62
|
+
- **channel:** `"email"`
|
|
63
|
+
- **tags:** `["type:transactional"]`
|
|
64
|
+
- **classification:** `"transactional"`
|
|
65
|
+
|
|
66
|
+
### TX-05: Account Deletion Confirmation
|
|
67
|
+
- **trigger:** `{ "event": "user.deletion_requested", "type": "event" }`
|
|
68
|
+
- **wait:** `"P0D"`
|
|
69
|
+
- **channel:** `"email"`
|
|
70
|
+
- **tags:** `["type:transactional"]`
|
|
71
|
+
- **classification:** `"transactional"`
|
|
72
|
+
|
|
73
|
+
Only generate TX messages for channels the user selected, **except** TX-01 (email verification) which is always email.
|
|
74
|
+
|
|
75
|
+
Tag all with `type:transactional`.
|
|
76
|
+
|
|
77
|
+
---
|
|
78
|
+
|
|
79
|
+
## Step 2: Generate Lifecycle Messages -- AARRR Framework
|
|
80
|
+
|
|
81
|
+
Use the SaaS template as the foundation, customized with data from `analysis.json`. Only include channels listed in `analysis.channels`.
|
|
82
|
+
|
|
83
|
+
### Acquisition (AQ)
|
|
84
|
+
|
|
85
|
+
For each logical message in this stage, create a **separate entry for each channel** in `analysis.channels`. For example, if the user selected email + in-app, a welcome message becomes two entries: one email, one in-app.
|
|
86
|
+
|
|
87
|
+
**Welcome Message (email: AQ-01, in-app: AQ-02)**
|
|
88
|
+
- **trigger:** `{ "event": "user.email_verified", "type": "event" }`
|
|
89
|
+
- **wait:** `"PT5M"` (5 minutes after verification -- gives time for redirect)
|
|
90
|
+
- **channel:** One of the channels from `analysis.channels`
|
|
91
|
+
- **guards:** none
|
|
92
|
+
- **suppressions:** none
|
|
93
|
+
- **tags:** `["type:educational"]`
|
|
94
|
+
- **from:** CEO/founder persona from `analysis.voice.sender_personas`
|
|
95
|
+
- **goal:** "Onboarding start, set expectations"
|
|
96
|
+
- **content notes:** USP summary, quick-start CTA, support info, warm personal tone. In-app version should be much shorter and punchier than email.
|
|
97
|
+
|
|
98
|
+
**Getting Started Guide (email only)**
|
|
99
|
+
- **trigger:** `{ "event": "user.email_verified", "type": "event" }`
|
|
100
|
+
- **wait:** `"P1D"` (1 day after verification)
|
|
101
|
+
- **channel:** `"email"`
|
|
102
|
+
- **guards:** `[{ "condition": "User has not completed onboarding", "expression": "onboarding.completed == false" }]`
|
|
103
|
+
- **suppressions:** `[{ "condition": "User already active", "expression": "user.sessions_count >= 3" }]`
|
|
104
|
+
- **tags:** `["type:educational"]`
|
|
105
|
+
- **goal:** "Drive first meaningful action"
|
|
106
|
+
|
|
107
|
+
### Activation (AC) -- THE CORE SEQUENCE
|
|
108
|
+
|
|
109
|
+
This is the most important stage. Generate one **logical** message per key feature from `analysis.company.key_features`. Each introduces ONE feature.
|
|
110
|
+
|
|
111
|
+
**IMPORTANT:** For each feature, create a separate entry per channel in `analysis.channels`. If the user has email + in-app, each feature produces TWO entries. Assign sequential IDs across all entries (e.g., AC-01 email, AC-02 in-app, AC-03 email, AC-04 in-app...).
|
|
112
|
+
|
|
113
|
+
For each feature at index `i` (0-based) in `analysis.company.key_features`, for each channel:
|
|
114
|
+
|
|
115
|
+
**Feature introduction for `{feature_name}`**
|
|
116
|
+
- **trigger:** `{ "event": "user.email_verified", "type": "event" }` (all start from the same anchor)
|
|
117
|
+
- **wait:** Staggered cadence: `["P2D", "P3D", "P5D", "P7D", "P10D", "P14D"]` (use feature index to select -- both channel variants of the same feature share the same wait)
|
|
118
|
+
- **channel:** `"email"` or `"in-app"` (one per entry)
|
|
119
|
+
- **guards:** `[{ "condition": "User has not cancelled", "expression": "user.plan != 'cancelled'" }]`
|
|
120
|
+
- **suppressions:** `[{ "condition": "User already used this feature", "expression": "feature.{feature_name}_used == true" }]` -- this is critical: do not nag about features they already discovered
|
|
121
|
+
- **tags:** `["type:educational", "feature:{feature_name}"]`
|
|
122
|
+
- **from:** Product persona from `analysis.voice.sender_personas`
|
|
123
|
+
- **goal:** "Drive first use of {feature_name}"
|
|
124
|
+
- **format:** `"plain"` for email, `"plain"` for in-app
|
|
125
|
+
- **content notes:** Email version is longer, educational, storytelling. In-app version is a short nudge (1-2 sentences max with a CTA button).
|
|
126
|
+
|
|
127
|
+
### Revenue (RV)
|
|
128
|
+
|
|
129
|
+
For messages that need both email and in-app, create separate entries per channel with sequential IDs.
|
|
130
|
+
|
|
131
|
+
**Trial Ending Soon (email + in-app if available)**
|
|
132
|
+
- **trigger:** `{ "event": "trial.ending_soon", "type": "event" }`
|
|
133
|
+
- **wait:** `"P0D"` (instant on trigger, which fires ~3 days before expiry)
|
|
134
|
+
- **channel:** One entry per channel
|
|
135
|
+
- **guards:** `[{ "condition": "User is on trial", "expression": "user.plan == 'trial'" }]`
|
|
136
|
+
- **tags:** `["type:promotional", "segment:trial"]`
|
|
137
|
+
- **goal:** "Convert trial to paid"
|
|
138
|
+
|
|
139
|
+
**Trial Expired (email only)**
|
|
140
|
+
- **trigger:** `{ "event": "trial.expired", "type": "event" }`
|
|
141
|
+
- **wait:** `"P0D"`
|
|
142
|
+
- **channel:** `"email"`
|
|
143
|
+
- **guards:** `[{ "condition": "User has not upgraded", "expression": "user.plan != 'paid'" }]`
|
|
144
|
+
- **tags:** `["type:promotional", "segment:trial"]`
|
|
145
|
+
- **goal:** "Win back expired trial user"
|
|
146
|
+
|
|
147
|
+
**Usage Limit Approaching (in-app preferred, email fallback)**
|
|
148
|
+
- **trigger:** `{ "event": "usage.limit_approaching", "type": "event" }`
|
|
149
|
+
- **wait:** `"P0D"`
|
|
150
|
+
- **channel:** One entry per channel
|
|
151
|
+
- **tags:** `["type:behavioral"]`
|
|
152
|
+
- **goal:** "Drive upgrade via natural usage growth"
|
|
153
|
+
|
|
154
|
+
### Retention (RT)
|
|
155
|
+
|
|
156
|
+
For re-engagement messages, create separate entries per channel where applicable.
|
|
157
|
+
|
|
158
|
+
**Usage Recap (email only)**
|
|
159
|
+
- **trigger:** `{ "event": "scheduled", "type": "scheduled", "schedule": "every friday 9am" }`
|
|
160
|
+
- **wait:** `"P0D"`
|
|
161
|
+
- **channel:** `"email"`
|
|
162
|
+
- **guards:** `[{ "condition": "User was active this week", "expression": "user.sessions_this_week >= 1" }]`
|
|
163
|
+
- **tags:** `["type:behavioral"]`
|
|
164
|
+
- **goal:** "Reinforce value, drive continued usage"
|
|
165
|
+
|
|
166
|
+
**Inactive 3 Days (email + in-app if available)**
|
|
167
|
+
- **trigger:** `{ "event": "user.inactive_3_days", "type": "behavioral" }`
|
|
168
|
+
- **wait:** `"P0D"`
|
|
169
|
+
- **channel:** One entry per channel
|
|
170
|
+
- **tags:** `["type:behavioral", "segment:dormant"]`
|
|
171
|
+
- **goal:** "Re-engage before habit breaks"
|
|
172
|
+
|
|
173
|
+
**Inactive 7 Days (email only)**
|
|
174
|
+
- **trigger:** `{ "event": "user.inactive_7_days", "type": "behavioral" }`
|
|
175
|
+
- **wait:** `"P0D"`
|
|
176
|
+
- **channel:** `"email"`
|
|
177
|
+
- **tags:** `["type:behavioral", "segment:dormant"]`
|
|
178
|
+
- **goal:** "Show value they're missing"
|
|
179
|
+
|
|
180
|
+
**Inactive 14 Days (email only)**
|
|
181
|
+
- **trigger:** `{ "event": "user.inactive_14_days", "type": "behavioral" }`
|
|
182
|
+
- **wait:** `"P0D"`
|
|
183
|
+
- **channel:** `"email"`
|
|
184
|
+
- **tags:** `["type:behavioral", "segment:churning", "priority:high"]`
|
|
185
|
+
- **goal:** "Last attempt before marking churned"
|
|
186
|
+
|
|
187
|
+
### Referral (RF)
|
|
188
|
+
|
|
189
|
+
**Invite Teammates (email + in-app if available)**
|
|
190
|
+
- **trigger:** `{ "event": "milestone.first_success", "type": "event" }`
|
|
191
|
+
- **wait:** `"P1D"` (1 day after milestone -- let them enjoy the win first)
|
|
192
|
+
- **channel:** One entry per channel
|
|
193
|
+
- **tags:** `["type:promotional"]`
|
|
194
|
+
- **goal:** "Organic growth via team invites"
|
|
195
|
+
|
|
196
|
+
**Referral Program Introduction (email only)**
|
|
197
|
+
- **trigger:** `{ "event": "user.active_30_days", "type": "behavioral" }`
|
|
198
|
+
- **wait:** `"P0D"`
|
|
199
|
+
- **channel:** `"email"`
|
|
200
|
+
- **tags:** `["type:promotional"]`
|
|
201
|
+
- **goal:** "Turn power users into advocates"
|
|
202
|
+
|
|
203
|
+
---
|
|
204
|
+
|
|
205
|
+
## Step 3: PATH B Handling -- Existing Messages
|
|
206
|
+
|
|
207
|
+
If `analysis.path == "existing"` and `analysis.existing` is populated:
|
|
208
|
+
|
|
209
|
+
1. **Map existing messages** into the AARRR framework. For each existing message in `analysis.existing.messages`, determine which AARRR stage and message slot it corresponds to.
|
|
210
|
+
|
|
211
|
+
2. **Keep messages that are performing well.** If an existing message has good performance (open rate > 25%, click rate > 3%, or marked as "good" in assessment), preserve it. Set `origin: "existing"`.
|
|
212
|
+
|
|
213
|
+
3. **Improve messages with poor performance.** If an existing message has poor metrics or is marked for improvement, keep the original structure but flag it for rewriting. Set `origin: "improved"`.
|
|
214
|
+
|
|
215
|
+
4. **Fill gaps with new messages.** For any AARRR stage or message slot not covered by existing messages, generate new ones using the templates above. Set `origin: "new"`.
|
|
216
|
+
|
|
217
|
+
5. **Mark every message** with an `origin` field:
|
|
218
|
+
- `"existing"` -- Kept as-is from their current system
|
|
219
|
+
- `"improved"` -- Based on an existing message but rewritten/enhanced
|
|
220
|
+
- `"new"` -- Entirely new message to fill a gap
|
|
221
|
+
|
|
222
|
+
Prioritize improvements based on `analysis.existing.primary_goal`.
|
|
223
|
+
|
|
224
|
+
---
|
|
225
|
+
|
|
226
|
+
## Step 4: Apply Tags
|
|
227
|
+
|
|
228
|
+
Apply tags from `analysis.tags` to each message:
|
|
229
|
+
|
|
230
|
+
- All TX messages get `type:transactional`
|
|
231
|
+
- Feature-related AC messages get `feature:{feature_name}`
|
|
232
|
+
- Trial-related RV messages get `segment:trial`
|
|
233
|
+
- Re-engagement RT messages get `segment:dormant` or `segment:churning`
|
|
234
|
+
- Any message targeting specific plans gets `plan:{plan_name}`
|
|
235
|
+
|
|
236
|
+
Add any custom tags suggested in `analysis.tags` that are relevant.
|
|
237
|
+
|
|
238
|
+
---
|
|
239
|
+
|
|
240
|
+
## Output: matrix.json
|
|
241
|
+
|
|
242
|
+
Write `matrix.json` to the project output directory (same directory as `analysis.json`).
|
|
243
|
+
|
|
244
|
+
Structure:
|
|
245
|
+
|
|
246
|
+
```json
|
|
247
|
+
{
|
|
248
|
+
"version": "1.0",
|
|
249
|
+
"generated_at": "2025-01-15T10:30:00Z",
|
|
250
|
+
"project": "company-name",
|
|
251
|
+
"path": "fresh | existing",
|
|
252
|
+
"channels": ["email", "in-app"],
|
|
253
|
+
"messages": [
|
|
254
|
+
{
|
|
255
|
+
"id": "TX-01",
|
|
256
|
+
"stage": "TX",
|
|
257
|
+
"name": "Verify Your Email",
|
|
258
|
+
"classification": "transactional",
|
|
259
|
+
"trigger": {
|
|
260
|
+
"event": "user.signed_up",
|
|
261
|
+
"type": "event"
|
|
262
|
+
},
|
|
263
|
+
"wait": "P0D",
|
|
264
|
+
"guards": [],
|
|
265
|
+
"suppressions": [],
|
|
266
|
+
"channel": "email",
|
|
267
|
+
"cta": {
|
|
268
|
+
"text": "Verify email",
|
|
269
|
+
"url": "/verify?token={{token}}"
|
|
270
|
+
},
|
|
271
|
+
"segment": "All new signups",
|
|
272
|
+
"tags": ["type:transactional"],
|
|
273
|
+
"format": "plain",
|
|
274
|
+
"from": "Team",
|
|
275
|
+
"goal": "Email verification",
|
|
276
|
+
"comments": "Must be instant. Include verification link with 24h expiry.",
|
|
277
|
+
"origin": "new"
|
|
278
|
+
},
|
|
279
|
+
{
|
|
280
|
+
"id": "AQ-01",
|
|
281
|
+
"stage": "AQ",
|
|
282
|
+
"name": "Welcome Email",
|
|
283
|
+
"classification": "lifecycle",
|
|
284
|
+
"trigger": {
|
|
285
|
+
"event": "user.email_verified",
|
|
286
|
+
"type": "event"
|
|
287
|
+
},
|
|
288
|
+
"wait": "PT5M",
|
|
289
|
+
"guards": [],
|
|
290
|
+
"suppressions": [],
|
|
291
|
+
"channel": "email",
|
|
292
|
+
"cta": {
|
|
293
|
+
"text": "Start your first {action}",
|
|
294
|
+
"url": "/app/get-started"
|
|
295
|
+
},
|
|
296
|
+
"segment": "All verified users",
|
|
297
|
+
"tags": ["type:educational"],
|
|
298
|
+
"format": "plain",
|
|
299
|
+
"from": "Jakob, CEO",
|
|
300
|
+
"goal": "Onboarding start",
|
|
301
|
+
"comments": "Warm, personal. USP summary + quick-start CTA.",
|
|
302
|
+
"origin": "new"
|
|
303
|
+
},
|
|
304
|
+
{
|
|
305
|
+
"id": "AQ-02",
|
|
306
|
+
"stage": "AQ",
|
|
307
|
+
"name": "Welcome In-App",
|
|
308
|
+
"classification": "lifecycle",
|
|
309
|
+
"trigger": {
|
|
310
|
+
"event": "user.email_verified",
|
|
311
|
+
"type": "event"
|
|
312
|
+
},
|
|
313
|
+
"wait": "PT5M",
|
|
314
|
+
"guards": [],
|
|
315
|
+
"suppressions": [],
|
|
316
|
+
"channel": "in-app",
|
|
317
|
+
"cta": {
|
|
318
|
+
"text": "Get started",
|
|
319
|
+
"url": "/app/get-started"
|
|
320
|
+
},
|
|
321
|
+
"segment": "All verified users",
|
|
322
|
+
"tags": ["type:educational"],
|
|
323
|
+
"format": "plain",
|
|
324
|
+
"from": "Jakob, CEO",
|
|
325
|
+
"goal": "Onboarding start",
|
|
326
|
+
"comments": "Short nudge. 1-2 sentences + CTA button.",
|
|
327
|
+
"origin": "new"
|
|
328
|
+
}
|
|
329
|
+
]
|
|
330
|
+
}
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
Each message object must include ALL fields from the Message schema: `id`, `stage`, `name`, `classification`, `trigger`, `wait`, `guards`, `suppressions`, `channel`, `cta`, `segment`, `tags`, `format`, `from`, `goal`, `comments`, and `origin`.
|
|
334
|
+
|
|
335
|
+
**Remember: `channel` is singular (a string), not `channels` (an array).** Every entry targets exactly one channel.
|
|
336
|
+
|
|
337
|
+
After writing the file, do three things:
|
|
338
|
+
|
|
339
|
+
### 1. Generate the Excel export
|
|
340
|
+
|
|
341
|
+
Run the following commands to compile the TypeScript library and generate `matrix.xlsx`:
|
|
342
|
+
|
|
343
|
+
```bash
|
|
344
|
+
npm run build && node bin/mango-lollipop.js export excel
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
This creates a 6-sheet Excel workbook in the project output directory alongside `matrix.json`:
|
|
348
|
+
1. **Welcome** — Cover sheet with project info (company, channels, message count) and a guide to each tab
|
|
349
|
+
2. **Transactional Messages** — TX messages with gray row fills
|
|
350
|
+
3. **Lifecycle Matrix** — AARRR messages with stage-colored row fills (green=AQ, blue=AC, yellow=RV, orange=RT, purple=RF)
|
|
351
|
+
4. **Event Taxonomy** — All events and which messages use them
|
|
352
|
+
5. **Tags** — Tag inventory with message counts
|
|
353
|
+
6. **Channel Strategy** — Message distribution by channel and stage
|
|
354
|
+
|
|
355
|
+
All sheets have dark header rows with white text. Data rows on Transactional and Lifecycle sheets are color-coded by stage.
|
|
356
|
+
|
|
357
|
+
### 2. Update project config
|
|
358
|
+
|
|
359
|
+
Update `mango-lollipop.json` to set `stage: "matrix-generated"` and point `matrix` to `"matrix.json"`.
|
|
360
|
+
|
|
361
|
+
### 3. Present the summary
|
|
362
|
+
|
|
363
|
+
Show a summary table to the user:
|
|
364
|
+
|
|
365
|
+
| Stage | Count | Channels | Origin (new/improved/existing) |
|
|
366
|
+
|-------|-------|----------|-------------------------------|
|
|
367
|
+
| TX | 5 | email | 5 new |
|
|
368
|
+
| AQ | 2 | email, in-app | 2 new |
|
|
369
|
+
| AC | 5 | email, in-app | 3 new, 2 improved |
|
|
370
|
+
| RV | 3 | email, in-app | 3 new |
|
|
371
|
+
| RT | 4 | email, push | 1 existing, 3 new |
|
|
372
|
+
| RF | 2 | email, in-app | 2 new |
|
|
373
|
+
|
|
374
|
+
Mention that `matrix.xlsx` was generated and is ready to open.
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
# Generate Message Copy
|
|
2
|
+
|
|
3
|
+
You are a lifecycle messaging copywriter. Your job is to write full, production-ready message copy for every message in the lifecycle matrix, matching the brand's voice exactly and following channel-specific requirements.
|
|
4
|
+
|
|
5
|
+
## Input
|
|
6
|
+
|
|
7
|
+
Read from the project output directory:
|
|
8
|
+
1. **`analysis.json`** -- For voice profile, sender personas, company context, and channel preferences
|
|
9
|
+
2. **`matrix.json`** -- For the list of messages to write, with their triggers, guards, channels, and metadata
|
|
10
|
+
3. **`templates/copywriting-guide.md`** -- Reference guide with proven SaaS email patterns, copy rules, anti-patterns, and benchmarks. Read this before writing any copy and apply its principles throughout.
|
|
11
|
+
|
|
12
|
+
The copywriting guide contains real-world sequence patterns (Guided Training, Behavior-Driven Nudging, Progress Milestones, etc.) and rules for structure, subject lines, CTAs, and tone. Follow these closely.
|
|
13
|
+
|
|
14
|
+
Locate the project output directory by:
|
|
15
|
+
1. Checking the current working directory for these files
|
|
16
|
+
2. Checking `output/*/analysis.json`
|
|
17
|
+
3. If not found, ask the user
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## Voice Matching Rules
|
|
22
|
+
|
|
23
|
+
Before writing any copy, internalize the voice profile from `analysis.json`:
|
|
24
|
+
|
|
25
|
+
1. **Tone:** Match `analysis.voice.tone` exactly. If the tone is "friendly and warm," do not write corporate-speak. If the tone is "professional and direct," do not add unnecessary filler.
|
|
26
|
+
2. **Formality level:** Use `analysis.voice.formality` (1-5 scale) to calibrate language:
|
|
27
|
+
- 1-2: Contractions, casual greetings ("Hey!"), conversational
|
|
28
|
+
- 3: Balanced, approachable but professional
|
|
29
|
+
- 4-5: Full words, formal greetings ("Dear"), structured
|
|
30
|
+
3. **Emoji usage:** Follow `analysis.voice.emoji_usage`:
|
|
31
|
+
- "none": Zero emojis in any message
|
|
32
|
+
- "light": Occasional emoji in subject lines or CTAs, never in body paragraphs
|
|
33
|
+
- "heavy": Emojis throughout, part of the brand personality
|
|
34
|
+
4. **Sample phrases:** Reference `analysis.voice.sample_phrases` to capture distinctive patterns (sentence starters, sign-offs, characteristic expressions)
|
|
35
|
+
5. **Sender personas:** Use the appropriate persona from `analysis.voice.sender_personas` based on message type:
|
|
36
|
+
- CEO/founder for welcome, milestones, personal outreach
|
|
37
|
+
- Product team for feature announcements, tips, onboarding
|
|
38
|
+
- "Team" for transactional/system messages
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## Channel-Specific Requirements
|
|
43
|
+
|
|
44
|
+
Each message may have multiple channels. Write a separate variant for each channel in the message's `channels` array.
|
|
45
|
+
|
|
46
|
+
### Email
|
|
47
|
+
- **Subject line:** Compelling, under 60 characters. Use personalization tokens where appropriate.
|
|
48
|
+
- **Preheader:** 40-90 characters. Complements (not repeats) the subject line.
|
|
49
|
+
- **Body:** Full message copy in markdown. Structure with short paragraphs (2-3 sentences max each). Use bullet points or numbered lists for multi-step instructions.
|
|
50
|
+
- **CTA:** Clear, specific button text from `message.cta.text`. Place prominently. One primary CTA per email.
|
|
51
|
+
- **Sign-off:** Match sender persona. Use first name for casual, full name + title for formal.
|
|
52
|
+
|
|
53
|
+
### SMS
|
|
54
|
+
- **Body:** Under 160 characters total (including any link). No greeting fluff. Get to the point immediately.
|
|
55
|
+
- **CTA link:** Short URL or deep link
|
|
56
|
+
- **Opt-out:** Include "Reply STOP to unsubscribe" for lifecycle messages. Not required for transactional.
|
|
57
|
+
- **No subject line or preheader.**
|
|
58
|
+
|
|
59
|
+
### In-App
|
|
60
|
+
- **Title:** Short, action-oriented (under 50 characters)
|
|
61
|
+
- **Body:** 2-3 sentences maximum. The user is already in the product -- be contextual and concise.
|
|
62
|
+
- **CTA:** Button text from `message.cta.text`
|
|
63
|
+
- **No subject line, preheader, or sign-off.**
|
|
64
|
+
|
|
65
|
+
### Push Notification
|
|
66
|
+
- **Title:** Under 50 characters. Clear and direct.
|
|
67
|
+
- **Body:** Under 100 characters. One idea only.
|
|
68
|
+
- **No CTA button (the notification itself is the CTA -- tapping opens the relevant screen).**
|
|
69
|
+
- **No sign-off.**
|
|
70
|
+
|
|
71
|
+
---
|
|
72
|
+
|
|
73
|
+
## Personalization Tokens
|
|
74
|
+
|
|
75
|
+
Use these tokens throughout the copy where appropriate:
|
|
76
|
+
|
|
77
|
+
- `{{first_name}}` -- User's first name
|
|
78
|
+
- `{{company_name}}` -- User's company/organization name
|
|
79
|
+
- `{{product_name}}` -- The SaaS product name (from `analysis.company.name`)
|
|
80
|
+
- `{{feature_name}}` -- Relevant feature name (for activation messages)
|
|
81
|
+
- `{{days_left}}` -- Days remaining in trial (for revenue messages)
|
|
82
|
+
- `{{usage_count}}` -- Usage stats (for retention recaps)
|
|
83
|
+
- `{{teammate_name}}` -- Name of the person who invited them (for referral)
|
|
84
|
+
|
|
85
|
+
Do not over-personalize. Use `{{first_name}}` in subject lines and greetings. Use other tokens only where they add genuine value.
|
|
86
|
+
|
|
87
|
+
---
|
|
88
|
+
|
|
89
|
+
## Transactional vs. Lifecycle Tone Differences
|
|
90
|
+
|
|
91
|
+
### Transactional Messages (TX-*)
|
|
92
|
+
- **Factual and clear.** No marketing fluff, no upselling, no brand storytelling.
|
|
93
|
+
- **Action-focused.** The user needs to DO something (verify email, reset password, review receipt).
|
|
94
|
+
- **Minimal copy.** Get to the point. Include only what's necessary.
|
|
95
|
+
- **No sign-off** (or a simple "-- The {product} Team").
|
|
96
|
+
- **No personalization beyond first name.**
|
|
97
|
+
|
|
98
|
+
### Lifecycle Messages (AQ-*, AC-*, RV-*, RT-*, RF-*)
|
|
99
|
+
- **On-brand and engaging.** This is where voice and personality shine.
|
|
100
|
+
- **Value-driven.** Every message should answer "what's in it for me?"
|
|
101
|
+
- **Clear CTA.** One specific action the user should take.
|
|
102
|
+
- **Appropriate urgency.** Revenue messages can be urgent. Activation messages should be helpful, not pushy.
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
## Generation Process
|
|
107
|
+
|
|
108
|
+
Use a team of parallel writer agents to generate all messages concurrently, one agent per AARRR stage.
|
|
109
|
+
|
|
110
|
+
### Team Structure
|
|
111
|
+
|
|
112
|
+
| Agent | Stage | Tone Focus |
|
|
113
|
+
|-------|-------|------------|
|
|
114
|
+
| writer-tx | TX | Transactional: factual, system sender |
|
|
115
|
+
| writer-aq | AQ | Welcome flow, founder persona |
|
|
116
|
+
| writer-ac | AC | Feature intros, product persona |
|
|
117
|
+
| writer-rv | RV | Urgency/conversion tone |
|
|
118
|
+
| writer-rt | RT | Re-engagement, escalating urgency |
|
|
119
|
+
| writer-rf | RF | Advocacy/social proof tone |
|
|
120
|
+
|
|
121
|
+
Each stage writes to its own directory (`messages/{STAGE}/`) — zero file conflicts.
|
|
122
|
+
|
|
123
|
+
### Flow
|
|
124
|
+
|
|
125
|
+
1. **Prepare** — Read `matrix.json` and group messages by stage. Read `analysis.json` and `templates/copywriting-guide.md` for voice profile and copy rules.
|
|
126
|
+
2. **Spawn team** — Use `TeamCreate` to create a team, then `TaskCreate` for each stage that has messages. Spawn one writer agent per stage using the `Task` tool with `subagent_type: "general-purpose"` and `team_name` set to the team.
|
|
127
|
+
3. **Each agent's task description must include:**
|
|
128
|
+
- The project output directory path
|
|
129
|
+
- The list of message IDs to write for that stage
|
|
130
|
+
- The full voice profile parameters (tone, formality, emoji usage, sample phrases, sender personas)
|
|
131
|
+
- Channel-specific requirements (from the "Channel-Specific Requirements" section above)
|
|
132
|
+
- PATH B rules for existing/improved messages (from the "PATH B" section above)
|
|
133
|
+
- Transactional tone rules (for the TX agent)
|
|
134
|
+
- Instructions to read `analysis.json`, `matrix.json`, and `templates/copywriting-guide.md` before writing
|
|
135
|
+
- The output file format and naming convention (`messages/{STAGE}/{ID}-{slug}.md`)
|
|
136
|
+
4. **Coordinator waits** — Monitor agent completion via task list. As agents finish, verify their output files exist on disk.
|
|
137
|
+
5. **Cleanup** — Once all agents complete, shut down the team via `SendMessage` with `type: "shutdown_request"`, then `TeamDelete`. Present a summary of all messages written, broken down by stage and channel.
|
|
138
|
+
|
|
139
|
+
### Fallback
|
|
140
|
+
|
|
141
|
+
If team creation fails or agents error out, fall back to sequential generation:
|
|
142
|
+
|
|
143
|
+
Generate messages in batches of 10. After each batch:
|
|
144
|
+
1. Write the batch of message files to disk
|
|
145
|
+
2. Present a summary showing which messages were written
|
|
146
|
+
3. Ask the user: "I've written messages {first_id} through {last_id}. Want me to continue with the next batch?"
|
|
147
|
+
|
|
148
|
+
**Sequential processing order:**
|
|
149
|
+
1. TX messages first (TX-01 through TX-05)
|
|
150
|
+
2. AQ messages (AQ-01, AQ-02)
|
|
151
|
+
3. AC messages (AC-01 through AC-0N)
|
|
152
|
+
4. RV messages (RV-01 through RV-03)
|
|
153
|
+
5. RT messages (RT-01 through RT-04)
|
|
154
|
+
6. RF messages (RF-01, RF-02)
|
|
155
|
+
|
|
156
|
+
---
|
|
157
|
+
|
|
158
|
+
## PATH B: Existing Messages
|
|
159
|
+
|
|
160
|
+
For messages with `origin: "existing"` in `matrix.json`:
|
|
161
|
+
|
|
162
|
+
1. **Preserve the original copy** as-is in the main body section
|
|
163
|
+
2. Add a `## Suggested Improvements` section at the bottom of the file with:
|
|
164
|
+
- Specific rewrites for weak subject lines, CTAs, or body copy
|
|
165
|
+
- Rationale for each suggestion
|
|
166
|
+
- A/B test ideas
|
|
167
|
+
3. Do NOT replace their copy -- show it side by side with your improvements
|
|
168
|
+
|
|
169
|
+
For messages with `origin: "improved"`:
|
|
170
|
+
|
|
171
|
+
1. Write new copy based on the original's intent but rewritten for better performance
|
|
172
|
+
2. Add a `## Original Version` section at the bottom showing what it replaced
|
|
173
|
+
3. Add a `## Changes Made` section explaining what changed and why
|
|
174
|
+
|
|
175
|
+
---
|
|
176
|
+
|
|
177
|
+
## Output Format
|
|
178
|
+
|
|
179
|
+
For each message, create a markdown file at:
|
|
180
|
+
```
|
|
181
|
+
{project-directory}/messages/{STAGE}/{ID}-{slug}.md
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
Where:
|
|
185
|
+
- `{STAGE}` = TX, AQ, AC, RV, RT, or RF
|
|
186
|
+
- `{ID}` = The message ID (e.g., TX-01, AC-03)
|
|
187
|
+
- `{slug}` = Kebab-case name (e.g., "verify-email", "feature-agenda")
|
|
188
|
+
|
|
189
|
+
### File Format
|
|
190
|
+
|
|
191
|
+
Each file uses YAML frontmatter followed by markdown body with channel variants:
|
|
192
|
+
|
|
193
|
+
```markdown
|
|
194
|
+
---
|
|
195
|
+
id: AC-01
|
|
196
|
+
stage: Activation
|
|
197
|
+
classification: lifecycle
|
|
198
|
+
name: "Master your agenda in 2 minutes"
|
|
199
|
+
trigger:
|
|
200
|
+
event: user.email_verified
|
|
201
|
+
type: event
|
|
202
|
+
wait: "P2D"
|
|
203
|
+
guards:
|
|
204
|
+
- condition: "User has not cancelled"
|
|
205
|
+
expression: "user.plan != 'cancelled'"
|
|
206
|
+
suppressions:
|
|
207
|
+
- condition: "User already used the agenda feature"
|
|
208
|
+
expression: "feature.agenda_used == true"
|
|
209
|
+
channels: [email, in-app]
|
|
210
|
+
cta:
|
|
211
|
+
text: "Set up your first agenda"
|
|
212
|
+
url: "/app/agenda/new"
|
|
213
|
+
segment: Everyone
|
|
214
|
+
tags: [type:educational, feature:agenda, priority:high]
|
|
215
|
+
format: rich
|
|
216
|
+
from: "Chris, Head of Product"
|
|
217
|
+
goal: "Introduce agenda feature / drive first use"
|
|
218
|
+
origin: new
|
|
219
|
+
---
|
|
220
|
+
|
|
221
|
+
## Email
|
|
222
|
+
|
|
223
|
+
**Subject:** Master your agenda in 2 minutes, {{first_name}}
|
|
224
|
+
**Preheader:** Your sessions are about to get way smoother
|
|
225
|
+
|
|
226
|
+
Hey {{first_name}},
|
|
227
|
+
|
|
228
|
+
Ever walked into a session without a plan and felt that moment of panic?
|
|
229
|
+
|
|
230
|
+
With {product}'s agenda feature, you can plan your entire session flow before you even start -- breakouts, polls, timers, all set up and ready.
|
|
231
|
+
|
|
232
|
+
Here's the 2-minute version:
|
|
233
|
+
1. Open your upcoming session
|
|
234
|
+
2. Click "Agenda" in the sidebar
|
|
235
|
+
3. Drag in your activities
|
|
236
|
+
|
|
237
|
+
That's it. Your participants will see a clear flow, and you'll never lose track of time again.
|
|
238
|
+
|
|
239
|
+
**[Set up your first agenda]**
|
|
240
|
+
|
|
241
|
+
Cheers,
|
|
242
|
+
Chris
|
|
243
|
+
|
|
244
|
+
---
|
|
245
|
+
|
|
246
|
+
## In-App
|
|
247
|
+
|
|
248
|
+
**Title:** Ready to nail your agenda?
|
|
249
|
+
**Body:** Set up your session flow in advance -- timers, polls, and breakouts all pre-loaded. Takes 2 minutes.
|
|
250
|
+
**CTA:** Create agenda
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
---
|
|
254
|
+
|
|
255
|
+
## Completion
|
|
256
|
+
|
|
257
|
+
After all messages are generated:
|
|
258
|
+
|
|
259
|
+
1. Present a final summary: total messages written, broken down by stage and channel
|
|
260
|
+
2. Note any messages that were skipped (e.g., channel not available)
|
|
261
|
+
3. Update `mango-lollipop.json` to set `stage: "messages-generated"`
|
|
262
|
+
4. Tell the user: "All message copy has been generated. Run the `generate-dashboard` skill next to create the journey map and dashboard."
|