Sending mission-critical emails in Python: SMTP or API?
If you need to send emails from an application, it’s a good idea to think about how your usage changes over time. As with many technology choices, there’s a tradeoff between up-front efficiency and long-term flexibility.
You have two main choices for sending emails through your Python application. The first is SMTP — the standard protocol for outgoing email. The second option sidesteps SMTP's complexities by using an email provider’s API for richer functionality.
This article compares sending email in Python using SMTP and API. SMTP is quick to set up—especially with a library like smtplib—but it won't scale as your app grows, puts you at risk of spam flags, and makes diagnosing problems complex. An API setup takes a little longer, but it yields dividends with features you can leverage in many ways.
Go lean with SMTP #
SMTP is a basic, universal protocol for sending email. Direct SMTP implementation is as close as you can get to DIY email, but even the simplest sending setup still requires a third-party SMTP server.
Technically speaking, you could host your own SMTP server, but it’s rarely a good tradeoff. Inbox providers pay close attention to sender reputation to determine which emails make it through. An established SMTP service helps ensure your emails are actually delivered. The code below uses the Postmark SMTP servers.
Start fast with common tools (like smtplib) #
Sending email with SMTP is straightforward and very similar in any application. Once set up, the advantage is that you can try a new service without completely overhauling your application — you can change SMTP service providers using the same code.
To implement SMTP in a Python application, use the built-in library called smtplib and follow these steps:
1. Import smtplib
You don’t need to install any new libraries — the SMTP module is included when you install Python. Just import it, along with MIMEText, which helps with email formatting:
import smtplib
from email.mime.text import MIMEText
2. Set up your SMTP server
The structure of this section of code stays the same for all SMTP servers and accounts, but you must update the details and credentials:
SMTP_SERVER = "smtp.postmarkapp.com" # Replace with your SMTP server
SMTP_PORT = 587 # Update with your port. We recommend using a TLS-enabled port
SMTP_USER = "string12345" # Your SMTP server username/token
SMTP_PASS = "passwordstring98765" # Your SMTP server password
FROM_ADDR = “no-reply@mailservice.com” # Your sender email address
Though we haven’t done it in this example, it’s a good idea to store your credentials as environment variables so they’re not exposed in public code.
3. Create an email message
The variables above can be reused across multiple email functions. Each function should create an email object:
to_addr = "recipient@example.com"
subject = "SMTP Test"
body = "Hello world! This is an SMTP service test!"
# Create the email object
test_message = MIMEText(body, "plain") # "plain" means text-only email
test_message["Subject"] = subject
test_message["To"] = to_addr
4. Send the email message
Pull all the pieces together in your script:
with smtplib.SMTP(SMTP_SERVER, SMTP_PORT) as server:
server.starttls() # Secure connection
server.login(SMTP_USER, SMTP_PASS)
server.sendmail(FROM_ADDR, to_addr, test_message.as_string())
print("Email sent successfully!")
This script is adaptable and flexible, within the limits of SMTP itself.
Prepare for growing pains #
SMTP is a fine place to start, but it will not scale readily as your application grows in size and complexity.
The “S” in SMTP stands for “simple,” but it could also stand for “self-serve.” Compared to using an API, direct SMTP implementations offer fewer features and less observability.
SMTP doesn’t offer automatic retry, message batching, or queueing, all of which can significantly improve your delivery rates. If delivery fails, it can’t help you discover the reason. You could build these features yourself, but it would require expertise across many aspects of application architecture and email functionality.
SMTP is a good technology to understand, and can be useful for simple applications. But for many developers, building a feature-rich application around SMTP is not worth the trouble.
Access flexibility and features with an API #
The second route is to integrate with a reputable email service like Postmark via its API. You still leverage their SMTP servers, but you also gain access to structured tools, logs, and reports that make your app more flexible and invaluable as it scales.
An email API transforms requests from your application into instructions for a network of connected SMTP servers and other related services.
Imagine taking a load of packages to a business center, where a professional at the counter determines the most appropriate and reliable service for your needs. An API is more than a mail carrier — it’s a service bundler that unlocks functionality SMTP alone can’t deliver.
SDKs speed up setup #
It’s certainly possible to call an API directly from your application. In a modern Python app, you’re likely to use the requests module. It’s a well-traveled path and takes only a little legwork to establish.
A code base filled with HTTP requests can get messy quickly, though, and it can be time-consuming to set up, especially if you intend to use it in multiple parts of your application.
SDKs speed up the setup process, help you discover features, and lower the risk of errors in your code. In Python, working with an SDK often helps keep your code base more “Pythonic” — cleaner, more concise, and more readable.
To use Postmark’s new Python SDK to send a single email, follow these steps:
1. Install the Python SDK
Postmark has SDKs available in many languages, some officially supported and others maintained by the Postmark community.
For a Python application, download the package here and set up your credentials.
Save your credentials and sender email address as environment variables.
2. Import modules
In the file where you intend to send an email, import the following modules, and load your environment variables:
import asyncio
import os
import postmark
try:
from dotenv import load_dotenv
load_dotenv()
except ImportError:
pass
SENDER = os.environ["POSTMARK_SENDER_EMAIL"]
3. Create an email function
APIs offer many more sending options than SMTP alone, like templates, bounce tracking, and bulk messaging. If you take this route, explore what’s available. This is the most basic email sending function:
async def main():
async with postmark.ServerClient(os.environ["POSTMARK_SERVER_TOKEN"]) as client:
response = await client.outbound.send(
postmark.Email(
sender=SENDER,
to="receiver@example.com",
subject="SMTP Test",
text_body="Hello world! This is a test of the Python API.",
metadata={"user_id": "12345"},
)
)
print(f"Sent (model): {response.message_id}")
asyncio.run(main())
So far, we’ve mostly done what we did with smtplib above. But using the API offers two key advantages.
First, the built-in Email class ensures your messages are properly formatted, increasing the likelihood of successful delivery. Second, an API gives you access to response codes.
4. Get confirmation
One of the most valuable reasons to use an API is observability — you get much more information about what happens after you send an email.
The sending method above is wrapped in an async function that returns a response from the API. If your email sends correctly, the response looks like this:
HTTP/1.1 200 OK
Content-Type: application/json
{
"To": "receiver@example.com",
"SubmittedAt": "2014-02-17T07:25:01.4178645-05:00",
"MessageID": "0a129aee-e1cd-480d-b08d-4f48548ff48d",
"ErrorCode": 0,
"Message": "OK"
}
Even with this very basic level of tracking, you already have more insight into what happened to your email than with SMTP.
Other fields in the Email class provide even more detail, like whether the recipient opened the email or clicked links — you can get this information by sending the MessageID to the Messages endpoint. The Stats endpoint aggregates details across all your sent messages so you can see trends.
Build scalable, adaptable email functionality #
Setting up an API is definitely more work than setting up SMTP directly, but the payoff for your time is much greater.
An API can give you access to features like bulk sending and scheduled sending, webhooks for monitoring behavior and errors, and automatic retry — none of which are available with SMTP alone. Features like these allow you to build a more robust email program that can develop alongside your application.
Re-evaluate as you grow #
Choosing the best way to implement email means keeping an eye on your needs as they evolve. Your application is likely to grow over time, so pay attention to your changing use cases and don’t stick to a method that no longer works for you.
If you’re not quite ready to invest in setting up an API, SMTP can be a fine place to start. But you can migrate email sending methods anytime, whether you’re thinking of switching from SMTP to an API or switching email service providers. It may be much less of a challenge than you expect, and the benefits may be significant.

