Why Automate Follow-Ups and Reports?

Every small business owner spends hours every week on two mind-numbing tasks:

  1. Chasing customers — following up on quotes, appointments, or abandoned carts.
  2. Compiling reports — pulling sales data into spreadsheets to see how the week went.

With about 50 lines of Python you can hand both jobs to a script that runs while you sleep. No paid tools. No monthly subscription. Just Python, a Gmail account, and a CSV file.

What We Are Building

A single Python script that:

  • Reads a list of customers from a CSV file.
  • Sends a personalized follow-up email to each customer via Gmail SMTP.
  • Generates a weekly sales summary report (plain text, ready to paste into a spreadsheet or messaging app).
  • Logs every action so you can audit what happened.

Step 1: Prepare the Customer CSV

Create a file called customers.csv in the same folder as your script. Keep it simple:

name,email,last_contact,type,notes
Toko Makmur,toko.makmur@email.com,2026-09-28,quote_sent,Quoting 12 dozen eggs
Warung Sejahtera,warung@email.com,2026-09-30,appointment,Consultation done
Bu Ayu Catering,buayu@email.com,2026-09-25,cart_abandon,Left 3 items unpaid
Pak Budi Ternak,pakbudi@email.com,2026-09-20,followup,Pending order review

Every row represents a customer you want to reach. The type column decides what kind of follow-up message they get.

Step 2: The Python Script

Save this as followup_reporter.py:

import smtplib
import csv
from email.mime.text import MIMEText
from datetime import datetime, timedelta
import logging

# ------------------------------------------------
# CONFIGURATION — fill in your Gmail credentials
# ------------------------------------------------
SMTP_SERVER = "smtp.gmail.com"
SMTP_PORT = 587
SENDER_EMAIL = "you@gmail.com"         # <-- CHANGE THIS
SENDER_PASSWORD = "your-app-password"  # <-- USE GMAIL APP PASSWORD

CSV_FILE = "customers.csv"
LOG_FILE = "automation_log.txt"

# ---- Message templates ----
TEMPLATES = {
    "quote_sent": (
        "Hi {name},\n\n"
        "I just wanted to check in on the quote we sent you on {last_contact}. "
        "Happy to adjust the pricing or quantities if needed.\n\n"
        "Let me know — no pressure.\n\nBest,\nYour Business"
    ),
    "appointment": (
        "Hi {name},\n\n"
        "Thanks again for the consultation on {last_contact}. "
        "If you have any follow-up questions just reply to this email.\n\n"
        "Looking forward to working with you!\n\nBest,\nYour Business"
    ),
    "cart_abandon": (
        "Hi {name},\n\n"
        "I noticed you left some items in your cart. "
        "I'd be happy to reserve them for you — just let me know.\n\n"
        "Thanks,\nYour Business"
    ),
    "followup": (
        "Hi {name},\n\n"
        "Quick check-in regarding your pending order. "
        "Need any help finalizing it?\n\n"
        "Cheers,\nYour Business"
    ),
}

# ---- Sales data (replace with your real numbers) ----
WEEKLY_SALES = {
    "items_sold": 142,
    "revenue_idr": 4875000,
    "new_customers": 8,
    "top_product": "Free-range eggs (12-pack)",
}

# ------------------------------------------------
# SETUP LOGGING
# ------------------------------------------------
logging.basicConfig(
    filename=LOG_FILE,
    level=logging.INFO,
    format="%(asctime)s — %(levelname)s — %(message)s",
    datefmt="%Y-%m-%d %H:%M:%S",
)
logger = logging.getLogger("followup_reporter")


def send_email(to_email: str, subject: str, body: str) -> bool:
    """Send plain-text email via Gmail SMTP."""
    msg = MIMEText(body, "plain")
    msg["Subject"] = subject
    msg["From"] = SENDER_EMAIL
    msg["To"] = to_email

    try:
        with smtplib.SMTP(SMTP_SERVER, SMTP_PORT) as server:
            server.starttls()
            server.login(SENDER_EMAIL, SENDER_PASSWORD)
            server.send_message(msg)
        logger.info(f"Email sent to {to_email} — subject: {subject}")
        return True
    except Exception as e:
        logger.error(f"Failed to send email to {to_email}: {e}")
        return False


def process_customers():
    """Read CSV and send follow-up emails where last_contact is >7 days ago."""
    today = datetime.now()
    seven_days_ago = today - timedelta(days=7)

    sent = 0
    skipped = 0

    try:
        with open(CSV_FILE, "r", newline="", encoding="utf-8") as f:
            reader = csv.DictReader(f)
            for row in reader:
                name = row["name"].strip()
                email = row["email"].strip()
                ctype = row.get("type", "followup").strip()
                last_contact_str = row.get("last_contact", "").strip()

                if not email:
                    skipped += 1
                    continue

                # Skip customers contacted within the last 7 days
                if last_contact_str:
                    try:
                        last_contact_date = datetime.strptime(last_contact_str, "%Y-%m-%d")
                        if last_contact_date > seven_days_ago:
                            logger.info(f"Skipped {name} — contacted recently ({last_contact_str})")
                            skipped += 1
                            continue
                    except ValueError:
                        logger.warning(f"Could not parse date for {name}: {last_contact_str}")

                template = TEMPLATES.get(ctype, TEMPLATES["followup"])
                body = template.format(name=name, last_contact=last_contact_str)
                subject = f"Checking in, {name}"

                if send_email(email, subject, body):
                    sent += 1
                else:
                    skipped += 1

    except FileNotFoundError:
        logger.error(f"CSV file {CSV_FILE} not found.")
        print(f"ERROR: {CSV_FILE} not found — create it first.")
        return

    return sent, skipped


def generate_weekly_report():
    """Print and log a weekly sales summary."""
    report = (
        f"📊 WEEKLY SALES REPORT — {datetime.now().strftime('%b %d, %Y')}\n"
        f"{'─' * 45}\n"
        f"Items sold         : {WEEKLY_SALES['items_sold']}\n"
        f"Revenue (IDR)      : Rp {WEEKLY_SALES['revenue_idr']:,}\n"
        f"New customers      : {WEEKLY_SALES['new_customers']}\n"
        f"Top product         : {WEEKLY_SALES['top_product']}\n"
        f"{'─' * 45}\n"
    )
    print(report)
    logger.info(f"Weekly report generated:\n{report}")
    return report


def main():
    print("=== Customer Follow-Up + Weekly Report Automation ===\n")

    # Step 1 — follow-ups
    print("📬 Sending follow-up emails...")
    result = process_customers()
    if result:
        sent, skipped = result
        print(f"   ✅ Sent: {sent}   ⏭️  Skipped: {skipped}\n")

    # Step 2 — weekly report
    print("📈 Generating weekly report...")
    report = generate_weekly_report()

    print("✅ Done. Check automation_log.txt for full details.")


if __name__ == "__main__":
    main()

Step 3: Get a Gmail App Password

  1. Go to your Google Account → Security → 2-Step Verification and turn it on.
  2. Then go to App Passwords (search for it).
  3. Generate a password for Mail on Other (custom name) — name it python-followup.
  4. Copy the 16-character password and paste it into SENDER_PASSWORD in the script.

⚠️ Never use your regular Gmail password in a script. Always use an App Password.

Step 4: Run the Script

python3 followup_reporter.py

On the first run it will:

  • Send emails to customers whose last_contact is older than 7 days.
  • Skip customers contacted more recently.
  • Print a weekly sales report.
  • Log everything to automation_log.txt.

Scheduling with Cron (Linux / macOS)

To run this automatically every Monday morning:

crontab -e
# Add this line:
0 8 * * 1 cd /path/to/your/folder && python3 followup_reporter.py

That runs the script at 8:00 AM every Monday. No further action needed.

Adapting the Template for Your Business

Change the WEEKLY_SALES dictionary to match your actual numbers — or better yet, make the script read them from a CSV. Add new type values in the CSV to get different message templates. For example, add a birthday type with a discount offer.

What You Gain

  • Time saved: ~2 hours per week on follow-ups and report writing.
  • Consistency: Customers get contacted on schedule, not when you remember.
  • No vendor lock-in: Works offline. No SaaS monthly fee. Plain Python.

Once the script is stable, you can extend it to send the weekly report as an email to yourself each Monday, or log it to Google Sheets via the Google Sheets API.

Start with the CSV. Add a few customers. Run the script once manually. Then let cron take over. That is 80 % of the automation benefit with 20 % of the effort.