Auto Email Scheduler

 import smtplib

import schedule

import json

import os

import time

import threading

from email.mime.text import MIMEText

from email.mime.multipart import MIMEMultipart

from email.mime.base import MIMEBase

from email import encoders

from datetime import datetime, timedelta

from pathlib import Path

 

# ============================================================

# CONFIGURATION FILE

# ============================================================

 

CONFIG_FILE   = "email_config.json"

SCHEDULE_FILE = "email_schedule.json"

 

# ============================================================

# SAVE & LOAD CONFIG (SMTP credentials)

# ============================================================

 

def save_config(config):

    with open(CONFIG_FILE, "w") as f:

        json.dump(config, f, indent=2)

    print("   Configuration saved.")

 

 

def load_config():

    if Path(CONFIG_FILE).exists():

        with open(CONFIG_FILE, "r") as f:

            return json.load(f)

    return None

 

 

# ============================================================

# SAVE & LOAD SCHEDULED EMAILS

# ============================================================

 

def save_schedule(emails):

    with open(SCHEDULE_FILE, "w") as f:

        json.dump(emails, f, indent=2)

 

 

def load_schedule():

    if Path(SCHEDULE_FILE).exists():

        try:

            with open(SCHEDULE_FILE, "r") as f:

                return json.load(f)

        except:

            return []

    return []

 

 

# ============================================================

# SETUP SMTP CREDENTIALS

# ============================================================

 

def setup_credentials():

    print("\n" + "="*55)

    print("   SMTP CREDENTIALS SETUP")

    print("="*55)

    print("\n  Supported providers:")

    print("  1. Gmail       (smtp.gmail.com : 587)")

    print("  2. Outlook     (smtp.office365.com : 587)")

    print("  3. Yahoo       (smtp.mail.yahoo.com : 587)")

    print("  4. Custom SMTP")

 

    choice = input("\n  Choose provider (1-4): ").strip()

 

    providers = {

        "1": ("smtp.gmail.com",        587),

        "2": ("smtp.office365.com",    587),

        "3": ("smtp.mail.yahoo.com",   587),

    }

 

    if choice in providers:

        smtp_host, smtp_port = providers[choice]

    else:

        smtp_host = input("  SMTP Host: ").strip()

        smtp_port = int(input("  SMTP Port: ").strip())

 

    email    = input("\n  Your email address: ").strip()

    password = input("  App password (NOT your login password): ").strip()

 

    config = {

        "smtp_host": smtp_host,

        "smtp_port": smtp_port,

        "email":     email,

        "password":  password

    }

 

    # Test connection

    print("\n   Testing connection...")

    try:

        server = smtplib.SMTP(smtp_host, smtp_port)

        server.starttls()

        server.login(email, password)

        server.quit()

        print("   Connection successful!")

        save_config(config)

    except Exception as e:

        print(f"   Connection failed: {e}")

        print("   Tip: Use an App Password, not your account password.")

        print("     Gmail: myaccount.google.com → Security → App Passwords")

 

    return config

 

 

# ============================================================

# SEND EMAIL

# ============================================================

 

def send_email(config, to, subject, body, attachment_path=None, is_html=False):

    try:

        msg = MIMEMultipart()

        msg["From"]    = config["email"]

        msg["To"]      = to

        msg["Subject"] = subject

 

        # Body

        mime_type = "html" if is_html else "plain"

        msg.attach(MIMEText(body, mime_type))

 

        # Attachment

        if attachment_path and Path(attachment_path).exists():

            with open(attachment_path, "rb") as f:

                part = MIMEBase("application", "octet-stream")

                part.set_payload(f.read())

            encoders.encode_base64(part)

            part.add_header(

                "Content-Disposition",

                f"attachment; filename={Path(attachment_path).name}"

            )

            msg.attach(part)

 

        server = smtplib.SMTP(config["smtp_host"], config["smtp_port"])

        server.starttls()

        server.login(config["email"], config["password"])

        server.sendmail(config["email"], to, msg.as_string())

        server.quit()

 

        print(f"\n   Email sent to {to} at {datetime.now().strftime('%d-%m-%Y %H:%M:%S')}")

        return True

 

    except Exception as e:

        print(f"\n   Failed to send email to {to}: {e}")

        return False

 

 

# ============================================================

# COMPOSE EMAIL

# ============================================================

 

def compose_email():

    print("\n" + "="*55)

    print("    COMPOSE EMAIL")

    print("="*55)

 

    to         = input("\n  To (email address): ").strip()

    subject    = input("  Subject: ").strip()

 

    print("  Body (type END on a new line to finish):")

    lines = []

    while True:

        line = input()

        if line.strip().upper() == "END":

            break

        lines.append(line)

    body = "\n".join(lines)

 

    attachment = input("\n  Attachment path (or press Enter to skip): ").strip()

    attachment = attachment if attachment and Path(attachment).exists() else None

 

    if attachment is None and input("  Was that a path? File not found - skip attachment? (y/n): ").strip().lower() != "n":

        attachment = None

 

    return {

        "to":         to,

        "subject":    subject,

        "body":       body,

        "attachment": attachment,

        "is_html":    False

    }

 

 

# ============================================================

# SCHEDULE EMAIL

# ============================================================

 

def schedule_email(email_data):

    print("\n" + "="*55)

    print("   SCHEDULE EMAIL")

    print("="*55)

    print("\n  Schedule type:")

    print("  1. One-time  (specific date & time)")

    print("  2. Daily     (every day at a time)")

    print("  3. Weekly    (specific day each week)")

    print("  4. Send NOW")

 

    stype = input("\n  Choice (1-4): ").strip()

 

    if stype == "1":

        dt_str = input("  Send at (DD-MM-YYYY HH:MM): ").strip()

        try:

            send_at = datetime.strptime(dt_str, "%d-%m-%Y %H:%M")

            email_data["schedule_type"] = "once"

            email_data["send_at"]       = send_at.strftime("%d-%m-%Y %H:%M")

            email_data["sent"]          = False

        except ValueError:

            print("   Invalid date format.")

            return None

 

    elif stype == "2":

        time_str = input("  Daily at (HH:MM): ").strip()

        email_data["schedule_type"] = "daily"

        email_data["send_at"]       = time_str

        email_data["sent"]          = False

 

    elif stype == "3":

        days = ["monday","tuesday","wednesday","thursday","friday","saturday","sunday"]

        print("  Days:", ", ".join(d.capitalize() for d in days))

        day      = input("  Day: ").strip().lower()

        time_str = input("  Time (HH:MM): ").strip()

        if day not in days:

            print("   Invalid day.")

            return None

        email_data["schedule_type"] = "weekly"

        email_data["send_day"]      = day

        email_data["send_at"]       = time_str

        email_data["sent"]          = False

 

    elif stype == "4":

        email_data["schedule_type"] = "now"

        email_data["sent"]          = False

 

    else:

        print("   Invalid choice.")

        return None

 

    email_data["id"]      = datetime.now().strftime("%Y%m%d%H%M%S")

    email_data["created"] = datetime.now().strftime("%d-%m-%Y %H:%M:%S")

    return email_data

 

 

# ============================================================

# VIEW SCHEDULED EMAILS

# ============================================================

 

def view_scheduled(emails):

    pending = [e for e in emails if not e.get("sent")]

    sent    = [e for e in emails if e.get("sent")]

 

    print("\n" + "="*55)

    print(f"   SCHEDULED EMAILS  ({len(pending)} pending, {len(sent)} sent)")

    print("="*55)

 

    if not emails:

        print("\n   No emails scheduled yet.")

        return

 

    if pending:

        print("\n   PENDING:")

        for i, e in enumerate(pending, 1):

            print(f"\n  [{i}] To: {e['to']}")

            print(f"      Subject: {e['subject']}")

            stype = e.get("schedule_type", "")

            if stype == "once":

                print(f"      Send at: {e['send_at']} (one-time)")

            elif stype == "daily":

                print(f"      Daily at: {e['send_at']}")

            elif stype == "weekly":

                print(f"      Weekly: {e['send_day'].capitalize()} at {e['send_at']}")

            print(f"      Created: {e.get('created','')}")

 

    if sent:

        print("\n   SENT:")

        for e in sent[-5:]:  # show last 5 sent

            print(f"  • [{e['to']}] \"{e['subject']}\" — sent {e.get('sent_at','')}")

 

 

# ============================================================

# DELETE SCHEDULED EMAIL

# ============================================================

 

def delete_scheduled(emails):

    pending = [e for e in emails if not e.get("sent")]

    if not pending:

        print("\n  📭 No pending emails to delete.")

        return emails

 

    view_scheduled(emails)

    try:

        idx = int(input("\n  Enter number to delete: ").strip()) - 1

        if 0 <= idx < len(pending):

            removed = pending[idx]

            emails  = [e for e in emails if e["id"] != removed["id"]]

            save_schedule(emails)

            print(f"  🗑  Deleted email to {removed['to']}")

        else:

            print("   Invalid number.")

    except ValueError:

        print("   Invalid input.")

    return emails

 

 

# ============================================================

# SCHEDULER RUNNER (Background Thread)

# ============================================================

 

def run_scheduler(config, emails_ref):

    print("   Scheduler running in background...")

 

    while True:

        now = datetime.now()

        now_time = now.strftime("%H:%M")

        now_day  = now.strftime("%A").lower()

 

        for email in emails_ref:

            if email.get("sent"):

                continue

 

            stype = email.get("schedule_type")

 

            should_send = False

 

            if stype == "now":

                should_send = True

 

            elif stype == "once":

                try:

                    send_dt = datetime.strptime(email["send_at"], "%d-%m-%Y %H:%M")

                    if now >= send_dt and not email.get("sent"):

                        should_send = True

                except:

                    pass

 

            elif stype == "daily":

                if now_time == email.get("send_at"):

                    should_send = True

 

            elif stype == "weekly":

                if now_day == email.get("send_day") and now_time == email.get("send_at"):

                    should_send = True

 

            if should_send:

                success = send_email(

                    config,

                    email["to"],

                    email["subject"],

                    email["body"],

                    email.get("attachment"),

                    email.get("is_html", False)

                )

                if success:

                    email["sent"]    = True

                    email["sent_at"] = now.strftime("%d-%m-%Y %H:%M:%S")

                    save_schedule(emails_ref)

 

        time.sleep(30)  # Check every 30 seconds

 

 

# ============================================================

# MAIN MENU

# ============================================================

 

def print_menu():

    print("\n" + "-"*45)

    print("   AUTO EMAIL SCHEDULER")

    print("-"*45)

    print("  1. Setup / Update SMTP credentials")

    print("  2. Compose & Schedule new email")

    print("  3. View scheduled emails")

    print("  4. Delete a scheduled email")

    print("  5. Start scheduler (monitor & send)")

    print("  0. Exit")

    print("-"*45)

 

 

def main():

    print("\n" + "="*55)

    print("      AUTO EMAIL SCHEDULER")

    print("="*55)

 

    config = load_config()

    emails = load_schedule()

 

    if config:

        print(f"\n   Loaded credentials for: {config['email']}")

    else:

        print("\n  ⚠  No credentials found. Please set up SMTP first (Option 1).")

 

    scheduler_thread = None

 

    while True:

        print_menu()

        choice = input("  > ").strip()

 

        if choice == "1":

            config = setup_credentials()

 

        elif choice == "2":

            if not config:

                print("\n   Please set up SMTP credentials first (Option 1).")

                continue

            email_data = compose_email()

            if email_data:

                scheduled = schedule_email(email_data)

                if scheduled:

                    emails.append(scheduled)

                    save_schedule(emails)

                    print(f"\n   Email scheduled successfully! (ID: {scheduled['id']})")

 

        elif choice == "3":

            view_scheduled(emails)

 

        elif choice == "4":

            emails = delete_scheduled(emails)

 

        elif choice == "5":

            if not config:

                print("\n   Please set up SMTP credentials first.")

                continue

            if scheduler_thread and scheduler_thread.is_alive():

                print("\n   Scheduler is already running.")

            else:

                scheduler_thread = threading.Thread(

                    target=run_scheduler,

                    args=(config, emails),

                    daemon=True

                )

                scheduler_thread.start()

                print("\n   Scheduler started! It checks every 30 seconds.")

                print("  Keep this window open. Press 0 to exit.\n")

 

        elif choice == "0":

            print("\n   Goodbye! Scheduled emails will not send after exit.\n")

            break

 

        else:

            print("   Invalid choice.")

 

 

# ============================================================

# RUN

# ============================================================

 

if __name__ == "__main__":

    main()

No comments: