DEV Community

Vasquez MyGuy
Vasquez MyGuy

Posted on

I Automated My Entire Client Onboarding in Python — Here is the 150-Line Script

I Automated My Entire Client Onboarding in Python — Here's the 150-Line Script

Last month I spent 6 hours manually setting up a new client: creating their project folder, sending welcome emails, setting up Slack channels, creating Trello boards, generating invoice drafts. For one client.

If you're a freelancer or running a small agency, you know this pain. Every new client means 30-60 minutes of repetitive setup work before you can actually do the job they hired you for.

So I automated it. The whole pipeline. In 150 lines of Python.

What the Pipeline Does

When a new client signs up (via a form, email, or Stripe payment), this script automatically:

  1. Creates a project folder in Google Drive with the right structure
  2. Sends a personalized welcome email with next steps
  3. Creates a Trello board with pre-populated lists and cards
  4. Generates a draft invoice in Stripe
  5. Logs everything to a Notion database
  6. Sends me a Slack notification so I know to check in

The whole thing runs in under 10 seconds. Let me show you how.

The Architecture

New Client Signup
       |
       v
  [Webhook Receiver]  ← Stripe, Typeform, or raw HTTP
       |
       v
  [Client Pipeline]
       |
       +--→ Google Drive: /Clients/{Name}/
       +--→ Send Welcome Email
       +--→ Trello: New Board
       +--→ Stripe: Draft Invoice
       +--→ Notion: Log Entry
       +--→ Slack: Notification
Enter fullscreen mode Exit fullscreen mode

I chose a pipeline pattern over a monolithic script because:

  • Each step is independent and can fail/retry without affecting others
  • Easy to add new steps later
  • Testable in isolation

The Code

Step 1: Configuration and Data Models

from dataclasses import dataclass, field
from datetime import datetime
from typing import Optional
import os
import httpx
import json

@dataclass
class Client:
    """New client data from signup form or Stripe webhook."""
    name: str
    email: str
    company: str
    package: str  # "starter", "growth", "enterprise"
    phone: Optional[str] = None
    notes: Optional[str] = None
    created_at: str = field(default_factory=lambda: datetime.now().isoformat())

# Package pricing (USD cents)
PACKAGES = {
    "starter":     {"price": 500_00,  "hours": 10},
    "growth":      {"price": 1500_00, "hours": 30},
    "enterprise":  {"price": 5000_00, "hours": 80},
}

# Folder structure for new projects
FOLDER_TEMPLATE = [
    "01_Brief",
    "02_Assets",
    "03_Drafts",
    "04_Final_Deliverables",
    "05_Invoices",
]
Enter fullscreen mode Exit fullscreen mode

Step 2: The Pipeline Runner

class Pipeline:
    """Run a sequence of automation steps, log failures, continue on error."""

    def __init__(self):
        self.results = {}
        self.errors = []

    def run(self, client: Client):
        steps = [
            ("drive", create_project_folder),
            ("email", send_welcome_email),
            ("trello", create_trello_board),
            ("invoice", create_stripe_invoice),
            ("notion", log_to_notion),
            ("slack", notify_slack),
        ]

        for name, step_fn in steps:
            try:
                result = step_fn(client)
                self.results[name] = result
                print(f"{name}: {result}")
            except Exception as e:
                self.errors.append((name, str(e)))
                print(f"{name}: {e}")

        return len(self.errors) == 0, self.results, self.errors
Enter fullscreen mode Exit fullscreen mode

The key design choice: each step runs even if previous steps fail. If the Trello API is down, we still send the welcome email and create the invoice. We log errors and handle them asynchronously.

Step 3: Project Folder Creation

def create_project_folder(client: Client) -> str:
    """Create a Google Drive folder structure for the new client."""
    DRIVE_API = "https://www.googleapis.com/drive/v3/files"

    headers = {
        "Authorization": f"Bearer {os.environ['GOOGLE_TOKEN']}",
        "Content-Type": "application/json",
    }

    # Create root client folder
    root = httpx.post(DRIVE_API, headers=headers, json={
        "name": f"{client.company} - {client.name}",
        "mimeType": "application/vnd.google-apps.folder",
    }).json()

    # Create subfolders from template
    for folder_name in FOLDER_TEMPLATE:
        httpx.post(DRIVE_API, headers=headers, json={
            "name": folder_name,
            "mimeType": "application/vnd.google-apps.folder",
            "parents": [root["id"]],
        })

    return f"Created folder {root['id']}"
Enter fullscreen mode Exit fullscreen mode

Step 4: Welcome Email

def send_welcome_email(client: Client) -> str:
    """Send personalized onboarding email."""
    package_info = PACKAGES[client.package]
    hours = package_info["hours"]

    body = (
        f"Hi {client.name},\n\n"
        f"Welcome aboard! Excited to work with {client.company}.\n\n"
        f"Here is what happens next:\n\n"
        f"1. Project workspace created — access within 24 hours\n"
        f"2. Project board set up with {hours} hours allocated\n"
        f"3. Brief questionnaire arriving soon for requirements\n\n"
        f"First deliverable: 48 hours from brief completion.\n\n"
        f"Questions? Just reply — we respond within 2 hours.\n\n"
        f"Best,\nVasquez Ventures"
    )

    httpx.post("https://api.your-email-provider.com/send", json={
        "to": [client.email],
        "subject": f"Welcome to Vasquez Ventures, {client.name}!",
        "text": body,
        "from": "hello@vasquezventures.com",
    })

    return f"Welcome email sent to {client.email}"
Enter fullscreen mode Exit fullscreen mode

Step 5: Trello Board

def create_trello_board(client: Client) -> str:
    """Create a Trello board with pre-populated workflow."""
    TRELLO_API = "https://api.trello.com/1"
    key = os.environ["TRELLO_API_KEY"]
    token = os.environ["TRELLO_TOKEN"]

    board = httpx.post(f"{TRELLO_API}/boards", params={
        "key": key, "token": token,
        "name": f"{client.company} Project",
        "defaultLists": "false",
    }).json()

    board_id = board["id"]

    # Create workflow lists
    lists = ["Brief & Requirements", "In Progress", "Review", "Done"]
    list_ids = {}
    for list_name in lists:
        result = httpx.post(f"{TRELLO_API}/lists", params={
            "key": key, "token": token,
            "name": list_name,
            "idBoard": board_id,
        }).json()
        list_ids[list_name] = result["id"]

    # Add kickoff card
    httpx.post(f"{TRELLO_API}/cards", params={
        "key": key, "token": token,
        "name": f"Kickoff: {client.company}",
        "desc": f"Client: {client.name}\nPackage: {client.package}\nEmail: {client.email}",
        "idList": list_ids["Brief & Requirements"],
    })

    return f"Board created: {board['shortUrl']}"
Enter fullscreen mode Exit fullscreen mode

Step 6: Stripe Invoice

def create_stripe_invoice(client: Client) -> str:
    """Create a draft invoice in Stripe."""
    STRIPE_API = "https://api.stripe.com/v1"
    stripe_key = os.environ["STRIPE_SECRET_KEY"]
    headers = {"Authorization": f"Bearer {stripe_key}"}

    # Create or retrieve customer
    customer = httpx.post(
        f"{STRIPE_API}/customers",
        headers=headers,
        data={
            "name": client.name,
            "email": client.email,
            "metadata": {"company": client.company},
        },
    ).json()

    package_info = PACKAGES[client.package]

    # Create invoice item
    httpx.post(
        f"{STRIPE_API}/invoiceitems",
        headers=headers,
        data={
            "customer": customer["id"],
            "amount": package_info["price"],
            "currency": "usd",
            "description": f"{client.package.title()} Package",
        },
    )

    # Create draft invoice
    invoice = httpx.post(
        f"{STRIPE_API}/invoices",
        headers=headers,
        data={
            "customer": customer["id"],
            "collection_method": "send_invoice",
            "days_until_due": 14,
        },
    ).json()

    return f"Draft invoice {invoice['id']} created"
Enter fullscreen mode Exit fullscreen mode

Step 7: Notion Logging

def log_to_notion(client: Client) -> str:
    """Log new client to Notion database."""
    NOTION_API = "https://api.notion.com/v1"
    notion_key = os.environ["NOTION_API_KEY"]
    database_id = os.environ["NOTION_CLIENTS_DB"]

    httpx.post(
        f"{NOTION_API}/pages",
        headers={
            "Authorization": f"Bearer {notion_key}",
            "Notion-Version": "2022-06-28",
            "Content-Type": "application/json",
        },
        json={
            "parent": {"database_id": database_id},
            "properties": {
                "Name": {"title": [{"text": {"content": client.name}}]},
                "Company": {"rich_text": [{"text": {"content": client.company}}]},
                "Package": {"select": {"name": client.package}},
                "Email": {"email": client.email},
                "Status": {"select": {"name": "New"}},
            },
        },
    )

    return f"Logged {client.name} in Notion"
Enter fullscreen mode Exit fullscreen mode

Step 8: Slack Notification

def notify_slack(client: Client) -> str:
    """Send a Slack notification about the new client."""
    webhook_url = os.environ["SLACK_WEBHOOK_URL"]
    package_info = PACKAGES[client.package]
    price = package_info["price"] / 100

    httpx.post(webhook_url, json={
        "text": f"New client: {client.name} from {client.company}",
        "blocks": [
            {
                "type": "section",
                "text": {
                    "type": "mrkdwn",
                    "text": (
                        f"*New Client Signed Up!*\n\n"
                        f"Name: {client.name}\n"
                        f"Company: {client.company}\n"
                        f"Package: {client.package.title()}\n"
                        f"Email: {client.email}\n"
                        f"Value: ${price:,.0f}"
                    ),
                },
            }
        ],
    })

    return "Slack notification sent"
Enter fullscreen mode Exit fullscreen mode

Step 9: Webhook Receiver

from fastapi import FastAPI, Request

app = FastAPI()

@app.post("/webhook/new-client")
async def handle_new_client(request: Request):
    """Receive new client data from Stripe, Typeform, or manual trigger."""
    payload = await request.json()

    client = Client(
        name=payload.get("name", ""),
        email=payload.get("email", ""),
        company=payload.get("company", ""),
        package=payload.get("package", "starter"),
        phone=payload.get("phone"),
        notes=payload.get("notes"),
    )

    pipeline = Pipeline()
    success, results, errors = pipeline.run(client)

    return {
        "status": "success" if success else "partial",
        "client": client.name,
        "results": results,
        "errors": errors,
    }
Enter fullscreen mode Exit fullscreen mode

Step 10: Test It Locally

if __name__ == "__main__":
    # Test with a mock client
    test_client = Client(
        name="Sarah Chen",
        email="sarah@example.com",
        company="Acme Corp",
        package="growth",
    )

    pipeline = Pipeline()
    success, results, errors = pipeline.run(test_client)

    print(f"\nPipeline {'succeeded' if success else 'had errors'}")
    print(f"Results: {json.dumps(results, indent=2)}")
    if errors:
        print(f"Errors: {errors}")
Enter fullscreen mode Exit fullscreen mode

How to Deploy This

  1. Set up API keys: Google Drive, Trello, Stripe, Notion, Slack — all have free tiers or developer tokens.

  2. Deploy the webhook server:

pip install fastapi uvicorn httpx
uvicorn main:app --host 0.0.0.0 --port 8000
Enter fullscreen mode Exit fullscreen mode
  1. Connect to Stripe: Stripe Dashboard → Webhooks → Add endpoint → your domain + /webhook/new-client

  2. Or trigger manually: Run the script directly and pass client data as a JSON file:

python pipeline.py < client.json
Enter fullscreen mode Exit fullscreen mode

What I Learned

  • The pipeline pattern is king. Individual steps that can fail independently mean your onboarding doesn't break because Trello is down.
  • Start with email + notification. Those two steps alone save 30 minutes per client. Add the rest later.
  • Draft invoices, don't auto-charge. Always review before sending. But having the draft ready saves you 10 minutes.
  • Folder structure is underrated. A consistent folder structure for every client means you never lose files. Ever.

The ROI

I was spending 45 minutes per client on manual setup. With this pipeline, it's under 10 seconds plus a 2-minute review of the draft invoice.

At 5 clients/month, that's saving 3.75 hours/month — or about $375/month if you value your time at $100/hour.

The script took me 3 hours to write and test. It paid for itself in the first month.


If you want help automating your business workflows — from client onboarding to data pipelines to content systems — check out Vasquez Ventures. We build custom automation scripts starting at $200.

Got a specific workflow you want automated? Email me — I respond within 2 hours.

Top comments (0)