The Data Gap in Mobile Games
Most mobile games collect basic analytics: installs, sessions, purchases. But the real gold lies in the hundreds of micro-interactions players make every session — what level they reached, which items they bought, when they rage-quit, what time of day they play, how often they share on social media.
For CCFish, a Cocos Creator fishing game with a Telegram Mini App companion, this data gap was costing us revenue. We knew players who caught certain rare fish were 3x more likely to make in-app purchases — but our analytics pipeline couldn't trigger marketing actions based on in-game events in real-time.
The Architecture: Game Events → Marketing Actions
We built a server-side event pipeline that transforms raw Cocos Creator game events into structured marketing signals. Here's the full flow:
```
Game Client (Cocos Creator)
↕ WebSocket / HTTP
Event Ingestion API (Cloudflare Workers)
↓
Event Buffer (D1, batch writes every 5s)
↓
Event Processor (Workers Cron, every 60s)
↓ ↓ ↓
D1 Store Redis Cache Message Queue
↓
Marketing Trigger Engine
↓ ↓ ↓
Offer Engine Push Notifications Telegram Bot
```
Step 1: Defining Custom Game Events
In Cocos Creator, we instrument key player actions with a lightweight event emitter. Each event follows a consistent schema:
```typescript
interface GameEvent {
eventName: string;
playerId: string;
sessionId: string;
timestamp: number;
properties: Record<string, string | number | boolean>;
gameVersion: string;
}
```
Example events CCFish tracks:
| Event Name | Trigger | Properties | Marketing Use |
|------------|---------|------------|---------------|
| `rare_catch` | Player catches a legendary fish | fishType, fishRarity, level | Push limited-time offer |
| `streak_ended` | Player misses a daily login | streakDays | Send re-engagement message |
| `level_retry` | Player retries a failed level | levelId, attempts | Offer power-up discount |
| `shop_browse` | Player opens shop but doesn't buy | section, timeSpent | Send price-drop alert |
| `social_share` | Player shares catch on Telegram | platform, friendCount | Trigger referral bonus |
Step 2: The Event Buffer (D1)
Raw events arrive at high volume (up to 100/sec during peak). Writing each to D1 individually would be expensive. Instead, we buffer events and batch-write every 5 seconds:
```typescript
const EVENT_BUFFER = new Map<string, GameEvent[]>();
const BATCH_INTERVAL = 5000; // 5 seconds
async function bufferEvent(event: GameEvent): Promise<void> {
const key = `${event.playerId}:${event.sessionId}`;
if (!EVENT_BUFFER.has(key)) {
EVENT_BUFFER.set(key, []);
}
EVENT_BUFFER.get(key)!.push(event);
}
// Cron: flush buffer every 5s
async function flushBuffer(): Promise<void> {
const stmt = env.DB.prepare(
`INSERT INTO game_events (player_id, session_id, event_name, properties, timestamp)
VALUES (?, ?, ?, ?, ?)`
);
for (const [key, events] of EVENT_BUFFER) {
const batch = events.map(e =>
stmt.bind(e.playerId, e.sessionId, e.eventName, JSON.stringify(e.properties), e.timestamp)
);
await env.DB.batch(batch);
}
EVENT_BUFFER.clear();
}
```
This reduces D1 writes by 95% while keeping data fresh enough for real-time marketing responses.
Step 3: The Event Processor (Cron-Driven)
Every 60 seconds, a Workers cron job processes the raw event buffer into actionable insights:
```typescript
export default {
async scheduled(event: ScheduledEvent, env: Env): Promise<void> {
// 1. Aggregate events from last 60 seconds
const recentEvents = await env.DB.prepare(`
SELECT player_id, event_name, COUNT(*) as count,
MAX(timestamp) as last_seen
FROM game_events
WHERE timestamp > ?
GROUP BY player_id, event_name
`).bind(Date.now() - 60000).all();
// 2. Evaluate marketing rules
for (const row of recentEvents.results) {
const triggers = await evaluateRules(row as EventRow);
for (const trigger of triggers) {
await env.QUEUE.send(trigger);
}
}
// 3. Update player segments
await updateSegments(recentEvents.results);
}
};
```
Marketing Rule Examples
Rules are stored in a D1 table and evaluated against the aggregated events:
```sql
-- Rule: Offer fishing bait discount after 3 session losses
INSERT INTO marketing_rules (trigger_event, conditions, action, priority) VALUES
('level_failed', '{"count": {"gte": 3}, "window": "session"}',
'{"type": "offer", "offerId": "bait_discount", "delay": 5000}', 50);
-- Rule: Send VIP offer after catching 5 rare fish in one day
INSERT INTO marketing_rules (trigger_event, conditions, action, priority) VALUES
('rare_catch', '{"count": {"gte": 5}, "window": "1d"}',
'{"type": "offer", "offerId": "vip_pass", "delay": 0}', 90);
```
Step 4: Multi-Channel Marketing Actions
Once a marketing rule triggers, the system can push to any channel:
```typescript
async function executeAction(action: MarketingAction, playerId: string): Promise<void> {
switch (action.type) {
case 'offer':
// Push in-game offer via WebSocket
await pushOffer(playerId, action.offerId);
break;
case 'notification':
// Send push notification
await sendPushNotification(playerId, action.message);
break;
case 'telegram':
// Notify via Telegram Mini App
await sendTelegramMessage(playerId, action.message);
break;
}
}
```
Real Results
| Metric | Before Pipeline | After Pipeline |
|--------|----------------|----------------|
| Time from event to action | 5-30 minutes (manual) | < 60 seconds |
| Marketing actions per day | 500 (batch) | 15,000+ (real-time) |
| Offer conversion rate | 2.1% | 5.3% |
| Player re-engagement rate | 8% | 22% |
| Revenue lift (first 30 days) | — | +34% |
Key Learnings
1. **Start with 3-5 event types.** Don't instrument everything at once. Pick the events that have the clearest marketing follow-up (rare catch → offer, streak end → reminder).
2. **Batch aggressively.** Raw event volume is high. Batching is necessary to keep D1 costs reasonable.
3. **Delay matters.** A 5-second delay on an offer after a rare catch feels natural — let the player enjoy the moment first.
4. **Events are perishable.** Old events (>30 days) should be archived or purged. We use a D1 cron job that moves events older than 30 days to R2.
Conclusion
CCFish's server-side event pipeline turns raw Cocos Creator game events into a real-time marketing engine — all running on Cloudflare Workers + D1 with near-zero operating cost. By connecting what players do in-game to when and how we market to them, we've unlocked a 34% revenue lift without increasing ad spend.
This is the essence of Hybrid Dev+Marketing: building infrastructure that serves both engineering and growth.