GitHub ส่งอีเมลหาเราเองเมื่อ workflow ที่เราเป็นคนสั่งรันไม่ผ่าน แต่อีเมลพวกนี้มักไปกองรวมกับอีเมลอื่น และไม่บอกอะไรเลยตอนที่ deploy สำเร็จ เราอยากได้ข้อความสั้น ๆ บนมือถือทุกรอบ ผ่านหรือไม่ผ่านก็ส่ง ข้อความหน้าตาแบบนี้
✅ my-app — tests passed · deployed
main · เพิ่มหน้ารายงานยอดขาย
https://github.com/my-org/my-app/actions/runs/123456789
เราเคยส่งเข้า LINE Notify แต่ LINE ปิดบริการไปตั้งแต่ 31 มีนาคม 2568 ทางที่เหลือฝั่ง LINE คือ Messaging API ซึ่งต้องมี LINE OA และนับโควตาข้อความ push ทุกเดือน ส่วน Telegram Bot API ไม่มีค่าใช้จ่าย ส่งด้วย HTTP request เดียว และไม่ต้องมีบัญชีธุรกิจ งานแจ้งผล CI ของเราจึงใช้ Telegram
สร้าง bot และหา chat id
- เปิด Telegram คุยกับ @BotFather พิมพ์
/newbotตั้งชื่อ bot แล้วจะได้ token รูปแบบ123456789:AA…มา - ส่งข้อความอะไรก็ได้หา bot ตัวนั้น bot ส่งข้อความหาเราก่อนไม่ได้ จนกว่าเราจะเริ่มคุยด้วย
- เปิด
https://api.telegram.org/bot<TOKEN>/getUpdatesในเบราว์เซอร์ หา"chat":{"id":…}ในผลลัพธ์ ตัวเลขนั้นคือ chat id
ถ้าอยากให้ส่งเข้ากลุ่ม ให้เชิญ bot เข้ากลุ่มก่อน แล้วพิมพ์ /start ในกลุ่ม chat id ของกลุ่มเป็นเลขติดลบ ค่าเริ่มต้นของ bot ในกลุ่มคืออ่านเฉพาะคำสั่งที่ขึ้นต้นด้วย / ข้อความธรรมดาจึงไม่โผล่ใน getUpdates
เก็บสองค่านี้เป็น secret ของ repo ห้ามเขียนลงไฟล์ workflow
gh secret set TELEGRAM_BOT_TOKEN -R my-org/my-app
gh secret set TELEGRAM_CHAT_ID -R my-org/my-app
gh จะถามค่าแบบซ่อนตัวอักษร ค่าไม่ค้างอยู่ใน history ของ shell
job notify ต้องมี if: always()
notify:
needs: [test, deploy]
if: always()
runs-on: ubuntu-latest
steps:
- name: Send Telegram notification
env:
TELEGRAM_BOT_TOKEN: ${{ secrets.TELEGRAM_BOT_TOKEN }}
TELEGRAM_CHAT_ID: ${{ secrets.TELEGRAM_CHAT_ID }}
TEST_RESULT: ${{ needs.test.result }}
DEPLOY_RESULT: ${{ needs.deploy.result }}
COMMIT_MESSAGE: ${{ github.event.head_commit.message }}
run: |
# script อยู่ในสองหัวข้อถัดไป
ปกติ GitHub ข้าม job ที่ needs ไม่ผ่าน ถ้าไม่มี if: always() รอบที่ test แดงจะไม่มีข้อความมาเลย ซึ่งเป็นรอบที่เราอยากรู้ที่สุด
ค่าทุกตัวส่งเข้า script ผ่าน env: ข้อความ commit เป็นสิ่งที่คน push พิมพ์เอง ถ้าเขียน ${{ github.event.head_commit.message }} ลงใน script ตรง ๆ ข้อความนั้นอาจกลายเป็นคำสั่ง shell ได้ เหมือนที่เล่าไว้ในโพสต์เรื่อง lint เฉพาะไฟล์ที่ PR แก้
แปลงผลของแต่ละ job เป็นข้อความ
needs.<job>.result มีสี่ค่า คือ success, failure, cancelled และ skipped เราจับคู่ผลของ test กับ deploy ด้วย case
if [ -z "$TELEGRAM_BOT_TOKEN" ] || [ -z "$TELEGRAM_CHAT_ID" ]; then
echo "TELEGRAM_* secrets are not set on this repo — skipping the notification."
exit 0
fi
case "$TEST_RESULT:$DEPLOY_RESULT" in
success:success) icon="✅"; state="tests passed · deployed"; failed=0 ;;
success:skipped) icon="✅"; state="tests passed"; failed=0 ;;
success:*) icon="❌"; state="deploy $DEPLOY_RESULT"; failed=1 ;;
*) icon="❌"; state="tests $TEST_RESULT"; failed=1 ;;
esac
subject="$(printf '%s' "$COMMIT_MESSAGE" | head -1)"
[ -n "$subject" ] || subject="${GITHUB_SHA:0:7}"
success:skippedนับเป็นผ่าน branch อื่นที่ไม่ใช่mainไม่ deploy อยู่แล้ว deploy จึงเป็นskippedทุกรอบ- ใช้บรรทัดแรกของข้อความ commit ข้อความที่ยาวหลายบรรทัดจะได้ไม่ล้นจอ
head_commitว่างใน pull request และการกดรันเอง event สองแบบนี้ไม่มี commit ที่ push เข้ามา ก็ใช้ SHA เจ็ดตัวแรกแทน- ไม่มี secret ก็จบด้วย exit 0 repo ที่ยังไม่ได้ตั้ง Telegram จะได้ไม่แดงเพราะเรื่องนี้ แต่ข้อเสียคือถ้าลืมตั้ง ก็จะไม่มีข้อความมาโดยไม่มีอะไรเตือน ตั้ง secret แล้วสั่ง
gh secret listเช็กว่ามีครบสองตัวทุกครั้ง
ส่งด้วย curl แล้วจบด้วยผลจริง
text="$(printf '%s %s — %s\n%s · %s\n%s/%s/actions/runs/%s' \
"$icon" "${GITHUB_REPOSITORY##*/}" "$state" \
"$GITHUB_REF_NAME" "$subject" \
"$GITHUB_SERVER_URL" "$GITHUB_REPOSITORY" "$GITHUB_RUN_ID")"
curl -sS -X POST "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendMessage" \
--data-urlencode "chat_id=${TELEGRAM_CHAT_ID}" \
--data-urlencode "text=${text}" \
--data-urlencode 'link_preview_options={"is_disabled":true}' > /dev/null
exit "$failed"
--data-urlencodeเข้ารหัสข้อความให้ถูกต้อง ข้อความ commit ที่มีภาษาไทย&หรือขึ้นบรรทัดใหม่ก็ส่งได้ครบlink_preview_optionsปิดการ์ดตัวอย่างของลิงก์ ข้อความจะได้สั้นอยู่บรรทัดเดียวในหน้าแชต (เอกสารของ sendMessage)exit "$failed"ทำให้ job notify แดงตามรอบที่พัง ถ้า notify เขียวอยู่ข้าง deploy ที่แดง ตอนกวาดตาดูรายการ job จะดูเหมือนว่ามีบางอย่างผ่าน แล้วก็มองข้าม deploy ที่พังไปได้ง่าย เราส่งข้อความก่อนแล้วค่อยจบด้วยผลจริง รอบที่ผ่านทุก job ก็ยังเขียวตามเดิม
เครื่องที่ส่งต้องออกเน็ตไปหา Telegram ได้
ถ้า job notify รันบน self-hosted runner ที่อยู่ในเครือข่ายของบริษัท firewall อาจกัน api.telegram.org หรือดักการเชื่อมต่อ HTTPS ไว้ curl จะขึ้น error เรื่อง certificate แล้วข้อความไม่ไปถึง ก่อนวาง job ไว้บนเครื่องไหน ลอง curl -sv https://api.telegram.org/ จากเครื่องนั้นดูก่อนว่าได้ certificate ของ Telegram จริง
ถ้าเครื่องในออฟฟิศออกไปไม่ได้ ก็ให้ job notify รันบน ubuntu-latest ของ GitHub แทน job นี้ไม่ต้อง checkout โค้ดและไม่ต้องเข้า server ใช้เวลาไม่กี่วินาที แต่ GitHub ปัดเวลาของแต่ละ job ขึ้นเป็นนาทีเต็ม รอบหนึ่งจึงกินนาทีของแพ็กเกจหนึ่งนาที
AI agent ไม่ต้องนั่งรอ CI
ข้อดีที่ไม่ได้คิดไว้ตอนแรกคือเรื่อง AI agent ถ้าไม่มี job นี้ agent ที่ push โค้ดเสร็จมักสั่ง gh run watch แล้วรอจนจบรอบ บางรอบก็หลายนาที พอมีข้อความเข้า Telegram เราก็บอก agent ได้ว่า push แล้วจบงานได้เลย ผลจะมาถึงมือถือเราเอง
ถ้าข้อความเป็น ❌ ลิงก์ในข้อความจะพาไปหน้ารอบนั้น หรือส่ง run id ให้ agent อ่าน log เฉพาะ step ที่พังด้วย gh run view <run-id> --log-failed แล้วแก้ต่อได้เลย





