Previously, I created a simple reminder application with AWS Lambda and Simple Notification Service (SNS) SMS messages. With so many instant messenger applications these days, it is about time I move away from using SMS messages, specifically to Telegram.
Requirements
There are no changes to the previous requirements. To recap:
- To send a text message to people once a month
- Cheap
- Serverless on AWS
One of my main motivation to move away from SNS is due to the costs of publishing SMS messages. I wanted to bring that cost down even further.
Architecture

Similar to the previous application, I will still be using CloudWatch Events to trigger a Lambda function in a monthly interval to send the reminder message.
Previously, I had to manually subscribe mobile numbers to the SNS topic. The improvement this time for the Telegram bot comes from user self-service on-boarding to subscribe to the reminder notification, which is actually just a simple /start to the bot.
How the Bot works
Every day, a Lambda function (named Register) will be triggered to call the Telegram Bot API to retrieve any outstanding /start initiation. Upon detecting an outstanding message, the chat ID and username will be extracted and appended to the DynamoDB table.
Every month, a Lambda function (named sendMessage) will be triggered to scan the above-mentioned DynamoDB table, and recursively send out a custom Telegram message via the bot.
How to Configure the Bot
Step 1: Create a Telegram Bot with Botfather
Following the guide here: https://core.telegram.org/bots, you start by looking for @Botfather in Telegram and following the instructions to create a new bot. Give your bot a meaningful name, description and profile picture.

Take note of the token that Botfather assigns to you; it will be required for your Lambda functions to call your bot API.
Step 2: Create a DynamoDB Table
Telegram Chat IDs will be unique for every user that initiated a chat with my Telegram Bot. This made chat IDs a great candidate as the primary partition key. I set the primary partition key as chat_id (Number).
I also configured on-demand Read/Write Capacity modes, because I knew my DynamoDB Table is not going to be accessed frequently.

Remember to take note of the DynamoDB Table name, you will need it for the next step.
Step 3: Create Lambda functions (register and sendMessage)
Go to the Lambda console page and create 2 Lambda functions, 1 for the registration workflow and 1 for sending messages. The codes for the Lambda functions are as follows (I used the Node.js runtime):
// Lambda function For Registration
var AWS = require("aws-sdk")
var docClient = new AWS.DynamoDB.DocumentClient({region: "ap-southeast-1"})
const telegram = require('telegram-bot-api')
const tableName = process.env.DDB_TABLE_NAME
var bot = new telegram({
    token: process.env.TG_API_KEY,
    updates: {
        enabled: true
    }
})
const mp = new telegram.GetUpdateMessageProvider()
bot.setMessageProvider(mp)
bot.start()
.then(() => {
    console.log('API is started')
})
.catch(console.err)
exports.handler = () => {
    bot.on('update', update => {
        
        console.log(update)
        var params = {
            TableName: tableName,
            Item: { 
                chat_id: update.message.chat.id
            }
        }
        console.log("Adding a new item...")
        docClient.put(params, (err) => {
            if (err) {
                console.log('error cant read.', JSON.stringify(err, null, 2))
                bot.sendMessage({
                    chat_id: update.message.chat.id,
                    text: `Registration to Mr Gentle Reminderer is unsuccessful. Please let Zhen Kai know of the error. ${JSON.stringify(err, null, 2)}`
                }).catch(error => { console.log(error) })
            } else {
                console.log('succeed')
                bot.sendMessage({
                    chat_id: update.message.chat.id,
                    text: 'Registration to Mr Gentle Reminderer is successful! The gentle reminder will be sent on the first day of every month. Thanks!'
                }).catch(error => { console.log(error) })
            }
        })
    })
}// Lambda function for sending messages
var AWS = require("aws-sdk")
var docClient = new AWS.DynamoDB.DocumentClient({region: "ap-southeast-1"})
const telegram = require('telegram-bot-api')
const tableName = process.env.DDB_TABLE_NAME
var bot = new telegram({
    token: process.env.TG_API_KEY,
    updates: {
        enabled: true
    }
})
var params = {
    TableName: tableName,
    ProjectionExpression: "chat_id"
}
exports.handler = () => {
    docClient.scan(params, (err, data) => {
        if (err) {
            console.error("Unable to scan the table. Error JSON:", JSON.stringify(err, null, 2))
        } else {
            console.log("Scan succeeded")
            data.Items.forEach(chatIdObj => {
                console.log('Chat ID is:', chatIdObj.chat_id)
                bot.sendMessage({
                    chat_id: chatIdObj.chat_id,
                    text: 'Gentle reminder to transfer the monthly subscription fee to the subscriber. Thanks!'
                }).catch(error => { console.log(error) })
            })
            
        }
    })
}A few things to note:
- I used the Telegram Bot API NPM package for calling the Bot APIs.
- I zipped the npm package together with my Lambda code before I uploaded it.
- I used the DDB_TABLE_NAME environment variable for my Lambda function to know which table to call. Assign your DynamoDB Table to this variable.
- I used the TG_API_KEY environment variable for my Lambda function to know which Bot API to call. Assign your Bot token to this variable.
- Remember to give your Lambda functions the right IAM permissions to call DynamoDB.
Step 4: Configure CloudWatch Events
At the CloudWatch console page, under Events, create a new Event with the cron expression you want to trigger your respective Lambda functions.
I wanted to consolidate registration once a day and send messages once a month, so I used the following cron expressions:
- Daily register: cron(0 10 * * ? *)
- Monthly sendMessage: cron(0 11 1 * ? *)
Head over to the Lambda console page and choose “+ Add trigger”, and choose EventBridge (CloudWatch Events) and select the CloudWatch events you have created.

Summary
There are still a lot of room for improvement for this simple Telegram bot I hacked together over the weekend. A quicker feedback during bot registration using webhooks is a future improvement I would want to make.
I hope you had fun with this little project. Reach out to me if you have any feedback.
 
							
Hi zhenkai,
It is awesome program, thanks a lot for sample, and i wanted to know is it full of cost free? Does AWS bill for cloud watch triggers?