DEV Community

Brad
Brad

Posted on

How I Cut My Client Onboarding Time from 2 Hours to 10 Minutes with Python

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:

  1. Manually creating a contract in Google Docs
  2. Filling in their name, company, project details, rates
  3. Creating a project folder structure (manually!)
  4. Sending a welcome email with next steps
  5. Adding to spreadsheet CRM
  6. Setting up invoicing
  7. 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}")
Enter fullscreen mode Exit fullscreen mode

What This Does in 10 Minutes

  1. Input client info (name, email, project, rate) — 2 minutes
  2. Script runs — 30 seconds
  3. Review contract before sending — 5 minutes
  4. 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:

  1. config.json — your settings (email, name, template paths)
  2. 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"
}
Enter fullscreen mode Exit fullscreen mode

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)