BOT
MSG
FREE
Telegram Bot
alert • monitor • notify

Gửi thông báo Telegram từ NestJS: alert lỗi, deploy và monitor

Tích hợp Telegram Bot API vào NestJS: TelegramModule, exception filter bắt 5xx, BullMQ job fail, app start và deploy xong — không cần thư viện ngoài.

11 phút đọc18/06/2026

Setup bot trong 2 phút

  1. Tìm @BotFather trên Telegram → /newbot → đặt tên và username
  2. Nhận token dạng 110201543:AAHdqTcvCH1vGWJxfSeofSAs0K5PALDsaw
  3. Mở chat với bot, gửi /start
  4. Gọi https://api.telegram.org/botTOKEN/getUpdates để lấy chat_id
{ "result": [{ "message": { "chat": { "id": 123456789 } } }] }

Thêm hai biến vào .env:

TELEGRAM_BOT_TOKEN=110201543:AAHdqTcvCH1vGWJxfSeofSAs0K5PALDsaw
TELEGRAM_CHAT_ID=123456789

TelegramModule

Tạo module riêng để inject vào bất kỳ chỗ nào trong app:

// telegram/telegram.service.ts
@Injectable()
export class TelegramService {
  private readonly token: string;
  private readonly chatId: string;
  private readonly cooldowns = new Map<string, number>();

  constructor(private readonly config: ConfigService) {
    this.token = this.config.getOrThrow("TELEGRAM_BOT_TOKEN");
    this.chatId = this.config.getOrThrow("TELEGRAM_CHAT_ID");
  }

  async send(text: string): Promise<void> {
    await fetch(
      `https://api.telegram.org/bot${this.token}/sendMessage`,
      {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({
          chat_id: this.chatId,
          text,
          parse_mode: "HTML",
        }),
      }
    ).catch((err) => {
      // Không để alert làm crash app
      console.error("[Telegram] send failed:", err.message);
    });
  }

  // Gửi tối đa 1 lần trong khoảng cooldownMs cho mỗi key
  // Tránh bị spam alert khi lỗi xảy ra liên tục
  async sendOnce(key: string, text: string, cooldownMs = 60_000): Promise<void> {
    const now = Date.now();
    if (now - (this.cooldowns.get(key) ?? 0) < cooldownMs) return;
    this.cooldowns.set(key, now);
    await this.send(text);
  }
}
// telegram/telegram.module.ts
@Module({
  imports: [ConfigModule],
  providers: [TelegramService],
  exports: [TelegramService],
})
export class TelegramModule {}

Import vào AppModule:

@Module({
  imports: [
    ConfigModule.forRoot({ isGlobal: true }),
    TelegramModule,
    // ...
  ],
})
export class AppModule {}

Exception filter — alert lỗi production

Bắt mọi unhandled exception và gửi Telegram chỉ với 5xx:

// common/filters/all-exceptions.filter.ts
@Catch()
export class AllExceptionsFilter implements ExceptionFilter {
  constructor(private readonly telegram: TelegramService) {}

  async catch(exception: unknown, host: ArgumentsHost) {
    const ctx = host.switchToHttp();
    const req = ctx.getRequest<Request>();
    const res = ctx.getResponse<Response>();

    const status =
      exception instanceof HttpException
        ? exception.getStatus()
        : HttpStatus.INTERNAL_SERVER_ERROR;

    const message =
      exception instanceof Error ? exception.message : String(exception);

    if (status >= 500) {
      const alert =
        `🔴 <b>Server Error ${status}</b>\n` +
        `<code>${req.method} ${req.url}</code>\n` +
        `<code>${message}</code>`;

      // Cooldown 1 phút mỗi endpoint, tránh spam
      await this.telegram.sendOnce(`${req.method}:${req.url}`, alert);
    }

    res.status(status).json({
      statusCode: status,
      message,
      timestamp: new Date().toISOString(),
    });
  }
}

Đăng ký global trong main.ts:

async function bootstrap() {
  const app = await NestFactory.create(AppModule);

  const telegram = app.get(TelegramService);
  app.useGlobalFilters(new AllExceptionsFilter(telegram));

  await app.listen(3000);
}

Thông báo khi app start và shutdown

Implement OnApplicationBootstrap và OnApplicationShutdown trong AppModule:

// app.module.ts
@Module({ ... })
export class AppModule implements OnApplicationBootstrap, OnApplicationShutdown {
  constructor(private readonly telegram: TelegramService) {}

  async onApplicationBootstrap() {
    await this.telegram.send(
      `✅ <b>App started</b>\n` +
      `Env: <code>${process.env.NODE_ENV}</code>\n` +
      `Time: <code>${new Date().toLocaleString("vi-VN")}</code>`
    );
  }

  async onApplicationShutdown(signal?: string) {
    await this.telegram.send(
      `⚠️ <b>App shutting down</b> (${signal ?? "unknown signal"})`
    );
  }
}

Để onApplicationShutdown chạy được, cần bật shutdown hooks:

// main.ts
const app = await NestFactory.create(AppModule);
app.enableShutdownHooks(); // bắt SIGTERM, SIGINT
await app.listen(3000);

Alert khi BullMQ job thất bại

// jobs/alert.processor.ts (hoặc bất kỳ processor nào)
@Processor("email")
export class EmailProcessor {
  constructor(private readonly telegram: TelegramService) {}

  @Process("send")
  async handleSend(job: Job<{ to: string; subject: string }>) {
    // ... xử lý job
  }

  @OnQueueFailed()
  async onFailed(job: Job, error: Error) {
    const alert =
      `❌ <b>Job Failed</b>\n` +
      `Queue: <code>${job.queue.name}</code>\n` +
      `Job: <code>${job.name} #${job.id}</code>\n` +
      `Attempts: ${job.attemptsMade}/${job.opts.attempts}\n` +
      `Error: <code>${error.message}</code>`;

    await this.telegram.sendOnce(
      `job-fail:${job.queue.name}:${job.name}`,
      alert,
      5 * 60_000 // cooldown 5 phút mỗi loại job
    );
  }
}

Thông báo deploy từ script

Khi dùng PM2 + script deploy:

#!/bin/bash
# deploy.sh

notify() {
  curl -s -X POST "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendMessage" \
    --data-urlencode "chat_id=${TELEGRAM_CHAT_ID}" \
    --data-urlencode "text=$1" \
    -d "parse_mode=HTML" > /dev/null
}

notify "🚀 Bắt đầu deploy <b>$(hostname)</b>..."

git pull origin main && \
npm ci --omit=dev && \
npm run build && \
pm2 restart ecosystem.config.js --update-env

if [ $? -eq 0 ]; then
  notify "✅ Deploy thành công lúc $(date '+%H:%M:%S')"
else
  notify "❌ Deploy thất bại trên <b>$(hostname)</b>!"
  exit 1
fi

Format tin nhắn

parse_mode: "HTML" dễ kiểm soát hơn Markdown, không cần escape ký tự đặc biệt trong code:

// Hàm helper dùng chung
function formatAlert(opts: {
  emoji: string;
  title: string;
  fields: Record<string, string>;
  code?: string;
}): string {
  const lines = [
    `${opts.emoji} <b>${opts.title}</b>`,
    "",
    ...Object.entries(opts.fields).map(
      ([k, v]) => `<b>${k}:</b> <code>${v}</code>`
    ),
  ];

  if (opts.code) {
    lines.push("", `<pre>${opts.code.slice(0, 800)}</pre>`);
  }

  return lines.join("\n");
}

// Dùng trong exception filter
const alert = formatAlert({
  emoji: "🔴",
  title: "Server Error 500",
  fields: {
    Method: req.method,
    URL: req.url,
    Error: error.message,
  },
  code: error.stack,
});

Lưu ý production

  • Không await trong critical path: gửi Telegram có thể mất 200–500ms, nên fire-and-forget ở filter hoặc lifecycle hook
  • Rate limit Telegram: 30 msg/giây — đủ cho alerting, dùng sendOnce để tránh spam
  • Token phải ở env: không commit vào git, không log ra console
  • Test bằng curl trước: đảm bảo token và chat_id đúng trước khi tích hợp vào code