How to use Telegram Bot for Contact Form

I have deleted my Facebook account for 3 months. Now I use Telegram as the main contact point. When building this “small universe”, I find out that I can use Telegram API for sending messages. A light bulb goes off in my head with an idea, using Telegram Bot for the Contact Form.

How it works

This feature may be too overkill for a simple contact form, I do it for the sake of learning and creating things. Let’s jump into building progress.

Telegram Settings

Bot

Creating a Telegram Bot is very simple. Telegram has another bot handling that process. Its name is BotFather. All I have to do is type some commands.

Using /newbot command to create a new bot
Create a Bot with BotFather

After creating Bot, I receive an authentication token for using the Bots API. It looks like 992219941:AAEEoFKKPN1qJWYZkVoujOFcHysITXa8uAY. I’ll call <token> from now on.

Group

According to the API documentation, I need a chat_id of a group or a channel. The main reason is that a Bot cannot send direct messages to a User.

chat_id: Unique identifier for the target chat or username of the target channel (in the format @channelusername)

https://core.telegram.org/bots/api

When I have created a group, the first thing to do is give Administrator permission to the Bot.

Permission options
Set permission for the Bot

To find chat_id, I go to Group Type. A group is set to Private by default. It has a sharable link to join the group below, something like https://t.me/joinchat/<id>.

At first, I’m quite sure that the <id> here is chat_id, but when I put it into the API call, it fails immediately. After a lot of attempts, the only way to get the format @channelusername is to set Group Type to Public. So that I can have the group username as purpose. For me, chat_id will be @TheSmallUniverse.

Group Type configuration
Group Type in Public

Telegram API

Now I already have <token> and chat_id, the next step is to connect the API. Following the documentation, Send Message API is the only API that I need.

curl --location --request POST 'https://api.telegram.org/bot<token>/sendMessage' \
--header 'Content-Type: application/json' \
--data-raw '{
  "chat_id": "@TheSmallUniverse",
  "text": "Hello Darkness, my old friend!"
}'

And here is the response I get when running the command. There is a different detail, chat_id has changed to an Integer -1001258275042.

{
  "ok": true,
  "result": {
    "message_id": 5,
    "from": {
      "id": 992219941,
      "is_bot": true,
      "first_name": "The Small Bot",
      "username": "TheSmallBot"
    },
    "chat": {
      "id": -1001258275042,
      "title": "The Small Universe",
      "username": "TheSmallUniverse",
      "type": "supergroup"
    },
    "date": 1578890703,
    "text": "Hello Darkness, my old friend!"
  }
}

Contact Form

The configuration for Telegram is done. I need an UI to apply that functionality, and nothing better than a Contact Form.

User Interface

A meaningful message needs to have sender name, email and message content. As Telegram supports message formatting (Markdown & HTML), I come up with this interface.

On the client-side, I use Formik – a form validation library. When submitting the form, it calls an internal API, which is a simple serverless function.

Serverless Function

I use Next.js, which supports API Routes out of the box. If I don’t use Next.js, I could do this functionality with Zeit Serverless Functions or Netlify Functions.

The function looks like this:

export default async (req, res) => {
  const { name, message, email } = req.body

  if (!name || !message || !email) {
    res.status(400).json({
      ok: false,
      status: 400,
      message: "Missing required fields",
    })

    return
  }

  // https://core.telegram.org/bots/api#markdownv2-style
  const escapedEmail = email.replace(/_/g, "\\_")

  fetch(`https://api.telegram.org/bot${process.env.botToken}/sendMessage`, {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      chat_id: process.env.chatId,
      parse_mode: "Markdown",
      text: `“${message}”\n— ${name} <${escapedEmail}>`,
    }),
  })
    .then((response) => response.json())
    .then((data) => res.status(200).json(data))
    .catch((err) => res.status(err.status).json(err))
}

During local development, I create a .env file at the root of the project directory. It contains environment variables values.

CHAT_ID="@TheSmallUniverse"
BOT_TOKEN="992219941:AAEEoFKKPN1qJWYZkVoujOFcHysITXa8uAY"

For production, I add these values to Now Secrets. They will be available at Build Time. As I use Zeit Now service, all I have to do is config now.json file.

{
  "version": 2,
  "name": "The Small Things",
  "build": {
    "env": {
      "CHAT_ID": "@thesmallthings.chat-id",
      "BOT_TOKEN": "@thesmallthings.bot-token"
    }
  }
}

Then declare botToken and chatId in the next.config.js as environment variables.

// Use `dotenv` for local .env file
require("dotenv").config()

const nextConfig = {
  env: {
    chatId: process.env.CHAT_ID,
    botToken: process.env.BOT_TOKEN,
  },
}

Private Group

In my case, I don’t want to expose those messages for anyone in Telegram, so I set the group to Private.

At first, the Send Message API doesn’t accept @TheSmallUniverse as chat_id. Because when moving group to Private, its sharable link has changed. So the old chat_id won’t be available anymore. Luckily, I have the Integer chat_id from the previous API call.

{
  "chat": {
    "id": -1001258275042,
    "title": "The Small Universe",
    "username": "TheSmallUniverse",
    "type": "supergroup"
  }
}

Replace that value to .env file, the function works as expected.

CHAT_ID="-1001258275042"
BOT_TOKEN="992219941:AAEEoFKKPN1qJWYZkVoujOFcHysITXa8uAY"

But then, when I try to update Now Secrets for chat_id, I get this error

now secret add thesmallthings.chat-id -1001258275042
> Error! Invalid number of arguments. Usage: `now secret add <name> <value>`

That's because the secret value cannot contain the dash character. After searching for a while, I come across this solution.

At the root of the project directory, I create a chat-id.txt file, its content is the chat_id value. Then update the command to use that file:

now secret add -- thesmallthings.chat-id "`cat chat-id.txt`"
> Success! Secret thesmallthings.chat-id added [762ms]

The cat chat-id.txt takes the content of the chat-id.txt file and pastes it into the command line for me.

And, what’s done is done. Now I have an helpful way to receive messages from anyone.