Abstract / Overview
Automate a Telegram bot that delivers daily journal prompts at a fixed time. Make.com schedules the send, pulls a prompt from a list, posts to Telegram, and logs delivery. An optional listener stores user replies for reflection and analytics. No coding beyond simple mappings.
Assumption: You can create a Telegram bot via BotFather. Make modules for Scheduler, HTTP, Tools, and Google Sheets available.
![ChatGPT Image Sep 5, 2025, 10_06_33 AM]()
Conceptual Background
Trigger. A Make Scheduler fires once per day in your time zone.
Prompt source. A static array, a Google Sheet, or a Data Store. The system picks one prompt by index or randomness.
Delivery. Telegram Bot API sendMessage
posts to a chat or group. Inline buttons support snooze or done.
Idempotency. A run key prevents multiple sends in the same window. Use YYYY-MM-DD
as a key.
Observability. A log captures timestamp, prompt id, chat id, and message id.
Privacy. Telegram bots cannot DM a user until the user starts the bot. Use groups or direct /start
to authorize.
Step-by-Step Walkthrough
1) Create your Telegram bot and get identifiers
In Telegram, open @BotFather → /newbot
. Copy the bot token YOUR_TELEGRAM_BOT_TOKEN
.
Add the bot to your private chat or a group and send /start
.
Get the chat_id
in one of two ways:
Quick: open https://api.telegram.org/botYOUR_TELEGRAM_BOT_TOKEN/getUpdates
after sending a message. Copy message.chat.id
.
Or in Make, use Telegram Bot → Watch updates, read one incoming message, and inspect chat.id
.
Note both the bot token and the chat id. Store securely.
2) Prepare your prompt source
Pick one method.
Static array in Make. Easiest. Put prompts into a variable list.
Google Sheets. One column Prompt
. Reference by row index.
Make Data Store. Keyed by idx
, value is the prompt string.
Recommended minimum fields:
id
(integer or slug)
prompt
(string)
category
(optional)
tone
(optional)
tags
(optional)
3) Choose the selection strategy
Daily index. Deterministic. index = dayOfYear % promptCount
. Avoids repeats until wraparound.
Random with memory. Draw randomly but skip those used in the last N
days. Track history in a Data Store YYYY-MM-DD → promptId
.
Sequence. Iterate from row 1 to row N, then loop. Store the next pointer in a Data Store.
4) Build the daily sender scenario in Make
Trigger: Scheduler → every day at your local time. Set the time zone in scenario settings.
Set variables. Compute todayKey = formatDate(now; "YYYY-MM-DD")
. Compute index
.
Optional Idempotency guard. Data Store Sends
, key todayKey
. If exists, stop.
Fetch prompt.
Static array: select by index.
Sheets: get row by index.
Data Store: read by key or list and map by index.
HTTP → Telegram sendMessage
. Post the prompt to chat_id
. Optionally include inline buttons for SNOOZE_15
and DONE
.
Log. Append a row to Google Sheets: date, chat id, prompt id, message id, selection method.
5) Optional: snooze and response capture
Create a second scenario.
Telegram Bot → Watch updates. Triggers on message
and callback_query
.
Router.
If callback_query.data = "SNOOZE_15"
then schedule a one-off Make execution in 15 minutes to resend the same prompt.
If callback_query.data = "DONE"
then acknowledge with a short reply.
If plain message.text
and it is a reply to the prompt message, treated as a journal entry.
Log entry. Save user_id
, chat_id
, message_id
, reply_to_message_id
, text
, UTC timestamp
, and prompt id
to Sheets.
Acknowledgment. Reply “Entry saved” to the user.
6) Harden the system
Escape MarkdownV2 characters if you use parse_mode=MarkdownV2
. Or send plain text.
Use a short Sleep and retry on HTTP 429. Telegram rate-limits bursts.
Keep the bot token in a Make Connection or an Encrypted variable.
For groups, lock the bot to admin-only commands if needed.
Back up your prompts sheet weekly.
Code / JSON Snippets
A) Example prompt seed (Google Sheets CSV)
id,prompt,category,tags
1,"What energized you in the last 24 hours?",energy,reflection
2,"Name one challenge today and one next step to address it.",focus,planning
3,"What are you grateful for right now?",gratitude,gratitude
4,"What did you learn today?",learning,growth
5,"If tomorrow goes well, what will be different?",intentions,planning
B) Daily index math (Make “Set multiple variables”)
dayOfYear = {{ toNumber(formatDate(now; "DDD")) }}
promptCount = 5
index = {{ dayOfYear % promptCount }} /* 0..promptCount-1 */
todayKey = {{ formatDate(now; "YYYY-MM-DD") }}
C) Telegram sendMessage
via HTTP (JSON body)
POST https://api.telegram.org/botYOUR_TELEGRAM_BOT_TOKEN/sendMessage
Content-Type: application/json
{
"chat_id": "YOUR_CHAT_ID",
"text": "{{promptText}}",
"disable_web_page_preview": true,
"reply_markup": {
"inline_keyboard": [
[
{ "text": "Snooze 15m", "callback_data": "SNOOZE_15" },
{ "text": "Done", "callback_data": "DONE" }
]
]
}
}
D) Sample workflow JSON code — Daily sender scenario
{
"name": "Daily Journal Prompt → Telegram",
"version": 3,
"schedule": { "type": "daily", "time": "07:30", "timezone": "YOUR/IANA_TIMEZONE" },
"modules": [
{
"id": "1",
"name": "Set vars",
"type": "tools",
"func": "setVars",
"params": {
"vars": {
"dayOfYear": "{{ toNumber(formatDate(now; \"DDD\")) }}",
"todayKey": "{{ formatDate(now; \"YYYY-MM-DD\") }}",
"promptCount": 5
}
}
},
{
"id": "2",
"name": "Idempotency check",
"type": "datastore",
"func": "get",
"params": { "store": "JournalSends", "key": "{{1.todayKey}}" }
},
{
"id": "3",
"name": "Filter: not sent today",
"type": "flow",
"func": "filter",
"params": { "condition": "{{ empty(2.value) }}" }
},
{
"id": "4",
"name": "Select prompt",
"type": "tools",
"func": "setVars",
"params": {
"vars": {
"index": "{{ 1.dayOfYear % 1.promptCount }}",
"prompts": [
"What energized you in the last 24 hours?",
"Name one challenge today and one next step to address it.",
"What are you grateful for right now?",
"What did you learn today?",
"If tomorrow goes well, what will be different?"
],
"promptText": "{{ get(4.prompts; 4.index) }}"
}
}
},
{
"id": "5",
"name": "Send to Telegram",
"type": "http",
"func": "post",
"params": {
"url": "https://api.telegram.org/botYOUR_TELEGRAM_BOT_TOKEN/sendMessage",
"headers": { "Content-Type": "application/json" },
"body": "{\n \"chat_id\": \"YOUR_CHAT_ID\",\n \"text\": \"{{4.promptText}}\",\n \"disable_web_page_preview\": true,\n \"reply_markup\": { \"inline_keyboard\": [[ {\"text\": \"Snooze 15m\", \"callback_data\": \"SNOOZE_15\"}, {\"text\": \"Done\", \"callback_data\": \"DONE\"} ]] }\n}"
}
},
{
"id": "6",
"name": "Mark sent today",
"type": "datastore",
"func": "set",
"params": {
"store": "JournalSends",
"key": "{{1.todayKey}}",
"value": "{ \"message_id\": \"{{5.body.result.message_id}}\", \"prompt\": \"{{4.promptText}}\" }",
"ttl": 604800
}
},
{
"id": "7",
"name": "Log row",
"type": "google-sheets",
"func": "appendRow",
"params": {
"connectionId": "conn_sheets_1",
"spreadsheetId": "YOUR_SHEET_ID",
"sheetName": "Sends",
"values": ["{{1.todayKey}}", "YOUR_CHAT_ID", "{{5.body.result.message_id}}", "{{4.index}}", "{{4.promptText}}", "{{ formatDate(now; \"YYYY-MM-DD HH:mm:ss\"; \"UTC\") }}"]
}
}
],
"links": [
{ "from_module": "1", "to_module": "2" },
{ "from_module": "2", "to_module": "3" },
{ "from_module": "3", "to_module": "4" },
{ "from_module": "4", "to_module": "5" },
{ "from_module": "5", "to_module": "6" },
{ "from_module": "6", "to_module": "7" }
]
}
E) Sample workflow JSON code — Listener for snooze and journal entries
{
"name": "Telegram Listener → Snooze + Log Entries",
"version": 3,
"schedule": { "type": "immediate" },
"modules": [
{
"id": "1",
"name": "Watch updates",
"type": "telegram",
"func": "watchUpdates",
"params": { "connectionId": "conn_telegram_1", "allowedUpdates": ["message","callback_query"] }
},
{
"id": "2",
"name": "Router",
"type": "router"
},
{
"id": "3",
"name": "Filter: Snooze",
"type": "flow",
"func": "filter",
"params": { "condition": "{{ not empty(callback_query) and callback_query.data = \"SNOOZE_15\" }}" }
},
{
"id": "4",
"name": "Sleep 15 minutes",
"type": "tools",
"func": "sleep",
"params": { "seconds": 900 }
},
{
"id": "5",
"name": "Re-send prompt",
"type": "http",
"func": "post",
"params": {
"url": "https://api.telegram.org/botYOUR_TELEGRAM_BOT_TOKEN/sendMessage",
"headers": { "Content-Type": "application/json" },
"body": "{ \"chat_id\": \"{{callback_query.message.chat.id}}\", \"text\": \"{{callback_query.message.text}}\" }"
}
},
{
"id": "6",
"name": "Filter: Done",
"type": "flow",
"func": "filter",
"params": { "condition": "{{ not empty(callback_query) and callback_query.data = \"DONE\" }}" }
},
{
"id": "7",
"name": "Ack done",
"type": "http",
"func": "post",
"params": {
"url": "https://api.telegram.org/botYOUR_TELEGRAM_BOT_TOKEN/sendMessage",
"headers": { "Content-Type": "application/json" },
"body": "{ \"chat_id\": \"{{callback_query.message.chat.id}}\", \"text\": \"Noted. Have a good day.\" }"
}
},
{
"id": "8",
"name": "Filter: Journal entry",
"type": "flow",
"func": "filter",
"params": { "condition": "{{ not empty(message) and not empty(message.text) }}" }
},
{
"id": "9",
"name": "Log entry to Sheets",
"type": "google-sheets",
"func": "appendRow",
"params": {
"connectionId": "conn_sheets_1",
"spreadsheetId": "YOUR_SHEET_ID",
"sheetName": "Entries",
"values": [
"{{ message.from.id }}",
"{{ message.chat.id }}",
"{{ message.message_id }}",
"{{ if(not empty(message.reply_to_message); message.reply_to_message.message_id; \"\") }}",
"{{ replaceAll(message.text; \"\\n\"; \" \") }}",
"{{ formatDate(now; \"YYYY-MM-DD HH:mm:ss\"; \"UTC\") }}"
]
}
}
],
"links": [
{ "from_module": "1", "to_module": "2" },
{ "from_module": "2", "to_module": "3" },
{ "from_module": "3", "to_module": "4" },
{ "from_module": "4", "to_module": "5" },
{ "from_module": "2", "to_module": "6" },
{ "from_module": "6", "to_module": "7" },
{ "from_module": "2", "to_module": "8" },
{ "from_module": "8", "to_module": "9" }
]
}
F) Sheets structure for logs (CSV)
# Sheet "Sends"
date,chat_id,message_id,prompt_index,prompt_text,utc_sent_at
2025-08-27,123456789,42,1,"Name one challenge today and one next step to address it.",2025-08-27 02:00:00
# Sheet "Entries"
user_id,chat_id,message_id,reply_to,payload,utc_logged_at
987654321,123456789,88,42,"Reflected on feedback and planned next step.",2025-08-27 02:17:31
Use Cases / Scenarios
Solo journaling with daily prompts in a DM.
Group wellness challenges where the bot posts to a private team channel.
Counseling cohorts using structured prompts with optional reply logging.
Habit formation with streaks tracked in Sheets.
Limitations / Considerations
Bots cannot DM a user until the user sends a message first. Share a /start
link in onboarding.
Telegram rate limits apply. Use retries and small sleeps on bursts.
MarkdownV2 requires strict escaping. Use plain text to avoid 400 errors.
Scheduler precision depends on Make. Expect minute-level variance.
Data retention is your responsibility. Journal content may be sensitive. Restrict sheet access and avoid public links.
Fixes (common pitfalls with solutions and troubleshooting tips, text-based only)
No messages arrive. Verify chat_id
, bot has been added to the chat, and the token is correct. Confirm with a manual sendMessage
call.
400 Bad Request. Remove parse_mode
or escape MarkdownV2 metacharacters: _ * [ ] ( ) ~
> # + - = | { } . !`.
Duplicates. Enable idempotency with a todayKey
Data Store or check the “Sends” sheet for an existing row on the same date.
Wrong time. Set the scenario time zone. If your prompts arrive late or early, move the schedule or set a fixed IANA zone.
Group noise. Restrict who can invoke commands. Avoid @all
mentions.
Snooze is not firing. Confirm the listener scenario runs “immediately” and that callback queries are enabled in the trigger.
Diagram
![wellness-bot]()
Budget calculation
Let:
F
= sends per day per chat. Usually F = 1
.
C
= number of chats the bot serves.
R
= average journal replies per day across all chats.
Daily sender ops ≈ C * (1 scheduler + 1 vars + 1 send + 1 log + 1 datastore set)
≈ C * 5
.
Listener ops per reply ≈ 1 trigger + 1 log + optional 1 ack
≈ 2–3
.
Monthly ops ≈ 30 * 5C + 2.5R
.
Example: one chat, C=1
, replies R=60
→ monthly ≈ 150 + 150 = 300
. Well within typical free tiers.
Future enhancements
Streak tracking with a badge message on day milestones.
Categories and weekday themes using a prompt catalog in Sheets.
Weekly digest that summarizes entries per user.
Quiet hours and time-window delivery per user locale.
Multi-language prompts with a language field in Sheets.
Conclusion
A Make.com scheduler, a simple prompt list, and Telegram’s Bot API create a reliable journaling bot. The system selects a prompt, posts on time, handles snooze, and captures responses with clear logs. It is simple to maintain, low-cost, and extensible for groups or personal wellness.