The Problem
Every mobile team knows the drill: a build breaks, the developer spends 2 hours fixing it, and the only person who notices is the QA engineer who tries to install the new build. The product manager finds out three days later during standup. The marketing team doesn't find out at all until they start planning a release campaign and realize the beta build hasn't been updated in a week.
This communication gap costs more than just confusion. It delays marketing campaigns, wastes ad spend on unready features, and creates tension between engineering and go-to-market teams. CCFish's solution: turn every CI event into a structured, audience-appropriate communication.
The CCFish Pipeline: Build Events Become Marketing Signals
CCFish extended its CI pipeline with a post-build step that generates a communication payload from the build result. Instead of just sending a Slack message to #engineering, the pipeline creates:
- A structured build summary JSON (passed/failed tests, new features flagged, version bump)
- A stakeholder-facing summary (no technical jargon, just "Version 2.4.1 available for testing")
- A marketing-facing changelog (feature descriptions in user-benefit language)
Architecture
The pipeline runs as a GitHub Actions workflow with three stages:
```
Build Stage -> Test Stage -> Deploy Stage -> Communications Stage
|
+-> Generate summary JSON
+-> Select audience template
+-> Send via configured channel
+-> Archive to D1 for analytics
```
The communications stage checks three audience channels:
1. **Internal (Slack/Telegram):** Full technical summary with test results, known issues, build hash
2. **Stakeholder (Email):** Clean summary with release date estimate, feature highlights, risk level
3. **Marketing (Dashboard):** Changelog ready for newsletter, social posts, and App Store description updates
Each channel gets the same source data but rendered through a different template. This means no duplicate work -- the developer doesn't write a status update, the pipeline generates it.
The Email Template System
For the stakeholder email channel, CCFish uses a markdown-to-HTML converter that generates responsive emails from the build summary JSON:
```
Version 2.4.1 | Build #891 | Status: PASSED
Features in this build:
- Optimized product card layout for iPhone 14 Pro Max
- Added regional filtering for Southeast Asian markets
- Fixed crash on fish listing detail page (affecting ~2% of users)
Next steps: QA review -> TestFlight beta -> App Store submission
Estimated release: 5 business days
```
The email auto-includes a "View in TestFlight" button when a beta build is available, and a "View Release Timeline" link to the project board.
Results: From Dark Builds to Visible Progress
| Metric | Before | After | Change |
|--------|--------|-------|--------|
| Build notification audience | 3 engineers | 15+ team members | 5x wider |
| Marketing campaign lead time | 5 days | 10+ days | 2x more runway |
| Missed feature announcements | 3/month | 0/month | Eliminated |
| Stakeholder satisfaction | 6/10 | 9/10 | +50% |
Key Takeaways
- The same CI event can serve multiple audiences -- don't let debug logs be the only output
- Separate data generation from rendering: build summary JSON is the single source of truth
- Marketing teams don't need to know about test coverage -- but they do need to know when a new build is available
- Automating the communication loop creates 2x more marketing lead time for zero extra engineering work
- This pattern builds trust between engineering and marketing -- something no tool can buy
CCFish proves that CI/CD pipelines aren't just for developers. When you design the pipeline with stakeholder communication as a first-class output, every build becomes a marketing opportunity instead of a forgotten artifact.
Implementation: The Communications Stage Generator
The communications stage is a small Python script that runs as the final step in CCFish's CI pipeline. It reads the build summary JSON, selects the appropriate template for each audience, and dispatches messages through the configured channels.
```python
import json, os
from email.mime.text import MIMEText
def generate_communications(build_summary):
channels = build_summary.get("channels", {})
if channels.get("slack"):
slack_msg = format_slack_message(build_summary)
post_to_slack(slack_msg, channels["slack_webhook"])
if channels.get("email"):
email_body = render_email_template(
build_summary,
audience="stakeholder"
)
send_email(
to=channels["email_recipients"],
subject=f"Build #{build_summary['build_number']} - {build_summary['status']}",
body=MIMEText(email_body, "html")
)
if channels.get("marketing_dashboard"):
changelog = generate_marketing_changelog(build_summary)
save_to_dashboard(changelog)
def generate_marketing_changelog(summary):
Translates technical changes into user-benefit language
changes = []
for feature in summary.get("features", []):
changes.append({
"technical": feature["description"],
"marketing": translate_to_marketing(feature["description"]),
"suggested_social": f"We just shipped {feature['marketing']}!",
})
return changes
```
The `translate_to_marketing` function uses a simple template-based transformation -- not AI, just rule-based substitutions. "Fixed crash on X" becomes "Improved stability for X users". "Added Y" becomes "Now supports Y". This keeps the output predictable and reviewable.
Security: Never Leak Internal Details to External Channels
A critical design constraint: the stakeholder email must never expose internal details like build hashes, test coverage numbers, or stack traces. The pipeline enforces this by having separate template files with explicit field allowlists:
```
templates/
internal-slack.md # Full: build_hash, tests_passed, failed, coverage
stakeholder-email.md # Only: version, features, release_estimate, risk_level
marketing-dashboard.md # Only: features (marketing-translated), screenshots
```
Each template file declares which fields from the build summary JSON it's allowed to access. If a field is not in the allowlist, the template engine raises a warning and omits it. This prevents accidentally leaking `stack_trace` or `db_credentials` into a customer-facing email.
Real-World Example
When CCFish shipped its regional filtering feature for Southeast Asia, the pipeline generated:
- Slack: "#891 PASSED | 248 tests, 0 failed | New: regional-filtering (src/features/filters/)"
- Email to stakeholders: "Version 2.4.1 passed build validation. Key feature: regional filtering for SE Asian markets. Estimated release: 5 business days."
- Marketing dashboard: "New: CCFish now supports market-specific fish pricing across Thailand, Vietnam, and Indonesia."
The marketing team picked up the dashboard entry and turned it into a blog post and social campaign within 2 hours -- something that previously took 2-3 days of back-and-forth with engineering.