AI Automation & Agents  

Forward Gmail Emails with a Specific Label to Telegram

Abstract / Overview

Objective: forward Gmail messages with a chosen label into a Telegram chat. Delivery: Telegram Bot API. Orchestration: Make.com scenario for no-code, plus a Google Apps Script option.

Assumption: single Gmail account, one target Telegram chat (personal or group). Messages are forwarded as plain text with minimal formatting. Attachments and inline images are optional extensions described later.

Outcome: a reliable email-to-chat bridge with filters, deduplication, and simple rate control.

Flow

Telegram with make

Conceptual Background

  • Gmail labels: Labels act as selectors. A filter applies labels on arrival. Automations watch that label, not the entire inbox.
  • Telegram bot: A bot created via BotFather exposes sendMessage and related endpoints. Each destination chat has a numeric chat_id.
  • Polling vs push: Gmail push to third parties is constrained. Most practical flows poll: Make.com “Watch emails” or Apps Script time-driven triggers.
  • Idempotency: Use immutable identifiers such as Message-Id header or Gmail thread/message IDs to avoid duplicate forwards.
  • Formatting: Telegram supports MarkdownV2 and HTML subsets. Escape characters to prevent formatting errors.
  • Privacy: Email may contain PII. Forward only fields required for operations. Avoid forwarding signatures or footers if not needed.
  • Quotas and budgets: Gmail and Apps Script have daily quotas. Make.com is billed per operation. Telegram Bot API is free but rate-limited. Use a budget guard.

Step-by-Step Walkthrough

1. Prerequisites

  • Gmail account with permission to create filters and labels.
  • Telegram account.
  • Make.com account (for the no-code path).
  • Optional: Google Apps Script access for the code path.

2. Create the Telegram bot and capture chat ID

  1. Open Telegram. Start a chat with @BotFather.
  2. Run /newbot. Provide a name and a unique username ending in bot.
  3. Copy the bot token. Format: 1234567890:AA.... Save as TELEGRAM_BOT_TOKEN.
  4. Start a chat with your bot. Send a simple hi.
  5. Obtain your chat_id. Two common paths:
    • Add https://api.telegram.org/bot<TELEGRAM_BOT_TOKEN>/getUpdates in a browser and read the message.chat.id.
    • Or add the bot to a group and run the same getUpdates after posting a message in that group. Record the group’s chat.id.

3. Prepare Gmail: label and filter

  1. In Gmail, create a label. Example: ForwardToTelegram.
  2. Create a filter: search criteria such as from:[email protected] or subject keywords.
  3. In filter actions, check Apply the label and select ForwardToTelegram. Optionally, check Never send it to Spam.
  4. Apply to matching conversations. New arrivals will carry the label.

4. No-code path with Make.com

  1. Create a scenario
    • Module 1: Gmail → Watch Emails.
    • Search: Label equals ForwardToTelegram.
    • Set Include to Unread only if you want single-pass behavior.
    • Choose an interval. Example: every minute.
  2. Add a transformer
    • Clean subject and body. Truncate body to a safe length, e.g., 3,000 characters.
    • Optionally strip quoted text using a regex.
  3. Add a deduplication step
    • Use Make Data Store with key ${messageId}.
    • If key exists, skip. If not, continue and store the key after send.
  4. Add a budget guard
    • Data Store counter tg_sends_monthly.
    • If counter >= LIMIT, route to an email or do nothing. Reset monthly.
  5. Send to Telegram
    • Module: HTTPMake a request or Telegram BotsSend a message (if available in your plan).
    • URL: https://api.telegram.org/bot{{TELEGRAM_BOT_TOKEN}}/sendMessage.
    • Method: POST
    • JSON body: { "chat_id": "{{CHAT_ID}}", "text": "…", "parse_mode": "MarkdownV2", "disable_web_page_preview": true }.
    • Compose text as:
      *New labeled email*
      From: {{from}}
      Subj: {{subject}}
      Label: ForwardToTelegram
      ---
      {{body_excerpt}}
      Link: https://mail.google.com/mail/u/0/#inbox/{{gmailThreadId}}
      
    • Escape MarkdownV2 special characters in dynamic fields.
  6. Mark processed (optional)
    • Add Gmail → Modify a Message. Mark as Read or add a TelegramSent label.

5. Code path with Google Apps Script

Use a time-based trigger. The script scans the label and posts new messages to Telegram. It stores processed Gmail message IDs in Script Properties to prevent duplicates.

Steps

  1. In Google Drive, create an Apps Script project.
  2. Paste the code from Section 4.2.
  3. Set TELEGRAM_BOT_TOKEN, CHAT_ID, and GMAIL_LABEL.
  4. Add a time-driven trigger. Example: every 5 minutes.
  5. Run once to grant permissions.

6. Handling attachments and rich content

  1. Start simple: body text only.
  2. For attachments, iterate getAttachments() and upload with sendDocument or sendPhoto.
  3. For HTML mail, convert to text or use parse_mode: HTML after sanitization.

7. Hardening

  • Retries: Retry on HTTP 429 or 5xx with exponential backoff.
  • Observability: Log each send with timestamp, thread ID, and Telegram response.
  • Security: Store the bot token in Make Secrets or Apps Script Properties Service.
  • Access control: Telegram bots cannot read private messages sent by others unless they initiate. For groups, ensure the bot is added and has permission to post.

Code / JSON Snippets

All snippets are minimal and ready to adapt.

1. Telegram sendMessage with cURL

Use for quick validation.

# Send a test message
export TELEGRAM_BOT_TOKEN="YOUR_TELEGRAM_BOT_TOKEN"
export CHAT_ID="YOUR_CHAT_ID"
curl -sS -X POST "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendMessage" \
  -H "Content-Type: application/json" \
  -d "{\"chat_id\":\"${CHAT_ID}\",\"text\":\"Gmail → Telegram test\",\"disable_web_page_preview\":true}"

2. Google Apps Script: poll a Gmail label and forward to Telegram

Stores processed message IDs to avoid duplicates and escapes MarkdownV2.

/**
 * Gmail label → Telegram forwarder
 * Install a time-driven trigger: every 5 minutes.
 */
const TELEGRAM_BOT_TOKEN = 'YOUR_TELEGRAM_BOT_TOKEN';
const CHAT_ID = 'YOUR_CHAT_ID';
const GMAIL_LABEL = 'ForwardToTelegram';
const MAX_BODY_CHARS = 3000;

function escapeMdV2(s) {
  return String(s || '').replace(/[_*[\]()~`>#+\-=|{}.!]/g, '\\$&');
}

function forwardLabeledEmails() {
  const props = PropertiesService.getScriptProperties();
  const seen = new Set((props.getProperty('SEEN_IDS') || '').split(',').filter(Boolean));
  const label = GmailApp.getUserLabelByName(GMAIL_LABEL) || GmailApp.createLabel(GMAIL_LABEL);
  const threads = label.getThreads(0, 50); // recent threads first

  const sentIds = [];
  threads.forEach(thread => {
    const msgs = thread.getMessages();
    msgs.forEach(msg => {
      const id = msg.getId();
      if (seen.has(id)) return;

      const from = msg.getFrom();
      const subject = msg.getSubject() || '(no subject)';
      const plain = msg.getPlainBody() || msg.getBody().replace(/<[^>]+>/g, ' ');
      const body = plain.trim().slice(0, MAX_BODY_CHARS);
      const text =
        '*New labeled email*\n' +
        'From: ' + escapeMdV2(from) + '\n' +
        'Subj: ' + escapeMdV2(subject) + '\n' +
        'Label: ' + escapeMdV2(GMAIL_LABEL) + '\n' +
        '---\n' +
        escapeMdV2(body) + '\n' +
        'Link: https://mail.google.com/mail/u/0/#inbox/' + thread.getId();

      const url = 'https://api.telegram.org/bot' + TELEGRAM_BOT_TOKEN + '/sendMessage';
      const options = {
        method: 'post',
        contentType: 'application/json',
        payload: JSON.stringify({
          chat_id: CHAT_ID,
          text: text,
          parse_mode: 'MarkdownV2',
          disable_web_page_preview: true
        }),
        muteHttpExceptions: true
      };

      const resp = UrlFetchApp.fetch(url, options);
      if (resp.getResponseCode() < 300) {
        sentIds.push(id);
      } else {
        console.error('Telegram error', resp.getResponseCode(), resp.getContentText());
      }
    });
  });

  if (sentIds.length) {
    const updated = new Set([...seen, ...sentIds]);
    const trimmed = Array.from(updated).slice(-4000); // avoid unbounded growth
    props.setProperty('SEEN_IDS', trimmed.join(','));
  }
}

3. Make.com HTTP module body template (MarkdownV2)

Use when building the sendMessage request in Make.

{
  "chat_id": "{{CHAT_ID}}",
  "text": "*New labeled email*\nFrom: {{escape mdv2(from)}}\nSubj: {{escape mdv2(subject)}}\nLabel: ForwardToTelegram\n---\n{{escape mdv2(body_excerpt)}}\nLink: https://mail.google.com/mail/u/0/#inbox/{{gmailThreadId}}",
  "parse_mode": "MarkdownV2",
  "disable_web_page_preview": true
}

4. Minimal sample workflow JSON code

Portable spec for documentation and reproducibility.

{
  "name": "gmail-label-to-telegram",
  "assumptions": {
    "single_label": "ForwardToTelegram",
    "one_destination_chat": true,
    "timezone": "UTC"
  },
  "trigger": {
    "type": "gmail_watch_label",
    "label": "ForwardToTelegram",
    "interval_seconds": 60
  },
  "steps": [
    {
      "id": "dedupe",
      "type": "datastore_check",
      "key": "{{email.messageId}}",
      "store": "seen_messages",
      "on_exists": "abort"
    },
    {
      "id": "compose",
      "type": "template",
      "format": "MarkdownV2",
      "value": "*New labeled email*\\nFrom: {{escape mdv2 email.from}}\\nSubj: {{escape mdv2 email.subject}}\\nLabel: ForwardToTelegram\\n---\\n{{escape mdv2 email.body_excerpt}}\\nLink: https://mail.google.com/mail/u/0/#inbox/{{email.threadId}}"
    },
    {
      "id": "budget_guard",
      "type": "counter_check",
      "counter": "tg_sends_monthly",
      "limit": 500,
      "window": "calendar_month",
      "on_exceed": "route:drop"
    },
    {
      "id": "send",
      "type": "http_post",
      "url": "https://api.telegram.org/bot{{secrets.TELEGRAM_BOT_TOKEN}}/sendMessage",
      "json": {
        "chat_id": "{{secrets.CHAT_ID}}",
        "text": "{{steps.compose.output}}",
        "parse_mode": "MarkdownV2",
        "disable_web_page_preview": true
      },
      "retry": { "retries": 3, "backoff_seconds": 5 }
    },
    {
      "id": "mark_seen",
      "type": "datastore_put",
      "store": "seen_messages",
      "key": "{{email.messageId}}"
    }
  ],
  "secrets": {
    "TELEGRAM_BOT_TOKEN": "YOUR_TOKEN",
    "CHAT_ID": "YOUR_CHAT_ID"
  }
}

5. Optional. attachments as Telegram documents in Apps Script

function forwardAttachments(msg) {
  const atts = msg.getAttachments({includeInlineImages: false, includeAttachments: true});
  atts.forEach(att => {
    const url = 'https://api.telegram.org/bot' + TELEGRAM_BOT_TOKEN + '/sendDocument';
    const options = {
      method: 'post',
      payload: {
        chat_id: CHAT_ID,
        caption: 'Attachment: ' + att.getName(),
        document: Utilities.newBlob(att.getBytes(), att.getContentType(), att.getName())
      },
      muteHttpExceptions: true
    };
    UrlFetchApp.fetch(url, options);
  });
}

Use Cases / Scenarios

  • On-call alerts: Forward vendor or monitoring alerts labeled P1 into an on-call Telegram group.
  • Sales leads: Route lead capture emails from forms to a sales chat for faster response.
  • Service desk triage: Convert labeled ticket notifications into a channel where agents already work.
  • Compliance inbox: Forward only filtered subjects or senders. Keep the raw email in Gmail for audit.
  • Executive digest: Label curated messages and forward summaries into a private Telegram chat.

Limitations / Considerations

  • Gmail API and Apps Script quotas: Time-based triggers and UrlFetch calls have daily caps. Space polling to fit limits.
  • Make.com operation costs: Each poll and HTTP call consumes operations. Set polling frequency to match urgency.
  • Markdown escaping: Unescaped characters break messages. Always escape _*[]()~\>#+-=|{}.!` when using MarkdownV2.
  • Attachment size: Telegram has file size limits. Large attachments will fail. Prefer links to the Gmail thread.
  • Privacy and compliance: Remove signatures and disclaimers if not needed. Mask confidential data.
  • Threading: Telegram chats are linear. Do not expect email-like threading. Use short prefixes for categorization.
  • Reliability Add retries on 429 and transient failures. Log responses for audit.
  • Group permissions. For groups, ensure the bot can post. Check slow mode and anti-spam settings.
  • HTML emails: Rich HTML may not render well. Prefer plain-text excerpts.

Conclusion

A label-driven filter in Gmail and a Telegram bot produce a simple email-to-chat workflow. Make.com offers a fast, no-code path with dedupe and budgets. Apps Script provides a lightweight code solution inside Google Workspace. Start with plain text and a small polling interval. Add attachments and formatting only after the core path is stable.

Fixes (common pitfalls with solutions and troubleshooting tips)

  • No Telegram delivery. Verify chat_id by calling getUpdates after messaging the bot. Confirm the bot is added to the group.
  • HTTP 401/403. Token is wrong or bot lacks permission. Regenerate the BotFather token and update secrets.
  • Duplicate forwards. Add a Data Store or Script Properties set keyed by Gmail Message-Id. Do not rely on the subject line.
  • Gmail label not found. Create the label exactly. Names are case sensitive in your script.
  • Markdown errors like “can’t parse entities”. Escape all special characters or switch to parse_mode: "HTML" after sanitizing tags.
  • Over-polling. Increase the interval. Batch process recent threads in pages.
  • Large emails. Truncate the body to a fixed length and include the Gmail thread link.
  • Attachments failing. Check Telegram file size. Use sendDocument and blobs in Apps Script.
  • Make.com scenario idle. Check the connection, reauthorize Gmail, and ensure the label has new messages since the last run.
  • Apps Script trigger not firing. Use a time-driven trigger, not an on-open trigger. Grant permissions on first run.