Why Automate Follow-Ups and Reports?
Every small business owner spends hours every week on two mind-numbing tasks:
- Chasing customers — following up on quotes, appointments, or abandoned carts.
- 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
- Go to your Google Account → Security → 2-Step Verification and turn it on.
- Then go to App Passwords (search for it).
- Generate a password for Mail on Other (custom name) — name it
python-followup. - Copy the 16-character password and paste it into
SENDER_PASSWORDin 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_contactis 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.