How I Cut My Client Onboarding Time from 2 Hours to 10 Minutes with Python
Client onboarding used to take me forever. Creating contracts, setting up project folders, sending welcome emails, adding to my CRM — 2+ hours of repetitive work for every new client.
Then I automated it. Now it takes 10 minutes.
Here's exactly what I built, and how you can copy it.
The Old Way (Before Automation)
Every new client meant:
- Manually creating a contract in Google Docs
- Filling in their name, company, project details, rates
- Creating a project folder structure (manually!)
- Sending a welcome email with next steps
- Adding to spreadsheet CRM
- Setting up invoicing
- Creating a Slack channel
Time per client: 2-3 hours. Felt like work, not business.
The Python Solution
import os
import json
import smtplib
from datetime import datetime, timedelta
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from pathlib import Path
import shutil
class ClientOnboarding:
def __init__(self, config_path="config.json"):
with open(config_path) as f:
self.config = json.load(f)
self.templates_dir = Path(self.config["templates_dir"])
self.clients_dir = Path(self.config["clients_dir"])
def onboard_client(self, client_info: dict):
"""Full onboarding: folders + contract + email + CRM"""
print(f"Starting onboarding for {client_info['name']}...")
# 1. Create folder structure
client_dir = self._create_folder_structure(client_info)
print(f"✅ Folders created: {client_dir}")
# 2. Generate contract
contract_path = self._generate_contract(client_info, client_dir)
print(f"✅ Contract generated: {contract_path}")
# 3. Send welcome email
self._send_welcome_email(client_info, contract_path)
print(f"✅ Welcome email sent to {client_info['email']}")
# 4. Update CRM
self._update_crm(client_info, client_dir)
print(f"✅ CRM updated")
return {
"status": "success",
"client_dir": str(client_dir),
"contract": str(contract_path)
}
def _create_folder_structure(self, client_info: dict) -> Path:
"""Create standardized project folder structure"""
client_name = client_info['name'].replace(' ', '_').lower()
date_str = datetime.now().strftime("%Y%m")
client_dir = self.clients_dir / f"{date_str}_{client_name}"
# Standard subfolder structure
subfolders = [
"01_contracts",
"02_briefs",
"03_deliverables",
"04_invoices",
"05_correspondence",
"06_assets"
]
for folder in subfolders:
(client_dir / folder).mkdir(parents=True, exist_ok=True)
return client_dir
def _generate_contract(self, client_info: dict, client_dir: Path) -> Path:
"""Fill contract template with client details"""
template_path = self.templates_dir / "contract_template.txt"
with open(template_path) as f:
template = f.read()
# Replace placeholders
replacements = {
"{{CLIENT_NAME}}": client_info["name"],
"{{CLIENT_COMPANY}}": client_info.get("company", ""),
"{{CLIENT_EMAIL}}": client_info["email"],
"{{PROJECT_NAME}}": client_info["project_name"],
"{{RATE}}": str(client_info["rate"]),
"{{START_DATE}}": client_info.get("start_date", datetime.now().strftime("%Y-%m-%d")),
"{{END_DATE}}": client_info.get("end_date", "TBD"),
"{{MY_NAME}}": self.config["my_name"],
"{{MY_COMPANY}}": self.config["my_company"],
"{{DATE_TODAY}}": datetime.now().strftime("%B %d, %Y"),
}
for placeholder, value in replacements.items():
template = template.replace(placeholder, value)
contract_path = client_dir / "01_contracts" / "contract.txt"
with open(contract_path, "w") as f:
f.write(template)
return contract_path
def _send_welcome_email(self, client_info: dict, contract_path: Path):
"""Send professional welcome email with contract"""
msg = MIMEMultipart()
msg["From"] = self.config["email"]
msg["To"] = client_info["email"]
msg["Subject"] = f"Welcome! Next Steps for {client_info['project_name']}"
body = f"""Hi {client_info['name'].split()[0]},
I'm thrilled to be working with you on {client_info['project_name']}!
Here's what happens next:
1. **Review the attached contract** — sign and return by {(datetime.now() + timedelta(days=2)).strftime("%B %d")}
2. **Schedule our kickoff call** — pick a time: {self.config['scheduling_link']}
3. **Complete the project brief** — I'll send this in our next email
I'll get started as soon as I receive the signed contract.
Looking forward to working together!
Best,
{self.config['my_name']}
"""
msg.attach(MIMEText(body, "plain"))
# Attach contract
with open(contract_path, "rb") as f:
attachment = MIMEText(f.read().decode(), "plain")
attachment.add_header("Content-Disposition", "attachment",
filename="contract.txt")
msg.attach(attachment)
# Send
with smtplib.SMTP_SSL(self.config["smtp_host"], 465) as smtp:
smtp.login(self.config["email"], self.config["smtp_password"])
smtp.send_message(msg)
def _update_crm(self, client_info: dict, client_dir: Path):
"""Append to CSV CRM file"""
import csv
crm_path = self.config.get("crm_path", "clients.csv")
row = {
"Date": datetime.now().strftime("%Y-%m-%d"),
"Name": client_info["name"],
"Company": client_info.get("company", ""),
"Email": client_info["email"],
"Project": client_info["project_name"],
"Rate": client_info["rate"],
"Status": "Onboarding",
"Folder": str(client_dir),
}
file_exists = Path(crm_path).exists()
with open(crm_path, "a", newline="") as f:
writer = csv.DictWriter(f, fieldnames=row.keys())
if not file_exists:
writer.writeheader()
writer.writerow(row)
# Usage
if __name__ == "__main__":
onboarding = ClientOnboarding()
new_client = {
"name": "Sarah Johnson",
"company": "TechStartup Inc",
"email": "sarah@techstartup.com",
"project_name": "E-commerce Analytics Dashboard",
"rate": "$150/hour",
"start_date": "2024-02-01"
}
result = onboarding.onboard_client(new_client)
print(f"Onboarding complete: {result}")
What This Does in 10 Minutes
- Input client info (name, email, project, rate) — 2 minutes
- Script runs — 30 seconds
- Review contract before sending — 5 minutes
- Send — 30 seconds
Total: under 10 minutes vs 2+ hours.
The Time Math
If you onboard 4 new clients per month:
- Before: 8+ hours/month on admin
- After: 40 minutes/month
- Time saved: 7+ hours to bill at your rate
At $100/hour, that's $700/month in extra billable time. The script pays for itself in the first client.
Setting It Up
Required files:
-
config.json— your settings (email, name, template paths) -
contract_template.txt— your contract with{{PLACEHOLDERS}}
config.json structure:
{
"my_name": "Your Name",
"my_company": "Your Company",
"email": "you@domain.com",
"smtp_host": "smtp.gmail.com",
"smtp_password": "your-app-password",
"scheduling_link": "https://calendly.com/yourname",
"templates_dir": "./templates",
"clients_dir": "./clients",
"crm_path": "./clients.csv"
}
Extending It Further
Once the basic onboarding runs, you can add:
- DocuSign integration for e-signatures
- Google Drive folder creation instead of local
- Notion/Airtable CRM updates
- Slack channel creation
- Invoice generation on contract signing
Each addition saves more time.
Want the complete toolkit with contract templates, config files, and all the automation scripts mentioned here?
Get the Python Business Automation Toolkit →
Includes 15+ scripts for invoicing, follow-ups, time tracking, client onboarding, and more. One-time purchase, immediate download.
What part of your client workflow do you want to automate first? Drop it in the comments — I'll help you figure out the approach.
Top comments (0)