Skip to Professional TimelineSkip to Personal TimelineSkip to Main Content

Start-up Initiative

A case study exploring the end-to-end technology stack behind the Start-up Initiative — from Google Places lead discovery and CRM pipeline management through tokenized intake forms, Amazon Chime voice integration, client provisioning, and Stripe-powered billing. Three perspectives: the architecture, the employee workflow, and the customer experience.

Technology Stack Perspective

Listen to Technology Stack Perspective

Building a client acquisition platform means solving a deceptively simple question first: how do you find the businesses you want to help? Everything else — the CRM, the intake forms, the billing — comes after you've answered that. So that's where we started.

Google Places Lead Discovery

The Google Places API became our eyes on the ground. The idea was straightforward: given a niche (say, plumbing or hair_salon) and a geographic area defined by a zip code and search radius, query Google Places for matching businesses and pull back everything we could learn about them — name, address, phone number, coordinates, business type, whether they already have a website.

That last detail turned out to be the most important filter. We weren't looking for businesses that already had a polished web presence — we were looking for the ones that didn't. The discovery pipeline automatically excludes businesses that already have a website listed in their Google Places profile, as well as those missing critical contact information like a phone number. What remains is a focused list of businesses that could genuinely benefit from what we're building, rather than a noisy dump of every result Google returns.

Each discovery search gets recorded in a DiscoveryLog — a simple but powerful audit trail that captures the zip code, search radius, geographic coordinates, niche, and how many results came back. This isn't just bookkeeping. Those logs feed a coverage map visualization that shows which areas have been searched and which are still untouched, making it easy to plan outreach campaigns geographically rather than guessing where to look next.

When a business passes the filters, it becomes a LeadRecord — the central entity that will follow this business through every stage of the pipeline. The record captures the Google Place ID (so we never create duplicates for the same business), the business name, formatted address, phone number, geographic coordinates, and the niche category. Each lead enters the funnel at the discovered stage, timestamped the moment it arrives. From here, the CRM takes over.

The credential pattern for the Places API follows the same approach we use across all our GCP integrations: a purpose-built service account with the minimum permissions needed — in this case, just Places API access. The credential is stored securely on the server and referenced via an environment variable, keeping it out of source control and making rotation painless.

CRM Pipeline

Once a lead exists, it needs a home — and more importantly, it needs a path forward. The CRM pipeline is built around eight funnel stages that represent the full lifecycle of a business relationship, from first discovery to active client or graceful exit.

The eight stages are: discovered, researched, contacted, intake_pending, intake_submitted, trial_active, converted, and churned. Each stage has a defined set of valid transitions — you can't skip from discovered straight to trial_active, for instance. The transition logic is enforced at the model level, so no matter how a stage change is triggered (admin panel, API call, automated workflow), the same validation rules apply. And churned is always an available exit from any non-terminal stage, because reality doesn't always follow the happy path.

Every stage transition records a timestamp on the lead record itself. There's a dedicated timestamp field for each stage — discovered_at, researched_at, contacted_at, and so on through churned_at. This means you can reconstruct the full timeline of any lead's journey without querying a separate activity log. How long did it take from discovery to first contact? How many days between sending the intake form and receiving a submission? The timestamps make these questions trivial to answer.

When a lead transitions to the researched stage, it triggers an enrichment process. The platform gathers additional data about the business from multiple sources and uses AI-powered summarization (via AWS Bedrock) to produce a research brief. This gives the team context before they ever pick up the phone — what the business does, what their online presence looks like, what opportunities might exist. The enrichment data lives on the lead record alongside the original discovery data, building a progressively richer picture of each business.

Contact tracking is baked into the lead model with fields for the preferred contact method (phone, email, or text), free-form contact notes, and an attempt counter that tracks how many outreach attempts have been made. There's also a do-not-contact flag — a simple boolean that, when set, prevents the lead from appearing in outreach queues. Compliance isn't glamorous, but it's non-negotiable.

Beyond the structured stage transitions, every meaningful interaction with a lead gets recorded in an activity log. Calls made, emails sent, notes added, stage changes — each event is timestamped and categorized, creating a chronological narrative of the relationship. This is the kind of data that's invaluable when someone new picks up a lead and needs to understand the full history in thirty seconds.

The diagram below shows how leads flow through the pipeline. Notice that converted and churned are terminal states — once a lead reaches either, there are no further transitions. Every other stage can exit to churned, reflecting the reality that a lead can drop off at any point in the process.

flowchart TD
    A[discovered] --> B[researched]
    A --> C[contacted]
    A --> H[churned]

    B --> C
    B --> H

    C --> D[intake_pending]
    C --> H

    D --> E[intake_submitted]
    D --> H

    E --> F[trial_active]
    E --> H

    F --> G[converted]
    F --> H
          

With the discovery engine feeding leads into the pipeline and the CRM tracking their journey through each stage, the foundation was in place. The next challenge was reaching out to those leads — which meant building a voice integration that could handle real phone calls, not just emails and form submissions.

Amazon Chime Voice Integration

Emails are easy to ignore. Text messages feel impersonal. When you're trying to reach a small business owner who's never heard of you, sometimes the most effective thing you can do is just call them. That realization led us to Amazon Chime SDK as the voice backbone of the outreach workflow.

The integration gives the team real voice calling capabilities — outbound calls to leads, inbound calls routed through a SIP media application, and a full recording pipeline that captures every conversation for later review. Each call creates a CallRecord that tracks the essentials: direction (inbound or outbound), duration, outcome (connected, voicemail, no answer), and the lead it's associated with. The call record also carries flags for recording status and transcription status, so the team can see at a glance whether a call has been processed through the full pipeline.

That pipeline is where things get interesting. When a call is recorded, the audio is captured through the Chime meeting session and stored in a secure S3 bucket. From there, AWS Transcribe converts the audio to text, producing a timestamped transcript. The transcript then feeds into an AI summarization step powered by AWS Bedrock, which distills the conversation into a concise brief — key topics discussed, any commitments made, next steps mentioned. The result is a call summary that lives on the CallRecord alongside the raw transcript, giving the team a quick-read version of every conversation without having to replay the audio.

Voice routing follows a SIP-based pattern. Each client profile can be linked to a Chime SIP media application, which handles the mapping between phone numbers and the platform's voice infrastructure. This means calls are routed through AWS's telephony network rather than requiring any on-premise PBX equipment — the entire voice stack is cloud-native, scalable, and managed. The credential and routing configuration follows the same least-privilege pattern we use across all AWS integrations: purpose-built IAM roles scoped to exactly the permissions needed for call management, recording access, and transcription.

Having voice integrated directly into the CRM changed the outreach workflow fundamentally. Instead of switching between a phone system and a lead management tool, the team could initiate calls from within the lead's record, and the call history would automatically appear in the activity log alongside emails, notes, and stage changes. It turned the CRM from a tracking tool into an actual workspace.

Tokenized Landing Pages and Intake Forms

Once a lead is interested — maybe after a phone call, maybe after an email exchange — the next step is gathering the information needed to build their web presence. We needed a way to send a prospective client a link they could open on their own time, fill out at their own pace, and submit without needing to create an account or remember a password. The answer was tokenized intake forms.

The concept is simple: when a lead reaches the intake_pending stage, the platform generates a unique, URL-safe token and stores it on the LeadRecord. That token becomes part of a landing page URL — a one-time link that's specific to that lead. When the recipient opens the link, the system validates the token against the database, confirms the lead is in the correct stage, and renders a multi-step intake form pre-associated with that lead. No login required, no account creation, no friction.

The intake form itself is structured as an eight-step wizard, each step collecting a different category of information. It starts with Business Info — the basics like business name, address, and contact details. Then Services, where the client describes what they offer. About captures the business story and mission. Domain/Branding collects preferences for domain names, color schemes, and existing brand assets like logos. Design Inspiration lets the client share examples of websites they admire. Content/Features gathers specifics about what pages and functionality they want. Photos handles file uploads for business imagery through a media asset pipeline. And finally, Analytics captures any existing tracking or analytics preferences.

When the client submits the completed form, the system validates the token one more time, creates an IntakeSubmission record linked to the LeadRecord, and transitions the lead's stage from intake_pending to intake_submitted. A confirmation email goes out to the client, and a notification hits the team so they know a new submission is ready for review. The entire flow — from clicking the link to submitting the form — happens without the client ever needing to authenticate.

flowchart TD
    A[LeadRecord reaches intake_pending] --> B[Generate URL-safe token]
    B --> C[Store token on LeadRecord]
    C --> D[Construct unique landing page URL]
    D --> E[Send URL to lead via email/text]
    E --> F[Lead opens landing page]
    F --> G{Validate token and stage}
    G -->|Valid| H[Render 8-step intake form]
    G -->|Invalid| I[Show error page]
    H --> J[Step 1: Business Info]
    J --> K[Step 2: Services]
    K --> L[Step 3: About]
    L --> M[Step 4: Domain/Branding]
    M --> N[Step 5: Design Inspiration]
    N --> O[Step 6: Content/Features]
    O --> P[Step 7: Photos]
    P --> Q[Step 8: Analytics]
    Q --> R[Submit form]
    R --> S{Re-validate token}
    S -->|Valid| T[Create IntakeSubmission]
    T --> U[Link to LeadRecord]
    U --> V[Transition to intake_submitted]
    V --> W[Send confirmation email]
    S -->|Invalid| I
          

The token generation and validation pattern is worth looking at more closely. The token needs to be unguessable (so no one can enumerate intake URLs), tied to a specific lead, and only valid while the lead is in the right stage. Here's the conceptual flow:


# Token Generation (when lead moves to intake_pending)
token = generate_url_safe_token()
lead.intake_token = token
lead.save()
intake_url = build_landing_page_url(token)
send_intake_link(lead.email, intake_url)

# Token Validation (when landing page is accessed)
lead = lookup_lead_by_token(token)
if lead is None:
    return error("Invalid token")
if lead.stage != "intake_pending":
    return error("Form no longer available")
render_intake_form(lead)

# Submission Processing
lead = lookup_lead_by_token(submitted_token)
validate_lead_exists_and_stage_is_pending(lead)
submission = create_intake_submission(form_data, lead)
lead.intake_submission = submission
lead.transition_to("intake_submitted")
send_confirmation_email(lead)
send_team_notification(submission)
        

The beauty of this approach is that the token acts as both an authentication mechanism and a data linkage key. The client never needs credentials, the form is always tied to the right lead, and the stage validation ensures a form can't be submitted twice or accessed after the lead has moved past the intake phase.

Lead-to-Profile Conversion

An intake submission sitting in the queue is just data. The real magic happens when the team reviews it, approves it, and kicks off the provisioning workflow that transforms a lead into an active client. This is the Lead-to-Profile conversion — the moment where a LeadRecord with an approved IntakeSubmission becomes a fully provisioned Profile with a user account, role-based access, and a trial subscription.

The conversion runs as an atomic transaction — either everything succeeds or nothing does. There's no halfway state where a profile exists but the user account doesn't, or the membership is created but the lead record isn't linked. The sequence starts by creating a Profile entity with a URL-safe abbreviation derived from the business name, subscription tier set to free_trial, and a 60-day trial window. Next, a User account is created with generated credentials based on the contact information from the intake submission. Then a ProfileMembership links that user to the new profile with the owner role — giving them full administrative access to their dashboard. Finally, the LeadRecord's client_profile field is set to point at the newly created profile, completing the linkage between the lead pipeline and the client management system.

Once the transaction commits, the lead's stage transitions to trial_active, and a welcome email goes out to the new client with their login credentials and a link to their dashboard. The email is the client's first interaction with the platform as an authenticated user rather than an anonymous lead — it's the handoff from the sales pipeline to the product experience.

flowchart TD
    A[Approved IntakeSubmission] --> B[Begin atomic transaction]
    B --> C[Create Profile with URL-safe abbreviation]
    C --> D[Set subscription: free_trial, 60-day window]
    D --> E[Create User account with generated credentials]
    E --> F[Create ProfileMembership with owner role]
    F --> G[Link LeadRecord.client_profile → Profile]
    G --> H{Transaction success?}
    H -->|Yes| I[Commit transaction]
    H -->|No| J[Rollback — no partial state]
    I --> K[Transition LeadRecord to trial_active]
    K --> L[Send welcome email with credentials]
    L --> M[Client accesses dashboard]
          

The conversion logic follows a deliberate pattern — create the entities in dependency order, link them together, and only commit when everything is consistent:


# Lead-to-Profile Conversion (triggered on IntakeSubmission approval)
with atomic_transaction():
    # Create the tenant entity
    profile = create_profile(
        name=submission.business_name,
        abbreviation=generate_url_safe_abbreviation(submission.business_name),
        subscription_tier="free_trial",
        trial_expires=now() + days(60)
    )

    # Create the user account
    user = create_user(
        email=submission.contact_email,
        name=submission.contact_name,
        credentials=generate_secure_credentials()
    )

    # Establish ownership
    membership = create_profile_membership(
        profile=profile,
        user=user,
        role="owner"
    )

    # Link lead to profile
    lead.client_profile = profile
    lead.save()

# Post-transaction steps
lead.transition_to("trial_active")
send_welcome_email(
    to=user.email,
    credentials=user.generated_credentials,
    dashboard_url=build_dashboard_url(profile)
)
        

What makes this pattern robust is the transactional boundary. If the user account creation fails (say, the email is already taken), the profile doesn't get orphaned in the database. If the membership creation fails, there's no user floating around without access to anything. The atomic guarantee means the system is always in a consistent state — either the full conversion happened, or it didn't happen at all.

With leads now converting into provisioned profiles, the next pieces of the puzzle were the subscription management, billing integration, and the data model tying everything together.

Profile Pages & Subscription Management

When a lead converts into a client, the entity that represents them going forward is the Profile — the top-level tenant in the system. Everything a client touches is scoped to their profile: their Google Ads credentials, their Stripe billing relationship, their Chime voice configuration, their Budget Guard spending caps, even their GA4 analytics property. The profile isn't just a user account — it's the organizational boundary that keeps one client's data completely separate from another's.

We landed on four subscription tiers after experimenting with different pricing models: free_trial, option_a, option_b, and none. The free trial gives new clients 60 days to explore the platform before committing. Options A and B represent different service levels at different price points — the specifics evolved over time, but the tier structure itself proved flexible enough to accommodate changes without schema migrations. Each profile carries its subscription tier, trial end date, and subscription start date as first-class fields, making it straightforward to query things like "which profiles have trials expiring this week" or "how many active Option B subscribers do we have."

Access to a profile isn't one-size-fits-all. The ProfileMembership model links users to profiles with role-based access — owner, admin, or viewer. The owner is typically the business owner who completed the intake form, but additional team members can be added as admins (full dashboard access) or viewers (read-only). The membership model enforces a unique constraint on the user-profile pair, so a user can belong to multiple profiles (useful for agencies managing several businesses) but can only have one role per profile. This turned out to be a surprisingly important design decision — early on we considered a simpler single-user-per-profile model, but the multi-membership approach opened the door for agency workflows that we hadn't initially anticipated.

The trial-to-paid conversion flow is where the subscription tier, the billing integration, and the profile all intersect. When a trial is approaching expiration, the platform can surface upgrade prompts. When the client selects a tier, the system creates a Stripe checkout session tied to their profile, and upon successful payment, the subscription tier updates from free_trial to their chosen plan. The profile's stripe_customer_id links the Django-side record to the Stripe-side customer, keeping billing state synchronized across both systems without duplicating data.

Promotional Offers & Billing

Client communication needed a structured approach beyond one-off emails. The ClientOffer model gives the team a way to create promotional offers with a title, body content, publish and expiration dates, and — critically — a target tiers field that controls which subscription tiers see the offer. This means a promotion can be targeted specifically at free trial users approaching their expiration, or exclusively at Option A subscribers who might benefit from upgrading. The is_active flag provides a manual kill switch, and the date-based filtering ensures offers appear and disappear on schedule without anyone needing to remember to toggle them.

The billing system itself is built on Stripe, and the integration follows a webhook-driven architecture rather than polling. When a client needs to pay — whether for a subscription upgrade, an hour pool purchase, or a custom invoice — the platform creates a Stripe checkout session with the relevant price data and redirects the client to Stripe's hosted payment page. This was a deliberate choice: rather than building a custom payment form (with all the PCI compliance headaches that entails), we let Stripe handle the sensitive card data entirely. The platform never sees or stores payment credentials.

After the client completes payment on Stripe's side, the confirmation flows back through a webhook. Stripe sends an event to a dedicated webhook endpoint, and the first thing the handler does is check for idempotency. Every incoming webhook event gets recorded in a WebhookEvent model keyed by Stripe's unique event ID. If the handler sees an event ID it's already processed, it returns a success response without doing anything — this prevents the double-processing issues that plague webhook integrations where Stripe retries delivery. Only after the idempotency check passes does the handler proceed to update the invoice status, activate the subscription tier, or credit the hour pool.

Beyond the standard checkout flow, the billing module supports quotes and invoices for custom billing scenarios. A Quote can be drafted with line items, sent to a client for review, and — if accepted — converted into a checkout session for payment. Invoice records track the full lifecycle from draft through sent, paid, overdue, or cancelled, with Stripe checkout session and payment intent IDs linking each invoice to its payment. The HourPool model handles prepaid service hours — clients purchase a block of hours at a per-hour rate, and usage gets logged against the pool until it's depleted or expires. Each of these billing models is scoped to a profile, maintaining the tenant isolation that runs through the entire system.

There's also a public-facing checkout endpoint that allows leads to self-serve tier selection using their intake token. This was an interesting addition — it means a lead who's already submitted their intake form can choose and pay for a subscription tier before the team even reviews their submission, shortening the time from interest to active client.

flowchart TD
    A[Client selects subscription tier] --> B[Create Stripe Checkout Session]
    B --> C[Redirect to Stripe payment page]
    C --> D[Client completes payment]
    D --> E[Stripe sends webhook event]
    E --> F{Check WebhookEvent for idempotency}
    F -->|Already processed| G[Return success — no action]
    F -->|New event| H[Record WebhookEvent]
    H --> I[Update Invoice status to paid]
    I --> J[Activate subscription tier on Profile]
    J --> K[Send payment confirmation email]
          

Data Model

Stepping back from the individual subsystems, it's worth looking at how all these entities relate to each other. The data model tells the story of the platform's architecture more concisely than any narrative can — it shows where the boundaries are, how data flows between subsystems, and which entities serve as the connective tissue holding everything together.

The Profile sits at the center as the tenant entity. Everything radiates outward from it: leads are scoped to a profile, discovery logs record searches performed under a profile, call records are associated with a profile, and all billing entities — invoices, quotes, hour pools — belong to a profile. The ProfileMembership model bridges the gap between Django's built-in User model and the profile, enabling the role-based access control that governs who can see and do what within each tenant's space.

The LeadRecord is the other major hub in the model. It connects to IntakeSubmission (one-to-one, via the intake token linkage), to CallRecord (one-to-many, tracking every voice interaction), and back to Profile in two distinct ways — the scoping relationship (which profile owns this lead) and the client_profile relationship (which profile was created when this lead converted). That dual relationship was one of those design decisions that felt slightly awkward at first but proved essential: the scoping profile is the team's operational context, while the client profile is the converted client's tenant entity. They're often different profiles entirely.

The ClientOffer connects to profiles through its target tiers JSON field rather than a direct foreign key — a pragmatic choice that avoids a many-to-many join table for what's essentially a filter condition. The DiscoveryLog provides the audit trail for lead discovery searches, scoped to the profile that initiated them. And the billing models — Invoice, Quote, and HourPool — each maintain their own relationship to the profile while linking to Stripe via session and payment intent identifiers.

erDiagram
    Profile ||--o{ ProfileMembership : "memberships"
    ProfileMembership }o--|| User : "user"
    Profile ||--o{ LeadRecord : "leads (scope)"
    LeadRecord ||--o| Profile : "client_profile"
    LeadRecord ||--o| IntakeSubmission : "intake_submission"
    LeadRecord ||--o{ CallRecord : "call_records"
    CallRecord }o--|| Profile : "profile"
    Profile ||--o{ DiscoveryLog : "discovery_logs"
    Profile ||--o{ Invoice : "invoices"
    Profile ||--o{ Quote : "quotes"
    Profile ||--o{ HourPool : "hour_pools"
    ClientOffer }o--o{ Profile : "target_tiers"

    Profile {
        string name
        string abbreviation
        string subscription_tier
        date trial_end_date
        string stripe_customer_id
    }
    LeadRecord {
        string business_name
        string stage
        string intake_token
        string google_place_id
    }
    IntakeSubmission {
        string business_name
        string intake_token
        string status
    }
    ProfileMembership {
        string role
    }
    CallRecord {
        string direction
        int duration_seconds
        string outcome
    }
    DiscoveryLog {
        string zip_code
        string niche
        int results_count
    }
    ClientOffer {
        string title
        json target_tiers
        boolean is_active
    }
    Invoice {
        string invoice_type
        string status
        int amount_cents
    }
    Quote {
        string title
        string status
        date valid_until
    }
    HourPool {
        decimal total_hours
        decimal used_hours
        int rate_per_hour_cents
    }
          

With the data model mapped out and the billing integration in place, the technology stack perspective is complete. But understanding the architecture is only one lens on the system. The next two perspectives — how employees use these tools day-to-day and how customers experience the platform from the outside — reveal a different kind of story: one about workflows, handoffs, and the human interactions that the technology enables.

Internal Employee Perspective

Listen to Internal Employee Perspective

Architecture diagrams and data models tell you what the system is. They don't tell you what it feels like to use it. The employee perspective fills that gap — it's the story of a typical day working inside the platform, from the moment a team member opens the CRM to the moment a new client is provisioned and ready to go.

The day usually starts with discovery. A team member selects a geographic area — maybe a zip code they haven't covered yet, or a region where a previous campaign showed promise — and kicks off a Google Places search for a specific niche. The results come back filtered: businesses without websites, with valid phone numbers, that haven't already been ingested. Each qualifying result becomes a new lead in the CRM, landing at the discovered stage with all the data Google Places provided — name, address, phone, coordinates, business type. The coverage map updates in real time, showing which areas have been searched and which are still blank, making it easy to plan the next batch of searches without overlapping previous work.

Once a batch of leads is in the system, the research phase begins. Transitioning a lead to researched triggers the enrichment pipeline — the platform pulls additional data from multiple sources and runs it through an AI summarization step that produces a concise research brief. By the time the team member is ready to make a call, they already know what the business does, what their current online presence looks like, and what opportunities might exist. That context turns a cold call into an informed conversation.

The outreach call itself happens directly from within the CRM. The team member initiates a Chime voice call from the lead's record, and the call is tracked automatically — direction, duration, outcome, whether it connected or went to voicemail. If the call is recorded, the audio flows through the transcription and AI summarization pipeline, and within minutes the team member has a written summary of the conversation sitting on the lead's record alongside the raw transcript. No manual note-taking required, though there's always the option to add free-form notes for context that the AI might miss.

When a lead expresses interest, the next step is sending the intake form. The team member transitions the lead to intake_pending, which generates a unique tokenized URL. That link gets sent to the lead via email or text — the team member chooses the channel based on the contact preferences they've gathered. From here, the ball is in the lead's court. The CRM shows the lead sitting at intake_pending, and the team member moves on to the next call.

Submissions trickle in over the following days. When a lead completes their intake form, the CRM updates the stage to intake_submitted and a notification alerts the team. The review process is straightforward — the team member opens the submission, reviews the eight sections of information the lead provided, and makes a decision. If everything looks good, they approve the submission and trigger the provisioning workflow.

Provisioning is where the lead becomes a client. The system creates a Profile, a user account, and an ownership membership in a single atomic transaction. The lead's record gets linked to the new profile, the stage transitions to trial_active, and a welcome email goes out with login credentials. The team member can see the new client's profile in the system immediately, complete with a 60-day free trial and access to their dashboard.

From there, subscription management becomes part of the ongoing relationship. The team can monitor trial expirations, send targeted promotional offers to specific subscription tiers, and track billing activity — invoices, quotes, hour pool usage — all scoped to the client's profile. When a trial client is ready to convert to a paid plan, the checkout flow handles the Stripe integration seamlessly, and the subscription tier updates automatically once payment confirms.

The diagram below traces this workflow as a sequence of interactions between the team member and the platform's subsystems — from the first discovery search through to a fully provisioned client.

sequenceDiagram
    participant Employee
    participant CRM
    participant Google Places
    participant Chime
    participant Intake System
    participant Provisioning

    Employee->>Google Places: Search niche + zip code
    Google Places-->>CRM: Filtered business results
    CRM-->>Employee: New leads at discovered stage

    Employee->>CRM: Transition lead to researched
    CRM->>CRM: Trigger enrichment + AI summary
    CRM-->>Employee: Research brief ready

    Employee->>Chime: Initiate outreach call
    Chime-->>CRM: CallRecord (direction, duration, outcome)
    Chime->>Chime: Record → Transcribe → AI summarize
    Chime-->>CRM: Transcript + call summary

    Employee->>CRM: Transition lead to intake_pending
    CRM->>Intake System: Generate tokenized URL
    Intake System-->>Employee: Unique intake link
    Employee->>Employee: Send link to lead via email/text

    Note over Intake System: Lead completes form independently

    Intake System-->>CRM: IntakeSubmission created
    CRM-->>Employee: Notification — submission ready

    Employee->>CRM: Review submission
    Employee->>Provisioning: Approve and trigger provisioning
    Provisioning->>Provisioning: Create Profile + User + Membership
    Provisioning->>CRM: Link lead to client profile
    Provisioning-->>Employee: Client provisioned — trial_active
    Provisioning->>Employee: Welcome email sent to client
          

What makes this workflow effective isn't any single feature — it's the continuity. The lead's record accumulates context at every stage: discovery data, research briefs, call transcripts, intake submissions. By the time a team member is provisioning a client, they have the full history of that relationship in one place, built up organically through the tools they were already using. The next perspective flips the lens entirely — what does this same journey look like from the customer's side?

External Customer Perspective

Listen to External Customer Perspective

From the customer's point of view, the technology stack is invisible. They don't see the CRM stages, the enrichment pipeline, or the provisioning transaction. What they experience is a series of human touchpoints — a phone call, a link, a form, an email, a dashboard — each one designed to feel simple and intentional, even though there's a complex system orchestrating things behind the scenes.

It starts with a phone call. A small business owner — let's say a plumber who's been running their business for years without a website — gets a call from someone who already knows a bit about their business. The caller can reference their location, their business type, maybe even mention that they noticed the business doesn't have a web presence yet. That's the research brief at work, though the customer has no idea it exists. The conversation is natural, informed, and focused on whether the business could benefit from a professional online presence. If the answer is yes, the caller explains that they'll send over a simple form to fill out — no pressure, no deadline, just whenever they have a few minutes.

A short while later, the customer receives a link — via email or text, depending on what they preferred during the call. The link opens a clean landing page that's specific to them. There's no login screen, no account creation, no password to remember. The tokenized URL handles all of that silently. The customer just sees a welcoming page that invites them to tell the team about their business.

The intake form is structured as eight manageable steps, each focused on a different aspect of the business. It starts with the basics — business name, address, contact information — and gradually moves into more expressive territory: what services they offer, the story behind their business, their preferences for colors and design, websites they admire, what features they'd want on their own site. The photo upload step lets them share images of their work, their storefront, their team. The form saves progress as they go, so they can step away and come back without losing anything. For a business owner who's never gone through this kind of process before, the step-by-step structure makes it approachable rather than overwhelming.

When they submit the completed form, a confirmation email arrives almost immediately — a simple acknowledgment that their information was received and the team will be in touch. Behind the scenes, the submission triggers a stage transition and a team notification, but the customer just sees a friendly confirmation. The waiting period is typically short.

The next email they receive is the welcome message. It contains their login credentials and a link to their new dashboard. This is the moment the customer transitions from "someone who filled out a form" to "an active client with their own space on the platform." The dashboard gives them visibility into their account — their subscription status, their profile information, and eventually their website analytics and advertising performance. The 60-day free trial means they can explore everything without financial commitment, getting a feel for the platform before deciding whether to continue.

When the trial period approaches its end — or whenever the customer decides they're ready — the billing interaction is equally straightforward. They select a subscription tier, get redirected to a secure Stripe-hosted payment page (the platform never handles their card details directly), complete the payment, and their subscription activates automatically. A confirmation email arrives, and their dashboard reflects the new tier. If they received a promotional offer targeted at their subscription level, that offer is surfaced at the right moment — not as a generic blast, but as a relevant suggestion based on where they are in their journey.

The entire experience — from that first phone call to an active, paying subscription — is designed to feel like a series of personal interactions rather than a funnel. The customer never fills out a form they don't understand, never creates an account they'll forget, and never enters payment information on a page that doesn't look trustworthy. Each step builds on the last, and each one is simpler than the customer probably expected.

sequenceDiagram
    participant Customer
    participant Landing Page
    participant Intake Form
    participant Email
    participant Dashboard
    participant Billing

    Note over Customer: Receives outreach phone call

    Email->>Customer: Tokenized intake link (email or text)
    Customer->>Landing Page: Opens unique URL
    Landing Page->>Landing Page: Validate token silently
    Landing Page-->>Customer: Personalized welcome page

    Customer->>Intake Form: Step 1 — Business Info
    Customer->>Intake Form: Step 2 — Services
    Customer->>Intake Form: Step 3 — About
    Customer->>Intake Form: Step 4 — Domain/Branding
    Customer->>Intake Form: Step 5 — Design Inspiration
    Customer->>Intake Form: Step 6 — Content/Features
    Customer->>Intake Form: Step 7 — Photos
    Customer->>Intake Form: Step 8 — Analytics
    Intake Form-->>Customer: Form submitted successfully

    Email->>Customer: Submission confirmation

    Note over Customer: Team reviews and approves

    Email->>Customer: Welcome email with credentials
    Customer->>Dashboard: First login
    Dashboard-->>Customer: Account overview + trial status

    Customer->>Billing: Select subscription tier
    Billing->>Billing: Stripe-hosted payment page
    Billing-->>Customer: Payment confirmed
    Email->>Customer: Subscription activation confirmation
    Customer->>Dashboard: Updated tier + full access
          

The employee and customer perspectives reveal two sides of the same coin — one focused on efficiency and context, the other on simplicity and trust. But neither perspective captures what happens in the spaces between human interactions, where the subsystems talk to each other autonomously. That's the system integration perspective — the automated choreography that keeps everything synchronized without anyone pressing a button.

System Integration

Listen to System Integration

The three perspectives above — technology, employee, customer — each tell a story with a human at the center. But some of the most important work in the platform happens without anyone initiating it. The subsystems don't just sit idle between human interactions; they talk to each other, passing data, triggering transitions, and maintaining consistency across the pipeline in ways that are invisible to both the team and the customer.

It starts at the boundary between discovery and the CRM. When the Google Places API returns a batch of filtered results, the CRM doesn't wait for someone to manually import them. Each qualifying business is ingested as a LeadRecord automatically — deduplicated by Google Place ID, stamped with the discovered stage, and linked to the DiscoveryLog that recorded the search parameters. The coverage map updates itself from those logs, so the next time a team member plans a search, the geographic intelligence is already current. No one had to press a sync button or run a report.

The handoff between the CRM and Amazon Chime follows a similar pattern. When a call is initiated from a lead's record, the CRM passes the context to Chime — the phone number, the lead association, the call direction. Chime handles the telephony, but when the call ends, the data flows back: a CallRecord lands on the lead with duration, outcome, and recording status. If the call was recorded, the audio moves through the transcription and summarization pipeline autonomously — Chime captures the audio, the transcription service converts it to text, and the AI summarization step distills it into a brief. By the time the team member looks at the lead's record again, the call summary is already there. The CRM didn't poll for it; the pipeline pushed it through.

The intake system sits at a particularly interesting integration point. When the CRM generates a tokenized URL and the lead eventually opens it, the intake system validates the token against the CRM's data without any intermediary. The form submission creates an IntakeSubmission, links it to the LeadRecord, and transitions the lead's stage — all in a single operation that spans two subsystems. The CRM's stage transition triggers a notification to the team, and the intake system sends a confirmation to the customer. Two different audiences, two different messages, one atomic event.

Provisioning is where the integration density peaks. When the team approves a submission, the provisioning service orchestrates a transaction that touches the CRM (stage transition to trial_active), the profile system (creating the tenant entity, user account, and membership), and the email service (welcome message with credentials). If any step fails, the entire transaction rolls back — the CRM doesn't show a converted lead with no profile, and the customer doesn't receive credentials for an account that doesn't exist. The atomicity isn't just a database concern; it's an integration contract between subsystems.

Stripe completes the loop. When a client selects a subscription tier and completes payment on Stripe's hosted page, the webhook event flows back to the platform's billing module. The idempotency check against the WebhookEvent model ensures that even if Stripe retries the webhook (which it will, by design), the platform processes it exactly once. The billing module updates the invoice, activates the subscription tier on the profile, and sends a confirmation — all without the CRM, the provisioning service, or the team needing to be involved. The payment event ripples through the system and settles into the right state automatically.

The diagram below traces these automated interactions as a sequence — not initiated by any human actor, but by the subsystems responding to events and passing data between themselves. It's the connective tissue that makes the employee's workflow feel seamless and the customer's experience feel effortless.

sequenceDiagram
    participant Google Places API
    participant CRM
    participant Amazon Chime
    participant Intake System
    participant Provisioning Service
    participant Stripe

    Google Places API->>CRM: Filtered business results (deduplicated)
    CRM->>CRM: Create LeadRecords at discovered stage
    CRM->>CRM: Update coverage map from DiscoveryLog

    CRM->>Amazon Chime: Initiate call (phone number + lead context)
    Amazon Chime-->>CRM: CallRecord (duration, outcome, recording status)
    Amazon Chime->>Amazon Chime: Audio → Transcribe → AI summarize
    Amazon Chime-->>CRM: Transcript + call summary on CallRecord

    CRM->>Intake System: Generate tokenized URL for lead
    Note over Intake System: Lead opens link independently
    Intake System->>Intake System: Validate token against CRM data
    Intake System->>CRM: Create IntakeSubmission + transition to intake_submitted
    Intake System-->>CRM: Team notification triggered

    CRM->>Provisioning Service: Approved submission — trigger provisioning
    Provisioning Service->>Provisioning Service: Atomic: Profile + User + Membership
    Provisioning Service->>CRM: Link lead.client_profile + transition to trial_active
    Provisioning Service->>Provisioning Service: Send welcome email with credentials

    Note over Stripe: Client selects tier and completes payment
    Stripe->>Stripe: Webhook event fired
    Stripe->>CRM: Webhook received — idempotency check via WebhookEvent
    CRM->>CRM: Update Invoice status + activate subscription tier
    CRM-->>Stripe: Acknowledge webhook (200 OK)
          

What emerges from this view is a system where the boundaries between subsystems are well-defined but the data flows freely across them. Google Places feeds the CRM, the CRM orchestrates Chime and the intake system, the intake system feeds back into the CRM, provisioning draws from the CRM and creates new entities, and Stripe closes the billing loop — each subsystem doing its part and trusting the others to do theirs. The three human perspectives — the architect who designed it, the employee who uses it daily, and the customer who experiences it from the outside — are all looking at the same machine from different angles. The technology stack is the blueprint, the employee workflow is the operating rhythm, the customer journey is the output, and the system integration is the engine that connects them all.