- by x32x01 ||
Sending a daily report by email is a common task - status emails, sales summaries, server health checks, or alert digests. Doing it manually is boring; automating it saves time and reduces human error. In this guide you’ll get a clear, beginner-friendly walkthrough to:
I’ll show working code you can copy, plus tips for security, error handling, and production use. Let’s go!
Why not just use a loop? (use scheduler or OS schedulers)
You’ll see examples that use the
Why? A Python loop can die if the process crashes or the machine reboots. OS-level schedulers are more reliable and easier to monitor.
Secure setup: credentials & best practices
Never hard-code passwords in the script. Use one of these:
Notes:
Scheduling: cron (Linux/macOS) and Task Scheduler (Windows)
If you rely on env vars, either export them in the script or source a file:
Docker & systemd options (resilient deployments)
Cloud schedulers (no server required)
If you don’t want to run a server 24/7, use cloud functions:
Monitoring, logging, and alerts
Quick checklist before you run in production
Automating daily email reports is a small automation with big payoff. With secure credentials, robust scheduling, and good logging, you’ll have a dependable report system that frees time and gives consistent updates.
- build a secure Python script that sends HTML/text emails and attachments,
- schedule it to run daily on Linux or Windows, and
- learn safer deployment options (Docker, systemd, cloud).
I’ll show working code you can copy, plus tips for security, error handling, and production use. Let’s go!
Why not just use a loop? (use scheduler or OS schedulers)
You’ll see examples that use the schedule Python library - that’s convenient for quick tests - but for production it’s better to use:- cron (Linux/macOS) or Task Scheduler (Windows) for robust scheduling, or
- systemd timers or Docker + cron, or
- cloud schedulers (AWS Lambda + CloudWatch, Google Cloud Functions + Scheduler).
Why? A Python loop can die if the process crashes or the machine reboots. OS-level schedulers are more reliable and easier to monitor.
Secure setup: credentials & best practices
Never hard-code passwords in the script. Use one of these:- Environment variables (
os.environ) - .env file with
python-dotenv(keep out of VCS) - Secrets manager (AWS Secrets Manager, Azure Key Vault, etc.)
- For Gmail, use App Passwords or OAuth2 - do not use raw account passwords.
Example: robust Python script (HTML + attachment + secure creds)
This script uses modern email APIs and reads credentials from environment variables. It also includes retry logic and logging. Python:
#!/usr/bin/env python3
# send_daily_report.py
import os
import smtplib
import ssl
import logging
from email.message import EmailMessage
from email.utils import formataddr
from datetime import datetime
from time import sleep
# --- config (read from environment) ---
SMTP_HOST = os.getenv("SMTP_HOST", "smtp.example.com")
SMTP_PORT = int(os.getenv("SMTP_PORT", "587")) # 587 for STARTTLS, 465 for SSL
SENDER_EMAIL = os.getenv("SENDER_EMAIL", "you@example.com")
SENDER_NAME = os.getenv("SENDER_NAME", "Report Bot")
SENDER_PASSWORD = os.getenv("SENDER_PASSWORD") # MUST be set securely
RECEIVER_EMAIL = os.getenv("RECEIVER_EMAIL", "recipient@example.com")
# --- logging ---
logging.basicConfig(level=logging.INFO,
format="%(asctime)s [%(levelname)s] %(message)s",
handlers=[logging.StreamHandler()])
# --- build message ---
def build_message(subject: str, plain_text: str, html_text: str, attachments: list = None) -> EmailMessage:
msg = EmailMessage()
msg["From"] = formataddr((SENDER_NAME, SENDER_EMAIL))
msg["To"] = RECEIVER_EMAIL
msg["Subject"] = subject
msg.set_content(plain_text)
if html_text:
msg.add_alternative(html_text, subtype="html")
# attachments: list of file paths
if attachments:
for path in attachments:
try:
with open(path, "rb") as f:
data = f.read()
maintype, subtype = ("application", "octet-stream")
filename = os.path.basename(path)
msg.add_attachment(data, maintype=maintype, subtype=subtype, filename=filename)
except Exception as e:
logging.warning("Failed to attach %s: %s", path, e)
return msg
# --- send with retry ---
def send_message(msg: EmailMessage, max_retries: int = 3, delay: int = 5):
context = ssl.create_default_context()
for attempt in range(1, max_retries + 1):
try:
logging.info("Connecting to SMTP %s:%s (attempt %d)", SMTP_HOST, SMTP_PORT, attempt)
if SMTP_PORT == 465:
with smtplib.SMTP_SSL(SMTP_HOST, SMTP_PORT, context=context) as server:
server.login(SENDER_EMAIL, SENDER_PASSWORD)
server.send_message(msg)
else:
with smtplib.SMTP(SMTP_HOST, SMTP_PORT) as server:
server.ehlo()
server.starttls(context=context)
server.ehlo()
server.login(SENDER_EMAIL, SENDER_PASSWORD)
server.send_message(msg)
logging.info("Email sent successfully.")
return True
except Exception as e:
logging.error("Send attempt %d failed: %s", attempt, e)
if attempt < max_retries:
logging.info("Retrying in %s seconds...", delay)
sleep(delay)
logging.critical("All send attempts failed.")
return False
# --- create report body (example) ---
def generate_report():
ts = datetime.utcnow().strftime("%Y-%m-%d %H:%M UTC")
# Example HTML body: replace with your real report generation (CSV, DB query, etc.)
html = f"""
<html>
<body>
<h2>Daily Report</h2>
<p>Timestamp: {ts}</p>
<p>Summary: Everything looks normal ✅</p>
<table border="1" cellpadding="4">
<tr><th>Metric</th><th>Value</th></tr>
<tr><td>Users</td><td>123</td></tr>
<tr><td>Errors</td><td>2</td></tr>
</table>
</body>
</html>
"""
plain = f"Daily Report\nTimestamp: {ts}\nSummary: Everything looks normal"
return plain, html
# --- main runner (single send) ---
def main():
if not SENDER_PASSWORD:
logging.critical("SENDER_PASSWORD not set in environment. Exiting.")
return 1
plain, html = generate_report()
subject = "Daily Report - " + datetime.utcnow().strftime("%Y-%m-%d")
msg = build_message(subject, plain, html, attachments=[])
ok = send_message(msg)
return 0 if ok else 2
if __name__ == "__main__":
raise SystemExit(main()) Notes:
- Generate your report content from DB queries, CSV, or monitoring APIs, then attach files or include HTML tables.
- Use environment variables for all secrets. In Linux you can set them in the crontab or systemd unit securely.
Scheduling: cron (Linux/macOS) and Task Scheduler (Windows)
Cron (Linux/macOS)
- Edit crontab:
crontab -e - Add a line to run at 08:00 daily:
Code:
0 8 * * * /usr/bin/python3 /opt/scripts/send_daily_report.py >> /var/log/send_daily_report.log 2>&1 If you rely on env vars, either export them in the script or source a file:
Code:
0 8 * * * . /opt/envs/report.env && /usr/bin/python3 /opt/scripts/send_daily_report.py >> /var/log/send_daily_report.log 2>&1 Windows Task Scheduler
- Create a basic task → Trigger: Daily → Action: Start a program
- Program:
C:\Python39\python.exe - Arguments:
C:\path\to\send_daily_report.py - Set “Run whether user is logged on or not” and supply credentials if needed.
Docker & systemd options (resilient deployments) 
- Wrap the script in a small Docker image and run cron inside the container or use the host cron to execute the docker run.
- Use a systemd timer for stricter failure/restart behavior (Linux). Systemd gives you logging with journalctl and restart policies.
Cloud schedulers (no server required)
If you don’t want to run a server 24/7, use cloud functions:- AWS Lambda + CloudWatch Events: Lambda can run Python, fetch your secrets (Secrets Manager), generate the report, and send via SES (Amazon Simple Email Service).
- GCP Cloud Functions + Cloud Scheduler: Use Gmail API or SendGrid for sending.
- Azure Functions + Timer Trigger: Use SendGrid or SMTP over TLS.
Monitoring, logging, and alerts
- Log success and failure to a file or central log (ELK, CloudWatch).
- Add a small alert: if sending fails X consecutive days, notify a Slack channel or SMS.
- Keep a rotation on logs (logrotate) so disk doesn’t fill up.
Quick checklist before you run in production
- Use app-specific password or OAuth when possible.
- Store credentials in env vars or secrets manager (no hard-coded secrets).
- Test manually before scheduling.
- Add logging and retry logic.
- Use OS scheduler (cron / Task Scheduler) for reliability.
- Monitor sends and failures.
Automating daily email reports is a small automation with big payoff. With secure credentials, robust scheduling, and good logging, you’ll have a dependable report system that frees time and gives consistent updates.
Last edited: