Route different audiences with rules
Send non-premium fresh accounts to a channel, one country to a local manager, and everyone else to the DM — with named destinations and ordered rules.
The rules engine is the general form of the junk-traffic
split: instead of one hardcoded DM/channel
choice, you define up to 20 named destinations and up to 30 ordered rules. For every
/start, rules are checked top to bottom and the first one that matches wins; nobody
matching falls through to a default destination.
This is the current, "v2" configuration surface — it lives entirely under destinations,
default_dest, and rules on the same gate row as everything else in this section.
Destinations
A destination is just a name and a URL:
{ "key": "dm", "title": "Manager's DM", "url": "https://t.me/your_manager" }| Field | Rule |
|---|---|
key | Slug, [a-z0-9_-], 1–16 chars, unique in the list. Rules and default_dest reference destinations by this key — it doesn't change once other things point at it. |
title | Free text, up to 64 chars. Display only. |
url | Must start with https://, up to 1024 chars. |
Save destinations with a full-replace PATCH (the list you send replaces the stored one):
curl -X PATCH https://app.tease.link/api/admin/tg/gate \
-H "Authorization: Bearer $TEASE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"destinations": [
{ "key": "dm", "title": "Manager DM", "url": "https://t.me/your_manager" },
{ "key": "channel", "title": "Overflow channel", "url": "https://t.me/your_channel" },
{ "key": "br_manager", "title": "Brazil manager", "url": "https://t.me/br_manager" }
],
"default_dest": "dm"
}'In the panel this is the Destinations card — add/rename/re-point inline; a destination that's in use by a rule or is the current default can't be deleted (the button is disabled with a tooltip explaining why) until you repoint that rule or the default first.
Rules
A rule is one condition set plus the destination it routes to when it matches:
{ "id": "r_br", "enabled": true, "conds": { "countries": ["BR"] }, "dest": "br_manager" }| Field | Rule |
|---|---|
id | Slug, [a-z0-9_-], 1–24 chars, unique. |
enabled | Boolean — a disabled rule is skipped without deleting it. |
dest | Must be a key from the destinations you're saving in the same patch (or already stored, if this patch doesn't touch destinations). |
conds | Object — see the table below. Every key is optional; all present conditions must match (AND). An empty {} matches everyone. |
Available conditions, all optional:
| Condition | Type | Matches when |
|---|---|---|
premium | boolean | The account's Telegram Premium flag equals this. |
id_min | integer ≥ 0 | Telegram id ≥ this value (a newer account). |
id_max | integer ≥ 0 | Telegram id < this value (an older account). |
langs | up to 40 two-letter codes | The resolved greeting language is in the list. |
countries | up to 40 two-letter ISO codes | The originating click's country is in the list — unknown country never matches. |
has_username | boolean | The Telegram account has a public @username set. |
organic | boolean | true = arrived with no ?start= click id; false = arrived tagged. |
Save the whole ordered list (order is the priority — first match wins):
curl -X PATCH https://app.tease.link/api/admin/tg/gate \
-H "Authorization: Bearer $TEASE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"rules": [
{ "id": "r_br", "enabled": true, "conds": { "countries": ["BR"] }, "dest": "br_manager" },
{ "id": "r_fresh", "enabled": true, "conds": { "premium": false, "id_min": 7000000000 }, "dest": "channel" }
]
}'In the panel this is the Split rules card: each row reads "IF [chips] → [destination]", with
up/down reordering, a per-rule on/off switch, and a "caught in the period" counter next to each
rule. The row at the bottom ("Everyone else →") sets default_dest.
default_dest, and every rule's dest, must resolve to a real destination key after the
patch is applied. Changing destinations in the same request that still leaves a stored rule or
the stored default pointing at a now-missing key is rejected with 422 — update them together.
Country conditions cost a lookup
A countries condition is the one thing that isn't free: only when at least one stored rule
carries it does the webhook spend an extra indexed lookup (per /start) to resolve the
originating click's country. No countries conditions anywhere → that lookup never runs.
How to know it worked
GET /api/admin/tg/gate (or just reloading the panel tab) echoes back the destinations, rules,
and default exactly as saved — including the enabled flag on each rule. The stats block's
by_dest and by_rule breakdowns for the selected period tell you which rule is actually firing
and how often; the panel shows this inline as each rule's hit counter.
If it didn't work
422on a rule'sdest— that key isn't in your destinations list (in this patch or already stored). Add the destination first, or fix the typo.422ondefault_dest— same shape of problem: it must be a real key.- A rule you saved never seems to fire — see Troubleshooting: a saved rule silently never fires.
What's next
- Set up a junk-traffic split for the simplest two-destination version of this same engine.
- Customize the bot's greeting.