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:
- Creates a project folder in Google Drive with the right structure
- Sends a personalized welcome email with next steps
- Creates a Trello board with pre-populated lists and cards
- Generates a draft invoice in Stripe
- Logs everything to a Notion database
- 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
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",
]
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
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']}"
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}"
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']}"
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"
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"
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"
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,
}
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}")
How to Deploy This
Set up API keys: Google Drive, Trello, Stripe, Notion, Slack — all have free tiers or developer tokens.
Deploy the webhook server:
pip install fastapi uvicorn httpx
uvicorn main:app --host 0.0.0.0 --port 8000
Connect to Stripe: Stripe Dashboard → Webhooks → Add endpoint → your domain +
/webhook/new-clientOr trigger manually: Run the script directly and pass client data as a JSON file:
python pipeline.py < client.json
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)