With 58% of adults checking email first thing in the morning before social media or web browsing, email remains the fastest way to catch time-sensitive alerts. So, let’s go over sending email notifications in Python. This way, you’ll immediately get the alert on your phone and any other devices that you check your email with.
This article is going to cover how to send emails to either one or multiple recipients. We’ll also implement a check to make sure it doesn’t bomb you with a ton of emails simultaneously.
This same Python email notification setup works for a lot more than PS5 stock checks. You can use it for price monitors, uptime checks, web scraping alerts, security monitoring, or cron job notifications that need to ping you when something changes. The rest of this tutorial shows a simple, reusable pattern you can adapt to any automated alert.
What You’ll Need
Table of Contents
ToggleThere are two things you’ll need: an email account for the bot to use, and two more imports at the start of the code.
Email Address for the Bot
Google no longer supports Less Secure App Access for signing in with just a username + password. Instead, for a personal Gmail account, the simplest reliable approach is to:
- Turn ON 2-Step Verification on the bot’s Google account
- Create an App Password (Google requires 2-Step Verification to create one)
- Use that App Password in your Python script instead of the normal Gmail password
This keeps your real password out of the code, and it matches Google’s recommended path for apps that can’t do Sign in with Google or OAuth.
If you don’t see App passwords, it may be unavailable for your account/security setup. In that case, use an email provider/API that supports OAuth or an email-sending service (SendGrid/SES/Mailgun/Postmark).
| Error / symptom (copy-paste) | What it usually means | Exact fix |
|---|---|---|
| 535-5.7.8 Username and Password not accepted | Wrong credentials or Gmail rejected password auth | Use an App Password (not your normal Gmail password). Confirm you’re logging in with the full Gmail address as the username. |
| App password required / 534-5.7.9 Application-specific password required | Gmail requires an app-specific password for SMTP | Create an App Password in the Google account security settings (requires 2-Step Verification), then use that 16-digit app password in your script. |
| smtplib.SMTPAuthenticationError (Python exception) | Python is surfacing the underlying Gmail auth error | Check the embedded SMTP error code/message, then apply the matching fix above (most often: switch to an App Password). |
| Daily sending limit reached / “You have reached a limit for sending mail” | Gmail rate-limited or blocked sending temporarily | Reduce volume, slow down sends, and wait for the sending window to reset (often within 1–24 hours). If you need reliable volume, use an email API provider instead of Gmail SMTP. |
Import Commands
There are two more things that you’ll need to import,, in addition to Requests and BeautifulSoup from the scraper portion. They are time and smtplib. They’re both modules that are already built into Python, so there are no additional installations required before using them.
Time should be pretty self-explanatory.
SMTPlib is Python’s module for handling emails, which is important for what we’re about to do.
This means that for everything covered in the previous DIY PS5 Availability Tracker article, plus what we’re about to go over, you’re going to be looking at:
from bs4 import BeautifulSoup
import time, smtplib, requests
from email.message import EmailMessage
Now, let’s get started!
Sending Email Notifications in Python
You have a few options regarding connection security. You could use SMTP_SSL() through port 465 for your connection. However, let’s use SSL’s more secure successor, TLS, via .starttls() through the modern standardized port 587.
Port 587 should be your default choice. It was designed for email client submission and supports StartTLS. Most email service providers, including Gmail, Outlook, and Twilio SendGrid, recommend port 587 as the primary choice for message submission.
Source: Twilio, Major Email Service Provider
The variables could be hardcoded into the main function of the bot’s code if you’re only sending notifications to yourself. However, let’s aim for the goal of easier scalability and being able to adjust for other purposes.
If the variables are made inside a function, their values don’t carry over between loops. Therefore, we should declare them beforehand and adjust the values as needed.
Declare Variables
The server and port information are filled in under the assumption that you’ll use a Gmail account as recommended. Other service providers have different identifiers, such as AT&T’s SMTP server address being Outbound.att.net.
sender_name = "[email protected]"
sender_password = "sender_app_password"
smtp_port = 587
smtp_server = "smtp.gmail.com"
We’ll set two receiving accounts that’ll be called in a for loop to demonstrate the skeleton needed for upward scalability. If you’re unfamiliar, loops will step through a list or range in sequence, executing the code once per item.
Most email clients will auto-link plain URLs, but if you want guaranteed clickable links and nicer formatting, build the email using Python’s email package and include an HTML alternative part (multipart/alternative).
email_recipients = ["[email protected]","[email protected]"]
email_subject = "PS5 in stock on Amazon!"
email_body_text = "PS5 in stock on Amazon: https://www.amazon.com/PlayStation-5-Console/dp/B09DFCB66S/"
email_body_html = """
<p><strong>PS5 in stock on Amazon!</strong></p>
<p><a href="https://www.amazon.com/PlayStation-5-Console/dp/B09DFCB66S/">Open the listing</a></p>
"""
Finally, we also need to declare the variables to track an internal timer to prevent email spam.
The bot could theoretically detect that the PS5 is available a few hundred times within a very short timeframe. At least, it might be easier after you have the scraper fleshed out to use rotating proxies to repeatedly check availability. The bot would then end up aggressively flooding your mailbox if you don’t have any failsafes in place.
Monitor responsibly:
- Don’t hammer retailers with rapid-fire requests.
- Respect each site’s Terms of Service and robots/policies.
- Use cooldowns, backoff, and retry delays to reduce load.
- If using proxies, rotate responsibly to avoid bursty spikes that look abusive.
Let’s use the time.time() command to get the current time. The output is a floating point value equivalent to the number of seconds since a fixed starting point.
We’ll save a timestamp whenever an email is successfully sent out. We can then determine how much time has passed by comparing the saved value to the current time. It’s then a simple formula of current time – last time > cooldown to ensure enough time has passed between emails.
If you’re thinking of the cooldown in minutes, remember to multiply by 60 to convert it into seconds. In this instance, we’ll set a 1-minute cooldown between emails.
last_email_sent = 0
email_cooldown = 60
Send Email Function
The function we’re using for sending email notifications in Python will operate under the assumption that you are only calling it if the desired conditions are met. In this example, that means that Amazon has PS5s in stock.
First up, we’ll define the send_email function. It will start with a check to see if any emails were sent out recently. It will get to work as long as it has been longer than the internal cooldown since the last one. The default value of 0 will ensure that there aren’t any issues when an email hasn’t been sent yet.
def send_email():
global last_email_sent # (or handle state differently; this matches your current pattern)
if time.time() - last_email_sent <= email_cooldown:
return
with smtplib.SMTP(smtp_server, smtp_port) as server:
server.starttls()
server.login(sender_name, sender_password)
for recipient in email_recipients:
msg = EmailMessage()
msg["From"] = sender_name
msg["To"] = recipient
msg["Subject"] = email_subject
# Plain text part (fallback)
msg.set_content(email_body_text)
# HTML part (clickable link + formatting)
msg.add_alternative(email_body_html, subtype="html")
server.send_message(msg)
last_email_sent = time.time()
print("Email sent successfully!")
Once the bot is all done sending out emails, remember to log out.
server.quit()
Also read: High-Scale Bot Automation: Succeed in Competitive Markets
SMTP vs Email API
SMTP is great for small, personal alerts. But if you need better deliverability, higher sending volume, logs, retries, and fewer mystery blocks, an email API is usually the upgrade path, especially once you start hitting sending limits.
| Scenario | Use SMTP when… | Use an Email API when… |
|---|---|---|
| Personal alerts (PS5 stock, uptime pings) | You’re emailing yourself or a small list and volume is low | You want reliable delivery + logs even for low volume |
| Transactional emails (password resets, receipts) | You’re prototyping and don’t care about audit logs yet | You need retries, dashboards, suppression lists, and consistent inboxing |
| Scaling alerts to many recipients | You’re sending only occasionally and staying far from provider limits | You need predictable throughput and don’t want account throttling/blocks |
| Deliverability matters (avoid spam) | You’re ok with “good enough” and low volume | You need domain authentication + reputation tooling + better placement controls |
| Observability | You’ll accept minimal visibility | You want event webhooks, analytics, and traceability per message |
Deliverability Basics
If you send from your own domain (example: [email protected]), deliverability isn’t luck, it’s paperwork for robots:
- SPF tells receivers which servers are allowed to send mail for your domain.
- DKIM cryptographically signs the message to prove it wasn’t tampered with.
- DMARC tells receivers what to do if SPF/DKIM fail and enforces “alignment” with your From domain.
Google’s Gmail sender guideline explicitly call out SPF/DKIM for all senders and SPF+DKIM+DMARC for higher-volume senders.
If you send 5,000+ messages/day to Gmail accounts, Google also requires DMARC and other bulk-sender rules.
Debugging Locally
Before you point your code at Gmail, run a local catch-all SMTP server so you can test content, subjects, and loops without spamming real inboxes.
Option A: aiosmtpd (recommended for modern Python)
pip install aiosmtpd
aiosmtpd -n -l localhost:8025
Then temporarily set:
smtp_server = "localhost"
smtp_port = 8025
Option B: MailHog (nice web UI, great for teams)
MailHog runs a fake SMTP server and shows captured emails in a web inbox.
docker run -p 1025:1025 -p 8025:8025 mailhog/mailhog
Then use:
smtp_server = "localhost"
smtp_port = 1025
Open the MailHog inbox UI here:
http://localhost:8025
Conclusion
What we’ve covered so far is enough for a basic notification scraper that you could schedule to run periodically. Next time, we’ll work on upgrading the tracker into a Discord bot for availability alerts. That way, it can announce notifications in chat and passively loop the availability checks.
As you can see, sending email notifications in Python isn’t much of a challenge with a little know-how. But, to ensure you catch drops immediately, your tracker needs to be actively on the lookout.
FAQs About Email Notifications in Python
Q1. Why do I need an App Password for Gmail in Python?
Google no longer supports basic password authentication for SMTP. You must enable 2-Step Verification on your Google account, then create an App Password from security settings. Use this 16-character password in your Python script instead of your regular Gmail password for secure authentication.
Q2. What’s the difference between SMTP and Email API for Python?
SMTP works for personal, low-volume alerts but offers minimal visibility and deliverability control. Email APIs (SendGrid, Mailgun, SES) provide better deliverability, sending volume, retry logic, analytics, suppression lists, and prevent account throttling. Use SMTP for personal projects; switch to APIs for scaling or transactional emails.
Q3. How do I prevent my Python bot from spamming emails?
Implement a cooldown timer using time.time() to track the last email sent. Compare current time minus last sent time against your cooldown period (e.g., 60 seconds). Only send emails if enough time has passed: if time.time() – last_email_sent > email_cooldown.
Q4. What port should I use for Python SMTP email?
Use port 587 with TLS encryption via starttls() for modern, secure SMTP connections. Port 465 uses SSL but is outdated. Port 587 is the standardized submission port and works with Gmail (smtp.gmail.com:587) and most email providers.
Q5. What are SPF, DKIM, and DMARC for email deliverability?
SPF authorizes which servers can send mail for your domain. DKIM cryptographically signs messages to prevent tampering. DMARC tells receivers what to do when SPF/DKIM fail and enforces alignment. Gmail requires these for bulk senders (5,000+ daily messages) to avoid spam folders.
How useful was this post?
Click on a star to rate it!
Average rating 5 / 5. Vote count: 1
No votes so far! Be the first to rate this post.
Tell Us More!
Let us improve this post!
Tell us how we can improve this post?

