In Mail IP

In Mail IP

I was planning to run a home server and access it remotely. But there was an issue. My ISP only provides static IPs for businesses. As a household user, I could get a public IP. The issue with that kind of IP is that it is subject to change without notice. As a solution, I have developed a Telegram bot that fetches the new IP whenever I request it. The following describes the procedure to set this up. After that, I will present another way to get the new IP in your mailbox whenever it changes.

First, we need to create a new bot on Telegram. To that end, you can search for @BotFather and follow the prompt. When you finish choosing the name and ID for your new bot, it will give you an access token. You shall not share that token.

Create a directory for your script and run the following command to set up a virtual environment for Python.

python3 -m venv .venv
. .venv/bin/activate        

We are going to use python-telegram-bot. Activate your virtual environment and run the installation command.

pip install python-telegram-bot --upgrade
pip install requests        

The following is the script that we are going to run.

import logging, requests
from telegram import (
    Update
)
from telegram.ext import (
    Application,
    CommandHandler,
    ContextTypes
)

bot_father_token = "YOUR_TOKEN"
your_username = "YOUR_TELEGRAM_ID"

async def getCommand(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
    logging.info(f"{update.effective_user}, {update.effective_chat}")
    if update.effective_user.username != your_username:
        return
    await update.message.reply_text("...")
    ip = requests.get('https://checkip.amazonaws.com').text.strip()
    await update.message.reply_text(ip)

def main() -> None:
    logging.basicConfig(filename="ipbot.log", encoding='utf-8', level=logging.INFO)
    application = Application.builder().token(bot_father_token).connect_timeout(30).build()
    application.add_handler(CommandHandler("get", getCommand))
    application.run_polling()

if __name__ == "__main__":
    main()        

On your phone, when you send the /get command to your bot, it fetches the current IP address.

No alt text provided for this image

The downside of this approach is that you will not get notified when the IP changes and will realize that after failing to connect to your server. One solution is to check periodically for IP changes. But such a thing is impossible because by running application.run_polling(), we are giving control to our bot. Hence our second solution.

Some Python libraries are trying to provide an email client. One major issue is the two step verifications. On top of that, Most providers won't allow their services to be used in that way. Luckily AWS has a free-of-charge service named SES (Simple Email Service). Setting up SES is a bit more involved than creating a Telegram bot. In sandbox mode, you must register the emails you are sending from and to (Verified identities.) You also need an Access Key for AWS Client in Python.

No alt text provided for this image
The SES Service
No alt text provided for this image
Verified Email Addresses in SES
No alt text provided for this image
Created Access Key - Security Credentials Menus

First, we install the AWS Python SDK and schedule module:

pip install boto3
pip install schedule        

Afterward, you can put your email address and Access key in the following script; however, it is discouraged to store your key in a plain file.

import logging, time, socket, requests, schedule, boto3
from botocore.exceptions import ClientError

IP = ''

def getCurrentIP() -> str:
    return requests.get('https://checkip.amazonaws.com').text.strip()

def emailNewIP(ip: str) -> bool:
    SENDER = "YOUR_NAME <FROM_YOUR_EMAIL>" # keep < and >
    RECIPIENT = "TO_YOUR_EMAIL"
    AWS_REGION = "ap-southeast-2" # can find it in server address
    SUBJECT = "New IP Assignment"
    BODY_TEXT = f"{ip} assigned to `{socket.gethostname()}`."
    CHARSET = "UTF-8"
    
    client = boto3.client('ses',
                          region_name=AWS_REGION,
                          aws_access_key_id="YOUR_KEY",
                          aws_secret_access_key="YOUR_SECRET"
                          )
    
    try:
        response = client.send_email(
            Destination = {'ToAddresses': [RECIPIENT]},
            Message = {
                'Body': {'Text': {'Charset': CHARSET,'Data': BODY_TEXT}},
                'Subject': {'Charset': CHARSET,'Data': SUBJECT},
                },
            Source = SENDER
        )
    except ClientError as e:
        logging.critical(e.response['Error']['Message'])
        return False
    else:
        logging.info(f"Email sent to {RECIPIENT} with new IP {ip}.")
        logging.info(response['MessageId'])
        return True

def periodic_task():
    global IP
    if (ip := getCurrentIP()) != IP:
        if emailNewIP(ip):
            IP = ip

schedule.every(60).seconds.do(periodic_task) # every minute

def main() -> None:
    logging.basicConfig(filename="in_mail_ip.log", encoding='utf-8', level=logging.INFO)
    while True:
        schedule.run_pending()
        time.sleep(1)

if __name__ == "__main__":
    main()        

That will periodically monitor your IP for a change and notify you when it happens.

We are not finished yet. Assuming you are running Linux, you need to add your script to services so that it can run automatically on reboots. Use the following template to define a new service at /etc/systemd/system/SERVICE_NAME:

[Unit]
Description=My Service
After=network.target

[Service]
Type=idle
WorkingDirectory=<dir>
User=<username>
ExecStart=<dir>/.venv/bin/python3 <dir>/script.py
Restart=always
RestartSec=10

[Install]
WantedBy=multi-user.target        

Next, enable the service:

systemctl daemon-reload
systemctl enable SERVICE_NAME
systemctl start SERVICE_NAME        

You should be good to go. Both scripts are available in a GitHub repository. I will appreciate it if you share your own solution to this or a similar problem.


To view or add a comment, sign in

More articles by Mohammad Rahimi

  • Programming Policies

    Picture this situation: you've got a continuous flow of data coming in, and your task is to process it and present the…

  • Exception x Exception

    In my previous post, we demystified std::forward. In this one, we'll explore what happens when you encounter another…

  • Forward Demystified

    In this post I have shed some light on one of the darker corners of the C++ language: std::forward. The code snippets…

  • Maintainer's Dream

    The distributed workflow for Linux source code development is called Benevolent Dictator. Some maintainers collect…

    2 Comments
  • FTowerX

    A few years ago, I was developing a program for remotely controlling a signal generator. I wanted to create a simple…

    1 Comment
  • GitCheat

    In my previous post, I introduced a long-existing tool that helps you migrate your workflow from Perforce to Git. Here,…

  • Perforce meets Git

    Perforce is a Centralised Version Control System founded in 1995 and used by many companies. Git came into existence…

  • StateBench

    While developing software, from large to small scale, you can use state machines to reduce code complexity and help…

  • Dot Bash History

    I have prepared a GitHub repository where you can find many useful commands and scripts. The following explains how you…

  • C++ and Benchmarking

    It may sound extreme, but I believe benchmarking too should get the special treatment of testing in a project. There…

Others also viewed

Explore content categories