CLAUDE CODE · TELEGRAM · LAUNCHD
Once a day, with no terminal open: it checks your Telegram channels, picks what matches your keywords, drafts a cover letter and sends it to your own Saved Messages. Nothing reaches an employer without you looking at it first.
A script runs on a schedule (on macOS via launchd) and opens a headless Claude Code session with a precisely defined task: look into a chosen Telegram folder, take the last day of messages from the channels in it, keep only the ones that match what you are looking for, and send the result to your own Saved Messages.
One message per matching job post: role and company, work format and salary (if stated), a link to the original post, the contacts to apply to, and a draft cover letter written from your own profile. The letter is marked as a draft — you send it yourself, if you decide to apply.
claude mcp list: the list should contain a line like telegram: ... — ✔ Connected. If it is not there, ask Claude Code to help you find and set one up — that is a separate step before everything described below.Ask Claude Code in a normal chat: “show me my Telegram folders” — it will call list_folders and return a list with ids. Note the id of the folder you need (in the examples below it is 169).
Two independent conditions: keywords for the role, and the work format. For example:
| Parameter | Example value |
|---|---|
| Keywords | editor, smm, writer, producer, journalist, copywriter |
| Work format | remote, work from home, hybrid, distributed |
A job post passes the filter only if at least one word from each group appears in it.
If you want not just a digest but ready drafts of replies, give Claude Code 3–5 sentences about yourself: your role, key experience, achievements, contacts. It will use only these facts and will not invent anything.
A file, for example ~/.claude/scripts/vacancy-digest.sh — the places to replace are marked “# ← replace”:
#!/bin/bash # A daily digest of Telegram job posts + draft replies. # Everything goes to Saved Messages only — nothing is sent to employers. export PATH="$HOME/.local/bin:/opt/homebrew/bin:/opt/homebrew/sbin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin" FOLDER_ID=169 # ← replace with your id from step 01 SINCE_DATE=$(date -v-1d +%Y-%m-%d) # on Linux: date -d yesterday +%Y-%m-%d KEYWORDS="editor, smm, writer, producer, journalist, copywriter" # ← replace FORMAT="remote, work from home, wfh, hybrid, distributed" # ← replace PROFILE="First Last — role/specialisation. 3-5 facts from your experience. Contacts: email, telegram, phone." # ← replace PROMPT="Check the Telegram folder (folder_id=$FOLDER_ID via mcp__telegram__get_folder) and find new job posts from the last day. 1. From the folder take only chats with type == \"Channel\". 2. For each channel call mcp__telegram__list_messages with from_date=\"$SINCE_DATE\", limit around 30. 3. Select messages containing at least one word from this list: $KEYWORDS — and mentioning the format: $FORMAT. 4. If nothing was found, send nothing and stop. 5. For each job post get a link to it via mcp__telegram__get_message_link. 6. Get your own chat_id via mcp__telegram__get_me (chat_id \"me\" = Saved Messages). 7. Write a short (4-6 sentences) cover letter in the first person based on the profile — real facts only. The letter must begin with “I am applying for [this role](link to the post) JOB TITLE” (the words “this role” being a markdown link to the post link from step 5), and near the end contain the phrase “my CV is attached”: $PROFILE 8. Via mcp__telegram__send_message (with parse_mode=\"md\", so that the link on “this role” renders as clickable) send ONE message per job post to the \"me\" chat: role/company, format/salary, link to the post, contacts, and the draft letter marked “not sent — check before sending”. Never write or send anything to employers directly — only to your own Saved Messages." claude -p "$PROMPT" \ --allowedTools "mcp__telegram__get_folder,mcp__telegram__list_messages,mcp__telegram__get_me,mcp__telegram__send_message,mcp__telegram__get_message_link" \ --model claude-sonnet-5
chmod +x ~/.claude/scripts/vacancy-digest.sh
The file ~/Library/LaunchAgents/com.NAME.vacancy-digest.plist (replace NAME and the path to the script):
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.NAME.vacancy-digest</string>
<key>ProgramArguments</key>
<array>
<string>/bin/bash</string>
<string>/Users/NAME/.claude/scripts/vacancy-digest.sh</string>
</array>
<key>StartCalendarInterval</key>
<dict>
<key>Hour</key><integer>11</integer>
<key>Minute</key><integer>0</integer>
</dict>
<key>StandardOutPath</key>
<string>/Users/NAME/.claude/logs/vacancy-digest.log</string>
<key>StandardErrorPath</key>
<string>/Users/NAME/.claude/logs/vacancy-digest.err.log</string>
<key>RunAtLoad</key><false/>
</dict>
</plist>
Hour/Minute is the run time in your local time zone. The computer has to be on at that moment (off overnight and on in the morning is fine — just set the time with some margin).
launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/com.NAME.vacancy-digest.plist
launchctl enable gui/$(id -u)/com.NAME.vacancy-digest
# a test run right now, without waiting for the schedule:
launchctl kickstart -p gui/$(id -u)/com.NAME.vacancy-digest
If everything is set up correctly, within 1–2 minutes (checking several channels takes a while) job cards will appear in your Saved Messages — or nothing, if nothing matching came up in the last day.
| Action | Command |
|---|---|
| Change the time | edit Hour/Minute in the plist, then bootout + bootstrap + enable again |
| Stop it completely | launchctl bootout gui/$(id -u)/com.NAME.vacancy-digest |
| See the logs | cat ~/.claude/logs/vacancy-digest.log |
The script is allowed exactly five Telegram tools (--allowedTools) — reading folders and messages, getting a link to a post, and sending a message to yourself. It has no access to any chats other than your own Saved Messages, and no way to write to anyone else.
Draft letters are drafts: Claude Code writes them only from the facts you gave in your profile, and marks every message clearly as unsent. Whether to apply, whether to edit the letter — that is always your call.