Send the check-in the week the training stops
A member who trained most mornings has opened the app once this week. The drop reaches the churn report at renewal, weeks after the habit broke, and by then the win-back goes to someone who has made up their mind. One week of silence from a daily member is the earlier moment. They are still paying, and the habit is still close enough to pick back up.
sessions_30d- Separate app sessions in the last 30 days
days_since_workout- Days since their last completed workout
top_workout_30d- The workout type they completed most in the last 30 days
Sends a check-in built around the workout they do most once a member who trained most days this month has gone a week without one. It leaves alone the member whose sessions say they are still training, and your CRM picks the message and the channel.
from datetime import timedelta
from snowplow_signals import (
Attribute, AtomicProperty, Event, EventProperty, StreamAttributeGroup, user_id,
)
screen_view = Event(name="screen_view", vendor="com.example", version="1-0-0")
workout_completed = Event(name="workout_completed", vendor="com.example", version="1-0-0")
sessions_30d = Attribute(
name="sessions_30d",
type="int32",
events=[screen_view],
aggregation="approx_count_distinct",
property=AtomicProperty(name="domain_sessionid"),
period=timedelta(days=30),
)
days_since_workout = Attribute(
name="days_since_workout",
type="double",
events=[workout_completed],
aggregation="time_since_last",
time_unit="d",
)
top_workout_30d = Attribute(
name="top_workout_30d",
type="string",
events=[workout_completed],
aggregation="most_frequent",
property=EventProperty(vendor="com.example", name="workout_completed", major_version=1, path="type"),
period=timedelta(days=30),
)
# Members are signed in, so the group keys on the user id your app sets.
fit_lapses_quiet = StreamAttributeGroup(
name="fit_lapses_quiet",
version=1,
attribute_key=user_id,
owner="you@example.com",
attributes=[sessions_30d, days_since_workout, top_workout_30d],
)// In the job that syncs member state to your CRM
const member = await signals.getServiceAttributes({
name: "fit_lapses_quiet",
attribute_key: "user_id",
identifier: userId,
});
// { sessions_30d: 14, days_since_workout: 6.8, top_workout_30d: "strength" }
if (member.days_since_workout >= 6 && member.sessions_30d >= 12) {
crm.send({
campaign: "weekly_check_in",
workout: member.top_workout_30d,
});
}