Amba

Best Practices

Opinionated guidance on getting the most out of Amba — event naming, segment design, streak choice, content organization.

Amba is permissive — it won't stop you from creating 300 one-off segments or naming every event click. These are the patterns that scale.

Event naming

Events drive streaks, XP, segment rules, and feeds. Treat event names like public API; renaming them means migrating history.

Do:

  • Use snake_case, past-tense: workout_completed, lesson_finished, article_read.
  • Namespace broad domains with a prefix: social_friend_added, content_post_created.
  • Keep event names stable — if a concept changes, add a new event rather than repurposing.
  • Put variable data in properties, not the name: workout_completed { type: "strength" }, not workout_completed_strength.

Don't:

  • Use noun-only names (workout) — past-tense verbs read better in rule builders.
  • Track UI-only events (button_clicked) in Amba; those belong in analytics, not engagement.
  • Invent parallel names (WorkoutCompleted, workout-completed, workoutCompleted) — pick one casing convention and stick.

Segment rule design

Keep rules simple. One or two conditions covers most targeting needs. Complex rules are harder to debug and slow to explain to non-engineers.

Use system segments where possible. Active (7d), Active (30d), New Users, All Users ship out of the box and don't count against any soft-cap on custom segments.

Prefer time-window operators over snapshots. last_seen_at within "7d" is self-maintaining — membership naturally evolves as users come and go. properties.last_session_ts gte <timestamp> goes stale immediately.

Put semantic booleans in properties, not dozens of segments. One segment per cohort is fine. One segment per feature flag is a smell — use remote config conditions instead.

Name segments for what they match, not what they enable. seg_inactive_7d is better than seg_re_engagement_push_target. The latter locks you into one use.

Streak period choice

Match the period to the behavior:

App typePeriodWhy
Affirmations / habitsdailyOne touchpoint per day is the expected cadence
Fitness / workoutsdailyDaily goal is the primary motivator
Language learningdailySpaced repetition is daily
Prayer / meditationdailyRitual is daily
Book / article readingweeklyExpecting daily reading is too aggressive
Savings / financialweeklyMost users don't save daily

Set a grace period. Six hours on a daily streak covers time zone quirks and midnight oversights. Default 24h grace with a freeze budget of 1–2 per month is generous without being toothless.

Tie one qualifying event per streak. If a streak qualifies on workout_completed OR meditation_finished, users will max one and ignore the other — the streak stops driving the behavior you actually want.

Content library organization

One library per content type, not per theme. daily_tips, weekly_recipes, motivation_quotes — each has its own content_schedules. Mixing content types in one library forces awkward filtering downstream.

Use category + tags for discovery. Categories are coarse ("fitness", "mindset"), tags are fine ("beginner", "hiit", "10min"). Filter libraries by category in the SDK (getLibrary(libraryId, { category })).

Let the scheduler pick items. daily_rotation and sequential both shuffle or sequence items for you; manual ordering via sort_order is only needed for sequential libraries.

Bulk-import via MCP for large libraries. amba_add_content_items takes an array — load 100 tips at once rather than one HTTP call per tip.

Push campaign cadence

Don't spam. Rule of thumb: one scheduled campaign per user per day, max. Segment-targeted campaigns are lower risk since they hit cohorts who opted into the trigger behavior.

Scheduled > immediate. Scheduled campaigns are retried automatically on transient failures and give you delivery visibility. Immediate sends are fine for test / transactional pushes, less fine for user-visible campaigns.

Use data payloads for deep links. data: { screen: "home" } + your app's deep-link handler beats embedding URLs in the body.

Segment-target even for broadcast campaigns. Audit your broadcasts against a segment (e.g. Active (30d)) to avoid pushing inactive users back into re-engagement flows.

Remote config

Flag code paths, not UI text. If you're using config for copy (welcome_headline), prefer server-rendered strings or a proper CMS. Config is for behavior.

Default to off. New features default to false; ramp via percentage or segment conditions. Graduation ("this is the default everyone sees") happens only after a full percentage rollout completes.

Percentage rollout > segment rollout for ramps. Segment membership churns on the 15-minute evaluation cadence; percentage rollouts are deterministic and immediate.

Clean up graduated flags. Once a flag is at 100% and stable, delete it. Long-lived flags calcify into dead code paths.

Auth

Anonymous-first. Don't gate the app on signup. Let users use the app anonymously and link an account later via linkAccount('apple'|'google', token) — this preserves their streak / XP / content history.

Email as a fallback, not the primary. Apple + Google cover ~95% of consumer sign-in. Email is friction; offer it, don't lead with it.

Amba owns credentials. Don't shoe-horn a third-party auth vendor on top of Amba auth — that's two source-of-truth systems. Passwords are stored as standard bcrypt hashes, so they're portable if you ever need to migrate out.

Entitlements

RevenueCat is the source of truth. Trust the webhook, don't manually set user_entitlements rows. If a purchase isn't syncing, fix the webhook plumbing before mutating state.

Always re-verify server-side before unlocking. Do not persist "user has premium" in local storage and skip the server check. Subscriptions lapse, charges fail, refunds happen.

MCP

Let the agent do the tedious stuff. Creating 10 segments + 10 push campaigns by hand is slow. Describing what you want to Claude or Cursor with MCP attached is the intended developer workflow.

Keep project_id in your shell env. Setting AMBA_PROJECT_ID means MCP tools can default it. Don't paste the id into every tool call.

Database

Tables in your project's database never carry project_id. If you find yourself reaching for WHERE project_id = ?, you're in the wrong database — the connection is already scoped to your project.

Query your project's database directly when you need SQL. Connection strings are available via support. Use psql / pgcli locally; don't try to reverse-engineer queries through the HTTP API.

Shipping

Alpha means alpha. Amba is stable enough for early adopters; breaking changes may still happen on pre-1.0 APIs. Pin versions, read the changelog.

Export your data before you need to. Each project's database is yours — ask for a dump and you'll get one. Don't wait until you're already trying to leave.

On this page