Product Configuration
Products are defined as YAML files in products/. Each file represents one product that Cryyer tracks and sends updates for.
Example
Section titled “Example”id: my-appname: My Apprepo: owner/repoemailSubjectTemplate: "My App — Week of {{weekOf}}"voice: | You are writing a product update email for My App users. Be concise, friendly, and focus on what matters to users.Fields
Section titled “Fields”| Field | Required | Description |
|---|---|---|
id | Yes | Unique identifier, also used as GitHub issue label |
name | Yes | Display name |
voice | Yes* | LLM voice/tone instructions (injected into the draft prompt) |
repo | Yes | owner/repo for GitHub activity gathering |
emailSubjectTemplate | Yes* | Subject line template — use {{weekOf}} for the date |
audiences | No | List of audience-specific overrides (see Audiences) |
tagline | No | Product tagline |
from_name | No | Override sender name for this product |
from_email | No | Override sender email for this product |
reply_to | No | Reply-to address |
* Required when audiences is not set. When using audiences, voice and emailSubjectTemplate are set per-audience instead.
Voice field
Section titled “Voice field”The voice field is injected directly into the LLM prompt and controls the tone of generated emails. Use a multi-line string to give detailed instructions about your product’s communication style.
voice: | You are writing a product update for Acme Cloud users. Tone: professional but approachable. Use "we" not "I". Focus on user-facing changes. Skip internal refactors. Keep paragraphs short — these are busy developers.Audiences
Section titled “Audiences”If you need different email voices for different subscriber groups (e.g. beta testers vs enterprise customers), use the audiences field instead of top-level voice and emailSubjectTemplate:
id: my-appname: My Apprepo: owner/repoaudiences: - id: beta voice: | You are writing a casual update for My App beta testers. Be enthusiastic and highlight experimental features. emailSubjectTemplate: "My App Beta — Week of {{weekOf}}" - id: enterprise voice: | You are writing a professional update for My App enterprise customers. Focus on stability, security, and compliance improvements. emailSubjectTemplate: "My App Enterprise Update — {{weekOf}}" from_name: My App Enterprise from_email: enterprise@myapp.comEach audience gets its own draft and email send. Subscribers are scoped per audience — a subscriber to my-app:beta won’t receive my-app:enterprise emails.
Audience fields
Section titled “Audience fields”| Field | Required | Description |
|---|---|---|
id | Yes | Unique identifier for this audience |
voice | Yes | LLM voice/tone instructions for this audience |
emailSubjectTemplate | Yes | Subject template for this audience |
from_name | No | Override sender name (inherits from product) |
from_email | No | Override sender email (inherits from product) |
reply_to | No | Override reply-to address (inherits from product) |
CLI usage with audiences
Section titled “CLI usage with audiences”When using cryyer draft-file or cryyer send-file with a multi-audience product, pass --audience to target a specific audience:
cryyer draft-file --product my-app --audience beta --output drafts/v1.0-beta.mdcryyer send-file drafts/v1.0-beta.md --product my-app --audience betaMCP and GitHub Actions
Section titled “MCP and GitHub Actions”The MCP server subscriber tools (list_subscribers, add_subscriber, remove_subscriber) accept an optional audience_id parameter. The composite GitHub Actions (draft-file, send-file) accept an optional audience input.
Monorepo filtering
Section titled “Monorepo filtering”If multiple products share the same repo (a monorepo), you can add a filter block so each product only gets its own activity:
id: admin-portalname: Admin Portalrepo: myorg/monorepoemailSubjectTemplate: "Admin Portal — Week of {{weekOf}}"voice: "..."filter: labels: ["admin-portal"] paths: ["apps/admin/"] tag_prefix: "admin/"All three filter fields are optional — use any combination. Without a filter block, Cryyer gathers all activity from the repo (the default).
| Field | What it filters | How it works |
|---|---|---|
paths | PRs + commits | Includes only PRs that touch files under these path prefixes (checks changed files per PR). Also scopes fallback commits to these paths. This is the easiest option — no labeling or tagging workflow needed. |
labels | PRs | Uses the GitHub Search API to fetch only merged PRs with these labels. A single API call, but requires your PRs to be labeled. |
tag_prefix | Releases | Only includes releases whose tag starts with this prefix (e.g. "admin/" matches "admin/v1.2.0" but not "ios/v3.0.0"). |
Example: three products, one repo
Section titled “Example: three products, one repo”The simplest setup — just use paths:
id: admin-portalrepo: myorg/monorepofilter: paths: ["apps/admin/"] tag_prefix: "admin/" # optional, if you use tag prefixes for releases# ...
# products/ios-app.yamlid: ios-apprepo: myorg/monorepofilter: paths: ["apps/ios/"] tag_prefix: "ios/"# ...
# products/api.yamlid: apirepo: myorg/monorepofilter: paths: ["packages/api/", "libs/shared/"]# ...Labels vs paths
Section titled “Labels vs paths”paths checks each PR’s changed files — no setup required, but makes one API call per PR. For most weekly digests (5–20 PRs) this is fine.
labels uses the GitHub Search API — a single API call no matter how many PRs exist, but requires your team to label PRs. If you already label PRs by area, this is more efficient.
If both labels and paths are set, labels is used for PRs (more efficient) and paths still applies to fallback commits.
Subject template
Section titled “Subject template”The emailSubjectTemplate supports these placeholders:
{{weekOf}}— replaced with the current week’s date (for weekly pipeline){{version}}— replaced with the release version (for release pipeline)
# Weekly pipelineemailSubjectTemplate: "My App — Week of {{weekOf}}"# → "My App — Week of January 6, 2025"
# Release pipelineemailSubjectTemplate: "My App v{{version}} Release Notes"# → "My App v1.2.0 Release Notes"