Capture thoughts on Telegram, persist them in Obsidian or GitHub, and never lose a moment again.
Telejournal is a bot that journals every private message into daily markdown notes, persisting to either a local Obsidian vault or a GitHub repository. Designed for personal journaling and private note-taking with rich media support.
User messages sent in a private chat are captured by the bot, including text and media.
Captured content is appended to your daily note with timestamped entries and structured formatting.
- Private chat journal capture for text, photos, voice recordings, video messages (including circular video notes), and locations
- UTC daily note partitioning at
YYYY/YYYY-MM-DD.md - Media storage (photos, voice, video) in
YYYY/attachments/ - Pluggable storage providers:
obsidian_vault(filesystem)github_repo(GitHub repository via REST API)
- YAML frontmatter management for
mood,tags, andcreated - In-memory state only (
context.chat_dataandcontext.bot_data) - Date override commands (
/setdate,/resetdate) - Tags and mood management with inline keyboard callbacks
/showasks whether to show note text only or rendered (text + embedded attachments)- Displayed note timestamps are normalized from
%% HH:MM:SS %%markers to>HH:MM:SSfor cleaner chat output - Daily UTC "on this day" brief sends a years summary and asks whether to show history as notes only or rendered
- Replies to historical bot messages can include a quote plus a clickable source-note link back to the original daily note
- Edited text messages update their original journal entry in place instead of creating a duplicate
- Guided
/settingscommand for runtime updates of tag choices, daily brief time, mood prompt toggle, and bot menu visibility /settingsupdates are persisted to YAML so values survive bot restarts (existing config files are backed up before overwrite)
Choose one installation method:
From PyPI (recommended for end users):
python -m pip install --upgrade telejournalFrom source (recommended for contributors):
git clone https://github.com/hugobatista/telejournal.git
cd telejournal
uv sync --extra devIf running from source, use uv run before commands in the sections below.
Example: uv run telejournal run --verbose.
-
Configure your environment variables as documented in
Environment. -
Start the bot:
telejournal run --verbose- Open your bot in Telegram and send:
/help
- Send a normal message (for example,
First journal entry) and confirm it appears in:
<STORAGE_ROOT>/YYYY/YYYY-MM-DD.md
Use whichever configuration style best fits your setup.
Environment variables only:
telejournal runYAML configuration file (config.yaml auto-detected if present):
telejournal run
telejournal run /path/to/config.yamlCLI overrides (highest priority):
telejournal run \
--telegram-token your_token \
--storage-provider obsidian_vault \
--obsidian-vault-root /path/to/vault \
--allowed-user-ids 123456,987654 \
--message-timestamp-window-seconds 60 \
--daily-brief-time-utc 09:00 \
--obsidian-vault-secure-file-permissionsGitHub storage provider example:
telejournal run \
--telegram-token your_token \
--storage-provider github_repo \
--github-owner your-org \
--github-repo your-journal-repo \
--github-branch main \
--github-batch-window-seconds 60 \
--github-token ghp_or_github_pat_token \
--allowed-user-ids 123456,987654When using github_repo, note writes and media uploads are queued in-memory and
flushed in burst commits every batch_window_seconds (default: 60). Bot
feedback remains immediate, while GitHub API traffic is reduced.
During shutdown, telejournal performs a best-effort final flush of queued GitHub writes (for example, when receiving SIGTERM in container stop flows).
After the bot is running, these commands are available in your private chat:
/helpShow bot usage summary/setdateStart a guided date selection flow/setdate YYYY-MM-DD [HH:MM:SS]Set target note date/time directly/resetdateReturn to current day/tagsShow tag buttons/tags work kidsAdd/select one or more tags/moodOpen mood picker/showShow current effective day note and choose notes only or rendered output/show YYYY-MM-DDShow a specific day note and choose notes only or rendered output/todayinhistoryShow same-day note years and choose notes only or rendered output/deleteDelete last entry and show deleted content/delete day [YYYY-MM-DD]Delete full day note/settingsGuided runtime configuration fortag_choices,daily_brief_time_utc,prompt_for_mood_if_missing, andbot_menu_enabled- Changes are persisted immediately.
- If the bot started from a YAML config file, that file is backed up and updated.
- If no YAML config was used,
./config.yamlis created/updated.
telejournal version
telejournal helpIf you use Linux secret service (secret-tool), you can skip a local .env
and use secret-tool-run:
secret-tool-run telejournal runCreate a .env file:
TELEGRAM_TOKEN=your_bot_token
LOG_LEVEL=INFO
TELEGRAM_ALLOWED_USER_IDS=123456,987654
STORAGE_PROVIDER=obsidian_vault
STORAGE_OBSIDIAN_VAULT_ROOT=/path/to/obsidian/vaultMESSAGE_TIMESTAMP_WINDOW_SECONDS(default:60) - Messages within this window share the same timestampSTORAGE_OBSIDIAN_VAULT_SECURE_FILE_PERMISSIONS(default:true) - Set restrictive permissions (0o700/0o600) on vault directories and files for security. Applies only toobsidian_vaultprovider.DAILY_BRIEF_TIME_UTC(default:09:00) - Daily UTC time for historical same-day brief (HH:MMorHH:MM:SS). Set to0to disable.TAG_CHOICES(default:family,health,love,hobby,other,finance,social) - Comma-separated tag choices used by inline tag buttons.PROMPT_FOR_MOOD_IF_MISSING(default:true) - Enable/disable automatic mood prompts after entry writes and timer checks.BOT_MENU_ENABLED(default:true) - Enable/disable Telegram command menu publishing at startup. When disabled, the bot removes its command menu.- GitHub provider variables:
STORAGE_GITHUB_OWNERSTORAGE_GITHUB_REPOSTORAGE_GITHUB_BRANCH(default:main)STORAGE_GITHUB_TOKEN(required forgithub_repo)STORAGE_GITHUB_PATH_PREFIX(optional)STORAGE_GITHUB_API_BASE_URL(default:https://api.github.com)STORAGE_GITHUB_BATCH_WINDOW_SECONDS(default:60)
For github_repo, use a fine-grained personal access token scoped to exactly one target repository, with Contents read/write permissions.
At startup, telejournal attempts to detect repository visibility and logs a warning if the configured repository is public.
For troubleshooting GitHub batching, set LOG_LEVEL=DEBUG to inspect queue and
flush activity (queue size, flush cycle, and retry logs).
The bot supports multiple configuration sources with a clear priority order:
- CLI Arguments - Command-line options override all other sources
- YAML File - Configuration file specified via
configargument - Environment Variables - Settings from
.envfile - Defaults - Built-in defaults for optional settings
Later sources override earlier ones. For example, if you specify --telegram-token on the command line, it will override the TELEGRAM_TOKEN environment variable.
You can provide a config.yaml file for more organized configuration management. The bot automatically looks for ./config.yaml if no config path is specified.
Example config.yaml:
telegram_token: "${TELEGRAM_TOKEN}" # Supports environment variable expansion
allowed_user_ids:
- 123456
- 987654
storage:
provider: obsidian_vault # obsidian_vault or github_repo
obsidian_vault:
root: /path/to/obsidian/vault
secure_file_permissions: true
github_repo:
owner: your-org
repo: your-journal-repo
branch: main
token: "${STORAGE_GITHUB_TOKEN}"
path_prefix: ""
api_base_url: https://api.github.com
batch_window_seconds: 60
log_level: INFO
message_timestamp_window_seconds: 60
daily_brief_time_utc: "0"
tag_choices: ["family", "health", "love", "hobby", "other", "finance", "social"]
prompt_for_mood_if_missing: true
bot_menu_enabled: trueConfiguration Keys:
telegram_token(required) - Your Telegram bot tokenallowed_user_ids(required) - List of Telegram user IDs that can use the botstorage(required) - Hierarchical storage provider configurationstorage.provider-obsidian_vaultorgithub_repostorage.obsidian_vault.root- Filesystem root for vault storagestorage.obsidian_vault.secure_file_permissions- Restrictive perms toggle for vault storagestorage.github_repo.owner- GitHub owner/orgstorage.github_repo.repo- GitHub repo namestorage.github_repo.branch- Branch for writes (defaultmain)storage.github_repo.token- Fine-grained token scoped to the target repo with Contents read/writestorage.github_repo.path_prefix- Optional sub-folder inside the repositorystorage.github_repo.api_base_url- API base URL (defaulthttps://api.github.com)storage.github_repo.batch_window_seconds- In-memory queue flush window in seconds (default60)
log_level(optional, default:INFO) - Logging level (DEBUG, INFO, WARNING, ERROR, CRITICAL)message_timestamp_window_seconds(optional, default:60) - Messages within this window share the same timestampdaily_brief_time_utc(optional, default:09:00) - Daily UTC time for the historical same-day brief (HH:MMorHH:MM:SS), or0to disabletag_choices(optional, default:family,health,love,hobby,other,finance,social) - List of inline tag button choicesprompt_for_mood_if_missing(optional, default:true) - Enable/disable mood prompts when entries exist without moodbot_menu_enabled(optional, default:true) - Enable/disable Telegram command menu publishing at startup
Environment Variable Expansion:
YAML configuration supports ${VAR_NAME} syntax for environment variable expansion:
telegram_token: "${TELEGRAM_TOKEN}"
storage:
provider: obsidian_vault
obsidian_vault:
root: "${STORAGE_OBSIDIAN_VAULT_ROOT}"This allows you to keep sensitive values in environment variables while using a configuration file for other settings.
uv run pytest
# With full coverage and type checking
bash validate.shYou can run the bot in Docker using either docker run or docker compose.
-
Create a
.env.dockerfile with your bot token and settings:TELEGRAM_TOKEN=your_bot_token STORAGE_PROVIDER=obsidian_vault STORAGE_OBSIDIAN_VAULT_ROOT=/data LOG_LEVEL=INFO TELEGRAM_ALLOWED_USER_IDS=123456,987654 STORAGE_OBSIDIAN_VAULT_SECURE_FILE_PERMISSIONS=false # This will avoid permission issues when running as non-root, but use with caution!
-
Create an
obsidian-journaldirectory in the same location as yourdocker-compose.ymlto serve as your vault, and set permissions so the container can write to it:mkdir obsidian-journal chmod 777 obsidian-journal # Use with caution, or set specific user/group permissions as needed -
Start the container:
docker compose up --build
This will mount your Obsidian vault from ./obsidian-journal to /data inside the container.
On SELinux-enabled Linux distributions (for example Fedora/RHEL), make sure
the bind mount uses :Z in docker-compose.yml:
volumes:
- ./obsidian-journal:/data:Z-
Build the image:
docker build -t telejournal:latest . -
Run the container:
docker run -d \ --env-file .env.docker \ -v "$PWD"/obsidian-journal:/data:Z \ --name telejournal \ telejournal:latest
This will start the bot in detached mode, using your local .env.docker file and mounting your Obsidian vault.
Note: If you see
pull access denied for telejournal, you must build the image first:docker build -t telejournal:latest .Then run the container as shown above.
For troubleshooting, check logs with:
docker logs telejournalA utility is provided to convert HTML exports generated by signalbackup-tools to Obsidian-compatible Markdown, preserving timestamps, attachments, and replies. This is useful for importing Signal chat history into your journal vault.
python tools/signalbackup-tool-import/html_to_markdown.py <input_html_file> <output_directory><input_html_file>: Path to your HTML export from signalbackup-tools (e.g.,html/self.html)<output_directory>: Directory where year folders and markdown files will be created
Example:
python tools/signalbackup-tool-import/html_to_markdown.py html/self.html obsidian-journalThis will create:
- Year folders (e.g.,
2022/,2023/) with daily markdown files - An
attachments/folder in each year for media files
See the script for more details and options.
To run the Telegram Journal Bot as a background service on Linux, you can use systemd. This ensures the bot starts on boot and restarts automatically if it fails.
The install-service command generates the service file automatically with sensible defaults:
telejournal install-serviceThis will:
- Create the service file at
/etc/systemd/system/telejournal.service - Use your current user account
- Set working directory to
~/obsidian-journal - Use
.envfrom your home directory - Automatically detect the
telejournalexecutable path
You can customize these defaults:
# Use custom paths and user
telejournal install-service \
--user myuser \
--working-directory /obsidian-journal \
--environment-file /telejournal/.env \
--execstart "/home/myuser/.venv/bin/telejournal run"
After running the command, follow the on-screen instructions to enable and start the service.
Alternatively, you can manually create a service file at /etc/systemd/system/telejournal.service with the following content (adjust paths and user as needed):
[Unit]
Description=Telegram Journal Bot
After=network.target
[Service]
Type=simple
User=youruser
WorkingDirectory=/home/youruser/obsidian-journal
EnvironmentFile=/home/youruser/.env
ExecStart=/home/youruser/.venv/bin/telejournal run
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.targetConfiguration details:
User- The user account that will run the bot (should own the vault directory)WorkingDirectory- Your Obsidian vault root directory (where notes are stored)EnvironmentFile- Path to your.envfile with required environment variablesExecStart- Full path to thetelejournalcommand (installed in your virtual environment)RestartSec- Wait 5 seconds before restarting on failure
If you installed telejournal system-wide via pip, you can use just telejournal run without the full path.
sudo systemctl daemon-reload
sudo systemctl enable telejournal.service
sudo systemctl start telejournal.serviceCheck logs with:
journalctl -u telejournal.service -fThis will keep the bot running in the background and restart it automatically on failure or reboot.

