How to send text messages for free using Python | Use Python to send text messages via email
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 |
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 |
And that's how you can send SMS and MMS for free using Python!