How to send text messages for free using Python | Use Python to send text messages via email

Jan 11, 2022 · 15 minute read

If you want to skip the work, you can use 🔗etext - a Python module I created using the same technique explained in this blog post.

Okay, first let's get on the same page with the terms, we will be using to build this. SMS or short messaging service is what a text message is; which is limited to 160 characters and sent over mobile networks. And MMS or multimedia messaging service is basically the same thing as a text message but instead with embedded multimedia like images videos or even pdf files which I didn't know you could send over text messages until about yesterday. To send free text messages what we'll be doing is using SMS gateways which are servers that serve as middlemen that can be used to deliver our messages to mobile phones via mobile networks.

Sending SMS messages

Unlike alternatives like Twilio, which lets you send text messages programmatically using their API for a fee, these SMS and MMS gateways can be used for free because one of the ways we can interact with them is by using email. In other words, these gateways which have been set up by the mobile providers allow us to send our text messages in the form of an email message and then forward our messages to mobile phones via SMS or MMS.

To make your life easier I have taken the liberty of aggregating a list of SMS and MMS email domains for U.S phone providers:

providers.py

PROVIDERS = {
    "AT&T": {"sms": "txt.att.net", "mms": "mms.att.net", "mms_support": True},
    "Boost Mobile": {
        "sms": "sms.myboostmobile.com",
        "mms": "myboostmobile.com",
        "mms_support": True,
    },
    "C-Spire": {"sms": "cspire1.com", "mms_support": False},
    "Cricket Wireless": {
        "sms": "sms.cricketwireless.net ",
        "mms": "mms.cricketwireless.net",
        "mms_support": True,
    },
    "Consumer Cellular": {"sms": "mailmymobile.net", "mms_support": False},
    "Google Project Fi": {"sms": "msg.fi.google.com", "mms_support": True},
    "Metro PCS": {"sms": "mymetropcs.com", "mms_support": True},
    "Mint Mobile": {"sms": "mailmymobile.net", "mms_support": False},
    "Page Plus": {
        "sms": "vtext.com",
        "mms": "mypixmessages.com",
        "mms_support": True,
    },
    "Republic Wireless": {
        "sms": "text.republicwireless.com",
        "mms_support": False,
    },
    "Sprint": {
        "sms": "messaging.sprintpcs.com",
        "mms": "pm.sprint.com",
        "mms_support": True,
    },
    "Straight Talk": {
        "sms": "vtext.com",
        "mms": "mypixmessages.com",
        "mms_support": True,
    },
    "T-Mobile": {"sms": "tmomail.net", "mms_support": True},
    "Ting": {"sms": "message.ting.com", "mms_support": False},
    "Tracfone": {"sms": "", "mms": "mmst5.tracfone.com", "mms_support": True},
    "U.S. Cellular": {
        "sms": "email.uscc.net",
        "mms": "mms.uscc.net",
        "mms_support": True,
    },
    "Verizon": {"sms": "vtext.com", "mms": "vzwpix.com", "mms_support": True},
    "Virgin Mobile": {
        "sms": "vmobl.com",
        "mms": "vmpix.com",
        "mms_support": True,
    },
    "Xfinity Mobile": {
        "sms": "vtext.com",
        "mms": "mypixmessages.com",
        "mms_support": True,
    },
}

As a bonus, I recently found this site, which has SMS gateways for multiple countries, which might be helpful if you are not in the US: 🔗 https://email2sms.info/

As a quick explanation, this is how it works using a 10 digit U.S phone number:

We can take the phone number followed by the @ symbol, followed by the domain of the SMS or MMS server.

number@gateway-domain.com

Other than having the email domain for SMS or MMS for your provider, you also need to have an email provider which gives you access to its SMTP servers. For our example, we will be using Gmail and Gmail's SMTP server. So you will need to have a Gmail account. You should also set up an app password as a way to log in to the SMTP server without needing two-step verification.

You can do this by going to https://myaccount.google.com/apppasswords. Select email for the app drop-down and any device; then you will get a password which we can later use to authenticate with Gmail's SMTP server.

Now let's get to the code. The first thing we have to do is import some modules that we're gonna be using we'll be using email to format the emails later. We also need the smtp library we'll be using that to send our emails through the SMTP servers. We also need ssl and we'll be using that as our connection with the SMTP servers. And then, one more thing we're going to add is the providers from before.

main.py

import email, smtplib, ssl
from providers import PROVIDERS

and now that we have this let's start making the SMS portion now our program. It takes a few parameters:

main.py

def send_sms_via_email(
    number: str,
    message: str,
    provider: str,
    sender_credentials: tuple,
    subject: str = "sent using etext",
    smtp_server: str = "smtp.gmail.com",
    smtp_port: int = 465,
):

The first one is the most obvious one which is the actual number that we'll be using to send these emails. We also need the actual message that we'll be sending and this again will be of type string next, we'll use the provider also of type string the provider is going to be the carrier, which again corresponds to one of these providers from before. Then we need the sender credentials and the credentials will involve not only the email that will be used to send these out but also that password that we got earlier using app passwords from Google.

The subject will also be of type string. One thing here is that I've noticed that certain SMS gateways won't allow you to send out an email without having a properly structured email and that's why we're including a subject. Next, we'll add a parameter for the SMTP server.

In this case, we're using Gmail to send the emails, so the default for that is smtp.gmail.com. One more thing we need is the port that's going to be used to send these emails. You don't need to know much about this if you're using Gmail but if you're using a different SMPT provider or different email server you might want to go ahead and see if they use a different port to send emails. In our case, the SMTP port is going to be of type integer.

The first thing we should define is the sender email and the email password for that sender now this information we're going to be getting from the sender credentials

main.py

def send_sms_via_email(
    number: str,
    message: str,
    provider: str,
    sender_credentials: tuple,
    subject: str = "sent using etext",
    smtp_server: str = "smtp.gmail.com",
    smtp_port: int = 465,
):
    sender_email, email_password = sender_credentials

and like I mentioned before, the receiver email is made up of a phone number followed by the domain of the SMS gateway. To format that I'm going to use an f-string here.

main.py

def send_sms_via_email(
    number: str,
    message: str,
    provider: str,
    sender_credentials: tuple,
    subject: str = "sent using etext",
    smtp_server: str = "smtp.gmail.com",
    smtp_port: int = 465,
):
    sender_email, email_password = sender_credentials
    receiver_email = f'{number}@{PROVIDERS.get(provider).get("sms")}'

Awesome now let's format our email message. We'll make a variable called email message now like I said before some providers won't allow you to send messages unless it's structured like an actual email that means that what we send doesn't only need a message but, also needs a subject and who is being sent to so again we're going to use an f string here.

main.py

def send_sms_via_email(
    number: str,
    message: str,
    provider: str,
    sender_credentials: tuple,
    subject: str = "sent using etext",
    smtp_server: str = "smtp.gmail.com",
    smtp_port: int = 465,
):
    sender_email, email_password = sender_credentials
    receiver_email = f'{number}@{PROVIDERS.get(provider).get("sms")}'

    email_message = f"Subject:{subject}\nTo:{receiver_email}\n{message}"

To send the email we're going to use a context manager. If you're not familiar with that it's just a way for the SMTP server connection to exit graciously after we're done with everything so we'll do with we'll use the smtp lib here we'll create an object using SMTP_SSL as well as use that instance to authenticate with the SMTP server:

main.py

def send_sms_via_email(
    number: str,
    message: str,
    provider: str,
    sender_credentials: tuple,
    subject: str = "sent using etext",
    smtp_server: str = "smtp.gmail.com",
    smtp_port: int = 465,
):
    sender_email, email_password = sender_credentials
    receiver_email = f'{number}@{PROVIDERS.get(provider).get("sms")}'

    email_message = f"Subject:{subject}\nTo:{receiver_email}\n{message}"

    with smtplib.SMTP_SSL(
        smtp_server, smtp_port, context=ssl.create_default_context()
    ) as email:
        email.login(sender_email, email_password)
        email.sendmail(sender_email, receiver_email, email_message)

Let's send an SMS message! I'm just going to quickly create the main method and then inside this main method let's call send_sms_via_email

main.py

def main():
    number = "5623720883"
    message = "hello world!"
    provider = "T-Mobile"

    sender_credentials = ("email@domain.com", "password")

    send_sms_via_email(number, message, provider, sender_credentials)

To run our program we'll use the __name__ dunder method

main.py

if __name__ == "__main__":
    main()

Sending MMS messages

Now that we have that let me show you how to send an MMS message. Everything is pretty similar so I'm going to start with this:

main.py
def send_mms_via_email(
    number: str,
    message: str,
    provider: str,
    sender_credentials: tuple,
    subject: str = "sent using etext",
    smtp_server: str = "smtp.gmail.com",
    smtp_port: int = 465,
):
    sender_email, email_password = sender_credentials
    receiver_email = f'{number}@{PROVIDERS.get(provider).get("sms")}'

Since we need a file to send a multimedia message let's add it as a parameter. It's going to be a file path of type string. Something else we'll need is the mime-type of the file, so let's add a mime main type and a mime subtype to our parameters. I'll get into what this means in a bit.

main.py
def send_mms_via_email(
    number: str,
    message: str,
    file_path: str,
    mime_maintype: str,
    mime_subtype: str,
    provider: str,
    sender_credentials: tuple,
    subject: str = "sent using etext",
    smtp_server: str = "smtp.gmail.com",
    smtp_port: int = 465,
):

Let's import more things, that we need to send MMS.

main.py
from email import encoders
from email.mime.base import MIMEBase
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText

from os.path import basename

Now what's going to change in the MMS function is the way we send an email. So instead of sending an email that shows the subject, the receiver, and the message; we need to create a multi-part email. The multi-part being (1) the message itself that we're sending and (2) the file that we're attaching to that email.

If we go back to our providers' list, you'll notice that some providers might not give you the ability to send multimedia messages or will use a single domain for both short messages and multimedia messages so because of that I have noted this in the list I aggregated. With the use of an SMS-supported key.

For example, T-Mobile does not have an MMS key, but it does support MMS, which means that we can use the same SMS domain to send both SMS and MMS. However, AT&T for example has both an SMS and MMS domain. So you need to use the appropriate one depending on what you're sending.

We'll use email_message like before, but instead, we'll use a multi-part object. We also need to add our email attributes. We'll use email_message to add the subject, receiver (whoever's receiving our email), and sender (who is sending the email).

main.py
def send_mms_via_email(
    number: str,
    message: str,
    file_path: str,
    mime_maintype: str,
    mime_subtype: str,
    provider: str,
    sender_credentials: tuple,
    subject: str = "sent using etext",
    smtp_server: str = "smtp.gmail.com",
    smtp_port: int = 465,
):

    sender_email, email_password = sender_credentials
    receiver_email = f'{number}@{PROVIDERS.get(provider).get("sms")}'

 ​   ​email_message​ ​=​ ​MIMEMultipart​()
 ​   ​email_message​[​"Subject"​] ​=​ ​subject
 ​   ​email_message​[​"From"​] ​=​ ​sender_email
 ​   ​email_message​[​"To"​] ​=​ ​receiver_email

Let's attach the message to our email, which is of mine type plain (plain text).

main.py
def send_mms_via_email(
    number: str,
    message: str,
    file_path: str,
    mime_maintype: str,
    mime_subtype: str,
    provider: str,
    sender_credentials: tuple,
    subject: str = "sent using etext",
    smtp_server: str = "smtp.gmail.com",
    smtp_port: int = 465,
):

    sender_email, email_password = sender_credentials
    receiver_email = f'{number}@{PROVIDERS.get(provider).get("sms")}'

 ​   ​email_message​ ​=​ ​MIMEMultipart​()
 ​   ​email_message​[​"Subject"​] ​=​ ​subject
 ​   ​email_message​[​"From"​] ​=​ ​sender_email
 ​   ​email_message​[​"To"​] ​=​ ​receiver_email

 ​   ​email_message​.​attach​(​MIMEText​(​message​, ​"plain"​))

What are MIME types?

If you're not familiar with MIME types, It's just a declaration of a type of file or a type of piece of content. In this case, mime text is telling us that the piece of content that we're using is text. Similarly, To attach the file that we're going to be sending, we will have to change specify the MIME type.

Attaching the file

Using a context manager we'll add the file path that we're passing in our parameters. Then we also need to specify a read type in this case we'll read the bytes. Using MIMEBase, we can specify the file's MIME type. The main type comes first and then we have the mine subtype. Then we'll set the payload which is the content we'll be using. In this case that's the file attachment. To send a file we need to encode it as base64, so from the encoders that we imported earlier, we'll encode the media to base64. We also need to add a header. The header is going to be of type content-disposition and for that, we want to add the file name that we're sending. Then, we can attach that to our email right we have to attach our actual file to our email as we did before with our message email:

main.py
def send_mms_via_email(
    number: str,
    message: str,
    file_path: str,
    mime_maintype: str,
    mime_subtype: str,
    provider: str,
    sender_credentials: tuple,
    subject: str = "sent using etext",
    smtp_server: str = "smtp.gmail.com",
    smtp_port: int = 465,
):

    sender_email, email_password = sender_credentials
    receiver_email = f'{number}@{PROVIDERS.get(provider).get("sms")}'

 ​   ​email_message​ ​=​ ​MIMEMultipart​()
 ​   ​email_message​[​"Subject"​] ​=​ ​subject
 ​   ​email_message​[​"From"​] ​=​ ​sender_email
 ​   email_message​[​"To"​] ​=​ ​receiver_email

 ​   email_message​.​attach​(​MIMEText​(​message​, ​"plain"​))

    with open(file_path, "rb") as attachment:
        part = MIMEBase(mime_maintype, mime_subtype)
        part.set_payload(attachment.read())

        encoders.encode_base64(part)
        part.add_header(
            "Content-Disposition",
            f"attachment; filename={basename(file_path)}",
        )

        email_message.attach(part)

The last thing we have to do is send our email, but we can't just send our email of type mime multi-part. We have to convert that into text outside of the context manager.

main.py
def send_mms_via_email(
    number: str,
    message: str,
    file_path: str,
    mime_maintype: str,
    mime_subtype: str,
    provider: str,
    sender_credentials: tuple,
    subject: str = "sent using etext",
    smtp_server: str = "smtp.gmail.com",
    smtp_port: int = 465,
):

    sender_email, email_password = sender_credentials
    receiver_email = f'{number}@{PROVIDERS.get(provider).get("sms")}'

 ​   ​email_message​ ​=​ ​MIMEMultipart​()
 ​   ​email_message​[​"Subject"​] ​=​ ​subject
 ​   ​email_message​[​"From"​] ​=​ ​sender_email
 ​   email_message​[​"To"​] ​=​ ​receiver_email

 ​   email_message​.​attach​(​MIMEText​(​message​, ​"plain"​))

    with open(file_path, "rb") as attachment:
        part = MIMEBase(mime_maintype, mime_subtype)
        part.set_payload(attachment.read())

        encoders.encode_base64(part)
        part.add_header(
            "Content-Disposition",
            f"attachment; filename={basename(file_path)}",
        )

        email_message.attach(part)

    text = email_message.as_string()

So now if we go down here we have the same thing as before from the SMS function but we have to change our email message to that text that we just made.

main.py
def send_mms_via_email(
    number: str,
    message: str,
    file_path: str,
    mime_maintype: str,
    mime_subtype: str,
    provider: str,
    sender_credentials: tuple,
    subject: str = "sent using etext",
    smtp_server: str = "smtp.gmail.com",
    smtp_port: int = 465,
):

    sender_email, email_password = sender_credentials
    receiver_email = f'{number}@{PROVIDERS.get(provider).get("sms")}'

 ​   ​email_message​ ​=​ ​MIMEMultipart​()
 ​   ​email_message​[​"Subject"​] ​=​ ​subject
 ​   ​email_message​[​"From"​] ​=​ ​sender_email
 ​   email_message​[​"To"​] ​=​ ​receiver_email

 ​   email_message​.​attach​(​MIMEText​(​message​, ​"plain"​))

    with open(file_path, "rb") as attachment:
        part = MIMEBase(mime_maintype, mime_subtype)
        part.set_payload(attachment.read())

        encoders.encode_base64(part)
        part.add_header(
            "Content-Disposition",
            f"attachment; filename={basename(file_path)}",
        )

        email_message.attach(part)

    text = email_message.as_string()

    with smtplib.SMTP_SSL(
        smtp_server, smtp_port, context=ssl.create_default_context()
    ) as email:
        email.login(sender_email, email_password)
        email.sendmail(sender_email, receiver_email, text)

Let's create the main function (without the SMS version). It's similar to the SMS version, but we have to add the file path and the mime type. To send a png image that's going to be a main type of image and then the subtype is almost always the actual extension of the file and this gives us a png.

main.py

def main():
    file_path = "/path/to/file/file.png"

    mime_maintype = "image"
    mime_subtype = "png"

    number = "5623720883"

    message = "hello world!"
    provider = "T-Mobile"

    sender_credentials = ("email@domain.com", "password")

    send_mms_via_email(
        number,
        message,
        file_path,
        mime_maintype,
        mime_subtype,
        provider,
        sender_credentials,
    )

if __name__ == "__main__":
    main()

Here is the final program with both SMS and MMS support:

main.py

import email, smtplib, ssl
from providers import PROVIDERS

# used for MMS
from email import encoders
from email.mime.base import MIMEBase
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText

from os.path import basename

def send_sms_via_email(
    number: str,
    message: str,
    provider: str,
    sender_credentials: tuple,
    subject: str = "sent using etext",
    smtp_server: str = "smtp.gmail.com",
    smtp_port: int = 465,
):
    sender_email, email_password = sender_credentials
    receiver_email = f'{number}@{PROVIDERS.get(provider).get("sms")}'

    email_message = f"Subject:{subject}\nTo:{receiver_email}\n{message}"

    with smtplib.SMTP_SSL(
        smtp_server, smtp_port, context=ssl.create_default_context()
    ) as email:
        email.login(sender_email, email_password)
        email.sendmail(sender_email, receiver_email, email_message)

def send_mms_via_email(
    number: str,
    message: str,
    file_path: str,
    mime_maintype: str,
    mime_subtype: str,
    provider: str,
    sender_credentials: tuple,
    subject: str = "sent using etext",
    smtp_server: str = "smtp.gmail.com",
    smtp_port: int = 465,
):

    sender_email, email_password = sender_credentials
    receiver_email = f'{number}@{PROVIDERS.get(provider).get("sms")}'

    email_message=MIMEMultipart()
    email_message["Subject"] = subject
    email_message["From"] = sender_email
    email_message["To"] = receiver_email

    email_message.attach(MIMEText(message, "plain"))

    with open(file_path, "rb") as attachment:
        part = MIMEBase(mime_maintype, mime_subtype)
        part.set_payload(attachment.read())

        encoders.encode_base64(part)
        part.add_header(
            "Content-Disposition",
            f"attachment; filename={basename(file_path)}",
        )

        email_message.attach(part)

    text = email_message.as_string()

    with smtplib.SMTP_SSL(
        smtp_server, smtp_port, context=ssl.create_default_context()
    ) as email:
        email.login(sender_email, email_password)
        email.sendmail(sender_email, receiver_email, text)


def main():
    number = "5623720883"
    message = "hello world!"
    provider = "T-Mobile"

    sender_credentials = ("email@domain.com", "password")

    # SMS
    send_sms_via_email(number, message, provider, sender_credentials)

    # MMS
    file_path = "/path/to/file/file.png"

    mime_maintype = "image"
    mime_subtype = "png"

    send_mms_via_email(
        number,
        message,
        file_path,
        mime_maintype,
        mime_subtype,
        provider,
        sender_credentials,
    )


if __name__ == "__main__":
    main()

In case you are not sending a png image, here are some common MIME types:

Extension Kind of document MIME Type
.aac AAC audio audio/aac
.abw AbiWord document application/x-abiword
.arc Archive document (multiple files embedded) application/x-freearc
.avi AVI: Audio Video Interleave video/x-msvideo
.azw Amazon Kindle eBook format application/vnd.amazon.ebook
.bin Any kind of binary data application/octet-stream
.bmp Windows OS/2 Bitmap Graphics image/bmp
.bz BZip archive application/x-bzip
.bz2 BZip2 archive application/x-bzip2
.cda CD audio application/x-cdf
.csh C-Shell script application/x-csh
.css Cascading Style Sheets (CSS) text/css
.csv Comma-separated values (CSV) text/csv
.doc Microsoft Word application/msword
.docx Microsoft Word (OpenXML) application/vnd.openxmlformats-officedocument.wordprocessingml.document
.eot MS Embedded OpenType fonts application/vnd.ms-fontobject
.epub Electronic publication (EPUB) application/epub+zip
.gz GZip Compressed Archive application/gzip
.gif Graphics Interchange Format (GIF) image/gif
.htm .html HyperText Markup Language (HTML) text/html
.ico Icon format image/vnd.microsoft.icon
.ics iCalendar format text/calendar
.jar Java Archive (JAR) application/java-archive
.jpeg .jpg JPEG images image/jpeg
.js JavaScript text/javascript (Specifications: HTML and its reasoning, and IETF)
.json JSON format application/json
.jsonld JSON-LD format application/ld+json
.mid .midi Musical Instrument Digital Interface (MIDI) audio/midi audio/x-midi
.mjs JavaScript module text/javascript
.mp3 MP3 audio audio/mpeg
.mp4 MP4 video video/mp4
.mpeg MPEG Video video/mpeg
.mpkg Apple Installer Package application/vnd.apple.installer+xml
.odp OpenDocument presentation document application/vnd.oasis.opendocument.presentation
.ods OpenDocument spreadsheet document application/vnd.oasis.opendocument.spreadsheet
.odt OpenDocument text document application/vnd.oasis.opendocument.text
.oga OGG audio audio/ogg
.ogv OGG video video/ogg
.ogx OGG application/ogg
.opus Opus audio audio/opus
.otf OpenType font font/otf
.png Portable Network Graphics image/png
.pdf Adobe Portable Document Format (PDF) application/pdf
.php Hypertext Preprocessor (Personal Home Page) application/x-httpd-php
.ppt Microsoft PowerPoint application/vnd.ms-powerpoint
.pptx Microsoft PowerPoint (OpenXML) application/vnd.openxmlformats-officedocument.presentationml.presentation
.rar RAR archive application/vnd.rar
.rtf Rich Text Format (RTF) application/rtf
.sh Bourne shell script application/x-sh
.svg Scalable Vector Graphics (SVG) image/svg+xml
.swf Small web format (SWF) or Adobe Flash document application/x-shockwave-flash
.tar Tape Archive (TAR) application/x-tar
.tif .tiff Tagged Image File Format (TIFF) image/tiff
.ts MPEG transport stream video/mp2t
.ttf TrueType Font font/ttf
.txt Text, (generally ASCII or ISO 8859-n) text/plain
.vsd Microsoft Visio application/vnd.visio
.wav Waveform Audio Format audio/wav
.weba WEBM audio audio/webm
.webm WEBM video video/webm
.webp WEBP image image/webp
.woff Web Open Font Format (WOFF) font/woff
.woff2 Web Open Font Format (WOFF) font/woff2
.xhtml XHTML application/xhtml+xml
.xls Microsoft Excel application/vnd.ms-excel
.xlsx Microsoft Excel (OpenXML) application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
.xml XML application/xml is recommended as of RFC 7303 (section 4.1), but text/xml is still used sometimes. You can assign a specific MIME type to a file with .xml extension depending on how its contents are meant to be interpreted. For instance, an Atom feed is application/atom+xml, but application/xml serves as a valid default.
.xul XUL application/vnd.mozilla.xul+xml
.zip ZIP archive application/zip
.3gp 3GPP audio/video container video/3gpp; audio/3gpp if it doesn't contain video
.3g2 3GPP2 audio/video container video/3gpp2; audio/3gpp2 if it doesn't contain video
.7z 7-zip archive application/x-7z-compressed

🔗Source

And that's how you can send SMS and MMS for free using Python!