Remind inactive students
Every Monday, send a short message to students with no finished workout for 14 days, with a first run that sends nothing.
Every Monday, students with no finished workout for 14 days get a message in the app. This use case writes in your name: it needs a key with Students › Read (students:read), Training › Read (training:read) and Messages › Write (messaging:write), and an offer that includes writes. Create the key in the app, terms of use accepted. A student's login cannot be read through the API: the use case looks at finished workouts. An event will replace this polling.
What this use case does
- Trigger: every Monday, then
GET /meforcoach.first_name, the signature (free). - Workouts:
GET /workouts?status=done&since=<14 days ago>, every page (statusisscheduled,done,in_progress,draftormissed).sinceapplies toupdated_at: so keep only thestudent_idof the workouts whoseended_at, orupdated_atif missing, is greater than or equal to 14 days ago. - Students:
GET /students, every page. Each student missing from that set gets a message, unless the student joined less than 14 days ago or has aPAUSEDorEXPIREDcontract. - Send: one
POST /operations/messaging.sendper student reminded.
curl -X POST https://api.fitdistance.io/api/partner/v1/operations/messaging.send \
-H "Authorization: Bearer fd_live_xxxxxxxx" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: inactif-42-2026-10-26" \
-d '{"text": "Hi Léa, …", "studentId": 42}'The Idempotency-Key is inactif-<id>-<YYYY-MM-DD>, with the date of the run: a replay the same day is refunded. See Writes and idempotency.
The message
Hi Léa, I haven't seen a finished workout from you in two weeks. If something is in your way, tell me here.
Mathias
What it costs
A page read costs one credit, debited before the call; each message sent costs one. A hundred students with three workouts a week: 600 workouts in 14 days, so 14 pages read with limit=50, 4 with limit=200, students included, then one message per student reminded. At this volume, the workouts take more than one page and the Make scenario stops on every run: use n8n or a script. A refused parameter still costs a credit; a 401, a 403 or a 429 costs nothing; a replay is refunded.
A first run that sends nothing
messaging.send has no preview by studentId: dry_run: true gives a 400 DRY_RUN_UNSUPPORTED, free. So the first run lists the students without messaging them, for the price of the reads only.
In n8n
- Choose Import from URL… and paste
https://fitdistance.io/documentation/api/cas-pratiques/eleve-inactif-relance.n8n.json, or download the workflow and choose Import from File…. Pick theFit'Distance APIcredential from Connect n8n in each HTTP Request node. - Run it and read the output of Students to remind: one item per student, with its text.
Sending writes to real students
Send message ships disabled: enable it only after this first run. The Idempotency-Key prevents a duplicate the same day, not from one Monday to the next: an inactive student is reminded every week, so trigger every other week to space them out.
- Enable Send message, run again, then activate the workflow. It sends at most one message per second.
The text is at the top of the code of Students to remind, in French: replace it with the English message above. The loop is the one of Connect n8n, plus a Code node, Collect workouts, that keeps the ids of the pages read.
In Make
- First module HTTP › Make a request on
GET /me; schedule on its clock: every Monday. - HTTP › Make a request:
GETonhttps://api.fitdistance.io/api/partner/v1/workouts,limit=200,status=done,since={{addDays(now; -14)}}. A filter on the link that follows:has_moreequal tofalse, to stop rather than wrongly remind a student whose workout is on the next page (n8n reads every page). Then an Iterator ondata, a filterifempty(ended_at; updated_at)later than or equal to{{addDays(now; -14)}}and a Flow Control › Array aggregator onstudent_id. - HTTP › Make a request:
GETon/studentswithlimit=200, an Iterator ondata, then a filter:joined_atearlier than{{addDays(now; -14)}},contract_statusdifferent fromPAUSEDandEXPIRED, andcontains(map(<aggregated array>; student_id); id)false. - Read the result of the filter, then add HTTP › Make a request:
POSTon/operations/messaging.send,Idempotency-Key=inactif-{{id}}-{{formatDate(now; YYYY-MM-DD)}}, Body content typeapplication/JSON, Body input methodJSON string, body{"text": "…", "studentId": {{id}}}.
Reference: students, training, sending a message.