Skip to main content

Send Cryptohopper MCP reports to Telegram, Discord, or email

Learn how to send Cryptohopper MCP reports to Telegram, Discord, or email — with setup scripts, channel comparison, and fixes for the most common delivery issues.

Written by Isaac


Prerequisites

  • A working MCP workflow that produces the content you want to deliver — see how to build a daily top-movers report for a template.

  • A scripting environment for the delivery step. Python or Node.js are the common choices. Delivery is a separate step after the MCP produces its output — it is not part of the MCP itself.

  • Credentials for your destination channel: a bot token for Telegram or Discord, or SMTP / transactional email credentials for email.


Setup — Telegram

  1. Create a Telegram bot

    Message @BotFather, send /newbot, and follow the prompts. Save the bot token.

  2. Get your chat ID

    Send any message to your Telegram bot, then fetch the URL below. The chat.id in the response is what you need.

    https://api.telegram.org/bot<YOUR_TOKEN>/getUpdates

  3. Test delivery

    import requests

    TOKEN = "your_bot_token"
    CHAT_ID = "your_chat_id"

    def send_telegram(text: str) -> None:
    requests.post(
    f"https://api.telegram.org/bot{TOKEN}/sendMessage",
    json={
    "chat_id": CHAT_ID,
    "text": text,
    "parse_mode": "Markdown",
    },
    ).raise_for_status()

    send_telegram("Hello from your Cryptohopper MCP workflow")

Telegram supports Markdown, so tables and bold formatting from the MCP render natively. Messages over 4,096 characters must be split — chunk long reports at paragraph boundaries.


Setup — Discord

  1. Create a webhook

    In your Discord server, open a channel → gear icon → Integrations → Webhooks → New Webhook. Copy the webhook URL.

  2. Test delivery

import requests

WEBHOOK = "https://discord.com/api/webhooks/..."

def send_discord(text: str) -> None:
requests.post(WEBHOOK, json={"content": text}).raise_for_status()

send_discord("Hello from your Cryptohopper MCP workflow")

Discord's content limit is 2,000 characters per message — split long reports. Discord has no native table support; format tables as aligned code blocks using triple backticks. For richer output, use Discord embeds (title, description, fields) instead of raw content.


Setup — Email

Two options: SMTP via Gmail or similar (simple, free, rate-limited) or a transactional service like SendGrid, Mailgun, or Resend (reliable, preferred for scheduled jobs).

SendGrid example

import os
import requests

API_KEY = os.environ["SENDGRID_API_KEY"]
FROM = os.environ.get("ALERT_FROM_EMAIL", "[email protected]")
TO = os.environ.get("ALERT_TO_EMAIL", "[email protected]")

def send_email(subject: str, body: str) -> None:
requests.post(
"https://api.sendgrid.com/v3/mail/send",
headers={"Authorization": f"Bearer {API_KEY}"},
json={
"personalizations": [{"to": [{"email": TO}]}],
"from": {"email": FROM},
"subject": subject,
"content": [{"type": "text/markdown", "value": body}],
},
).raise_for_status()

send_email("Daily top movers", "## Today's movers\n...")


Use Markdown or HTML for emailed reports — plain text quickly becomes unreadable for tabular data.


Choosing the right channel

Channel

Best for

Constraints

Telegram

Real-time alerts, on your phone, one-glance reading

4,096 char limit, Markdown-lite

Discord

Shared team channel, rich formatting, discussion around alerts

2,000 char limit, no real tables

Email

Longer digests, archival, anything you'll want to search later

Higher latency, easy to ignore

Ad-hoc alerts → Telegram. Team-visible output → Discord. Morning digests → email.


The canonical pattern

Keep the MCP step and the delivery step separate. A failure in delivery (network blip, expired token) should not lose the MCP output — you can still recover it from logs.

# 1. Run MCP workflow (produces text output)
report_text = run_mcp_workflow(prompt)

# 2. Deliver
try:
send_telegram(report_text)
except Exception as e:
log(f"Delivery failed: {e}")

Troubleshooting

Telegram getUpdates returns an empty list

You haven't sent a message to the bot yet, or you're querying with a different token. Send any message from your Telegram account to the bot first, then retry.

Discord webhook returns 429

Rate limit hit. Discord webhooks allow roughly 30 messages per minute — respect the Retry-After header. For high-frequency alerts, batch multiple events into one message rather than firing one per event.

Emails are landing in spam

Use a proper From address on a domain you own, with SPF/DKIM/DMARC configured. Avoid all-caps subject lines, trigger words, and attachments. A transactional service handles most of this correctly; plain SMTP from a shared provider often doesn't.

Long MCP reports get truncated in Telegram or Discord

Build a splitter that chunks at paragraph boundaries. Don't split in the middle of a table row — cut after the last complete row that fits, and open the next message with a (cont'd) header.

Delivery silently fails on scheduled runs but works manually

The most common cause is a missing environment variable or expired token in the scheduled environment. Cron runs without your shell's env; GitHub Actions needs secrets configured. Log the full error on every failure so it surfaces immediately.

You only want alerts when something actually flags

Make delivery conditional — don't send "nothing to report" messages, they become noise and you'll start ignoring the channel.

You want to deliver to multiple channels at once

Wrap delivery in a fan-out so each channel is independent and a failure in one doesn't block the others: for ch in [send_telegram, send_discord, send_email]: try ch(text).

Did this answer your question?