Automate Daily Email Reports with Python

x32x01
  • 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:
  • 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.
Also use STARTTLS or SSL to encrypt the connection. Use logging for errors so you can troubleshoot if sending fails.



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)​

  1. Edit crontab: crontab -e
  2. 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.
Cloud options are cost-effective and scale well.

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:
Related Threads
x32x01
Replies
0
Views
203
x32x01
x32x01
x32x01
Replies
0
Views
783
x32x01
x32x01
x32x01
Replies
0
Views
928
x32x01
x32x01
x32x01
Replies
0
Views
777
x32x01
x32x01
x32x01
Replies
0
Views
880
x32x01
x32x01
x32x01
Replies
0
Views
856
x32x01
x32x01
x32x01
Replies
0
Views
861
x32x01
x32x01
x32x01
Replies
0
Views
1K
x32x01
x32x01
x32x01
Replies
0
Views
1K
x32x01
x32x01
x32x01
Replies
0
Views
1K
x32x01
x32x01
Register & Login Faster
Forgot your password?
Forum Statistics
Threads
628
Messages
632
Members
64
Latest Member
alialguelmi
Back
Top