← Monday's Prompts

Automate Partnership Pipeline 🚀

Turn Monday's 3 prompts into production-ready deal flow

September 2, 2025
🤝 Strategy⚡ TypeScript + Python📈 10 → 500 deals/year

The Problem

On Monday you tested the 3 prompts in ChatGPT. Cool! You saw how discovery → outreach → tracking works. But here's reality: you can't manage 50 active partnerships in a spreadsheet. You lose context. Forget follow-ups. Miss deal signals. Your VP asks 'where are we with Acme Corp?' and you're digging through emails.

3+ hours
Per day updating spreadsheets manually
60% miss
Follow-ups forgotten in manual tracking
Can't scale
Beyond 20-30 active partnerships

See It Work

Watch the 3 prompts chain together automatically. This is what you'll build.

The Code

Three levels: start simple, add reliability, then scale to production. Pick where you are.

Level 1: Simple API Calls

Good for: 0-50 active deals | Setup time: 30 minutes

// Simple API Calls (0-50 active deals)
import Anthropic from '@anthropic-ai/sdk';
import axios from 'axios';

interface PartnershipCriteria {
  industry: string;
  employeeRange: string;
  fundingStage: string[];
  geography: string;
  technicalReqs: string[];
}

interface DealFlow {
  discovery: any;
  outreach: any;
  tracking: any;
}

async function automatePartnershipPipeline(
  criteria: string
): Promise<DealFlow> {
  const anthropic = new Anthropic({
    apiKey: process.env.ANTHROPIC_API_KEY!,
  });

  // Step 1: Discover and score potential partners
  const discoveryPrompt = `Extract partnership search criteria and identify top 3 potential partners.

Criteria: ${criteria}

Output as JSON with:
- search_criteria (industry, employee_range, funding_stage, etc)
- top_matches array with: company, score (0-100), why_good_fit, contact_priority, estimated_deal_size

Use real companies that match the criteria.`;

  const discoveryResponse = await anthropic.messages.create({
    model: 'claude-3-5-sonnet-20241022',
    max_tokens: 2048,
    messages: [{ role: 'user', content: discoveryPrompt }],
  });

  const discoveryContent = discoveryResponse.content[0];
  if (discoveryContent.type !== 'text') throw new Error('Invalid response');
  const discovery = JSON.parse(discoveryContent.text);

  // Step 2: Generate outreach sequences for top match
  const topPartner = discovery.top_matches[0];
  const outreachPrompt = `Create a 3-touch outreach sequence for this partnership opportunity.

Target: ${topPartner.company}
Why good fit: ${topPartner.why_good_fit}

Output as JSON with:
- decision_makers array (name, title, linkedin, email_pattern, outreach_angle)
- outreach_sequence array (day, channel, message, goal)
- next_action
- follow_up_date

Make messages personalized and specific, not generic.`;

  const outreachResponse = await anthropic.messages.create({
    model: 'claude-3-5-sonnet-20241022',
    max_tokens: 2048,
    messages: [{ role: 'user', content: outreachPrompt }],
  });

  const outreachContent = outreachResponse.content[0];
  if (outreachContent.type !== 'text') throw new Error('Invalid response');
  const outreach = JSON.parse(outreachContent.text);

  // Step 3: Initialize deal tracking
  const trackingPrompt = `Create initial deal tracking structure for this partnership.

Partner: ${topPartner.company}
Outreach plan: ${JSON.stringify(outreach.outreach_sequence)}

Output as JSON with:
- deal_stage (Initial Contact, Technical Review, Negotiation, Closing)
- health_score (0-100)
- next_steps array (action, owner, due_date, priority)
- risk_factors array (risk, mitigation, severity)
- estimated_close_date
- confidence (percentage)`;

  const trackingResponse = await anthropic.messages.create({
    model: 'claude-3-5-sonnet-20241022',
    max_tokens: 2048,
    messages: [{ role: 'user', content: trackingPrompt }],
  });

  const trackingContent = trackingResponse.content[0];
  if (trackingContent.type !== 'text') throw new Error('Invalid response');
  const tracking = JSON.parse(trackingContent.text);

  return { discovery, outreach, tracking };
}

// Usage
const criteria = `Looking for SaaS companies in HR tech space with 50-200 employees...`;
const pipeline = await automatePartnershipPipeline(criteria);

console.log(`Found ${pipeline.discovery.top_matches.length} potential partners`);
console.log(`Top match: ${pipeline.discovery.top_matches[0].company}`);
console.log(`Next action: ${pipeline.outreach.next_action}`);

Level 2: With CRM Integration & Enrichment

Good for: 50-200 active deals | Setup time: 2 hours

# With CRM Integration & Enrichment (50-200 active deals)
import anthropic
import requests
import os
from datetime import datetime, timedelta
from typing import Dict, List

class PartnershipPipeline:
    def __init__(self):
        self.anthropic = anthropic.Anthropic(api_key=os.getenv('ANTHROPIC_API_KEY'))
        self.clearbit_key = os.getenv('CLEARBIT_API_KEY')
        self.hubspot_key = os.getenv('HUBSPOT_API_KEY')
    
    def enrich_company_data(self, company_name: str) -> Dict:
        """Use Clearbit to get real company data"""
        try:
            response = requests.get(
                f'https://company.clearbit.com/v2/companies/find',
                params={'name': company_name},
                headers={'Authorization': f'Bearer {self.clearbit_key}'},
                timeout=10
            )
            response.raise_for_status()
            data = response.json()
            
            return {
                'name': data.get('name'),
                'domain': data.get('domain'),
                'employees': data.get('metrics', {}).get('employees'),
                'funding': data.get('metrics', {}).get('raised'),
                'tech_stack': data.get('tech', []),
                'linkedin': data.get('linkedin', {}).get('handle'),
            }
        except Exception as e:
            print(f'Enrichment failed for {company_name}: {e}')
            return {'name': company_name, 'error': str(e)}
    
    def create_hubspot_deal(self, partner_data: Dict, outreach_data: Dict) -> str:
        """Create deal in HubSpot CRM"""
        deal_payload = {
            'properties': {
                'dealname': f'Partnership - {partner_data["company"]}',
                'dealstage': 'qualifiedtobuy',  # Initial contact stage
                'amount': partner_data.get('estimated_deal_size', '0'),
                'closedate': (datetime.now() + timedelta(days=90)).isoformat(),
                'pipeline': 'partnerships',
                'partnership_score': partner_data.get('score', 0),
                'next_action': outreach_data.get('next_action', ''),
            }
        }
        
        try:
            response = requests.post(
                'https://api.hubapi.com/crm/v3/objects/deals',
                headers={
                    'Authorization': f'Bearer {self.hubspot_key}',
                    'Content-Type': 'application/json'
                },
                json=deal_payload,
                timeout=10
            )
            response.raise_for_status()
            deal_id = response.json()['id']
            print(f'Created HubSpot deal: {deal_id}')
            return deal_id
        except Exception as e:
            print(f'Failed to create HubSpot deal: {e}')
            return ''
    
    def discover_partners(self, criteria: str) -> List[Dict]:
        """Step 1: Discover and score partners with enrichment"""
        prompt = f"""Extract partnership criteria and identify top 5 potential partners.

Criteria: {criteria}

Output as JSON with search_criteria and top_matches array.
Include: company, score, why_good_fit, contact_priority, estimated_deal_size, integration_complexity."""
        
        response = self.anthropic.messages.create(
            model='claude-3-5-sonnet-20241022',
            max_tokens=2048,
            messages=[{'role': 'user', 'content': prompt}]
        )
        
        content = response.content[0]
        if content.type != 'text':
            raise ValueError('Invalid response type')
        
        discovery = anthropic.json.loads(content.text)
        
        # Enrich top 3 matches with real data
        for match in discovery['top_matches'][:3]:
            enriched = self.enrich_company_data(match['company'])
            match['enriched_data'] = enriched
        
        return discovery['top_matches']
    
    def generate_outreach(self, partner: Dict) -> Dict:
        """Step 2: Generate personalized outreach sequence"""
        prompt = f"""Create a 3-touch outreach sequence for this partnership.

Target: {partner['company']}
Why good fit: {partner['why_good_fit']}
Enriched data: {partner.get('enriched_data', {})}

Output as JSON with:
- decision_makers (find real people if possible)
- outreach_sequence (3 touches: LinkedIn, Email, Email)
- next_action
- follow_up_date

Make messages specific and personalized."""
        
        response = self.anthropic.messages.create(
            model='claude-3-5-sonnet-20241022',
            max_tokens=2048,
            messages=[{'role': 'user', 'content': prompt}]
        )
        
        content = response.content[0]
        if content.type != 'text':
            raise ValueError('Invalid response type')
        
        return anthropic.json.loads(content.text)
    
    def track_deal(self, partner: Dict, outreach: Dict) -> Dict:
        """Step 3: Initialize deal tracking with health scoring"""
        prompt = f"""Create deal tracking structure for this partnership.

Partner: {partner['company']}
Score: {partner['score']}
Outreach plan: {outreach['outreach_sequence']}

Output as JSON with:
- deal_stage
- health_score (0-100)
- signals array (type, signal, impact)
- next_steps array (action, owner, due_date, priority)
- risk_factors array (risk, mitigation, severity)
- estimated_close_date
- confidence"""
        
        response = self.anthropic.messages.create(
            model='claude-3-5-sonnet-20241022',
            max_tokens=2048,
            messages=[{'role': 'user', 'content': prompt}]
        )
        
        content = response.content[0]
        if content.type != 'text':
            raise ValueError('Invalid response type')
        
        return anthropic.json.loads(content.text)
    
    def run_pipeline(self, criteria: str) -> Dict:
        """Full pipeline: discover → enrich → outreach → track → CRM"""
        print('🔍 Discovering partners...')
        partners = self.discover_partners(criteria)
        
        print(f'✅ Found {len(partners)} potential partners')
        top_partner = partners[0]
        
        print(f'📧 Generating outreach for {top_partner["company"]}...')
        outreach = self.generate_outreach(top_partner)
        
        print('📊 Setting up deal tracking...')
        tracking = self.track_deal(top_partner, outreach)
        
        print('💼 Creating HubSpot deal...')
        deal_id = self.create_hubspot_deal(top_partner, outreach)
        
        return {
            'partner': top_partner,
            'outreach': outreach,
            'tracking': tracking,
            'crm_deal_id': deal_id
        }

# Usage
pipeline = PartnershipPipeline()

criteria = """Looking for SaaS companies in HR tech space 
with 50-200 employees, raised Series A or B..."""

result = pipeline.run_pipeline(criteria)
print(f"\n🎯 Pipeline complete for {result['partner']['company']}")
print(f"Next action: {result['outreach']['next_action']}")
print(f"HubSpot deal: {result['crm_deal_id']}")

Level 3: Production Pattern with LangGraph

Good for: 200+ active deals | Setup time: 1 day

# Production Pattern with LangGraph (200+ active deals)
from langgraph.graph import StateGraph, END
from typing import TypedDict, List, Annotated
import operator
import anthropic
import requests
import os

class PipelineState(TypedDict):
    criteria: str
    discovered_partners: Annotated[List[dict], operator.add]
    enriched_partners: Annotated[List[dict], operator.add]
    outreach_plans: dict
    active_deals: dict
    crm_synced: bool
    error_log: Annotated[List[str], operator.add]

def discover_node(state: PipelineState) -> PipelineState:
    """Discover potential partners using AI"""
    client = anthropic.Anthropic(api_key=os.getenv('ANTHROPIC_API_KEY'))
    
    try:
        response = client.messages.create(
            model='claude-3-5-sonnet-20241022',
            max_tokens=2048,
            messages=[{
                'role': 'user',
                'content': f'Find top 10 partners matching: {state["criteria"]}'
            }]
        )
        
        content = response.content[0]
        if content.type != 'text':
            raise ValueError('Invalid response')
        
        partners = anthropic.json.loads(content.text)['top_matches']
        state['discovered_partners'] = partners
        
    except Exception as e:
        state['error_log'].append(f'Discovery failed: {e}')
    
    return state

def enrich_node(state: PipelineState) -> PipelineState:
    """Enrich with Clearbit data"""
    clearbit_key = os.getenv('CLEARBIT_API_KEY')
    enriched = []
    
    for partner in state['discovered_partners'][:5]:  # Top 5 only
        try:
            response = requests.get(
                'https://company.clearbit.com/v2/companies/find',
                params={'name': partner['company']},
                headers={'Authorization': f'Bearer {clearbit_key}'},
                timeout=10
            )
            
            if response.status_code == 200:
                data = response.json()
                partner['enriched'] = {
                    'employees': data.get('metrics', {}).get('employees'),
                    'funding': data.get('metrics', {}).get('raised'),
                    'tech': data.get('tech', []),
                    'domain': data.get('domain')
                }
                enriched.append(partner)
        except Exception as e:
            state['error_log'].append(f'Enrichment failed for {partner["company"]}: {e}')
    
    state['enriched_partners'] = enriched
    return state

def outreach_node(state: PipelineState) -> PipelineState:
    """Generate outreach sequences for enriched partners"""
    client = anthropic.Anthropic(api_key=os.getenv('ANTHROPIC_API_KEY'))
    outreach_plans = {}
    
    for partner in state['enriched_partners'][:3]:  # Top 3 get full sequences
        try:
            response = client.messages.create(
                model='claude-3-5-sonnet-20241022',
                max_tokens=2048,
                messages=[{
                    'role': 'user',
                    'content': f'Create outreach sequence for: {partner}'
                }]
            )
            
            content = response.content[0]
            if content.type != 'text':
                raise ValueError('Invalid response')
            
            outreach_plans[partner['company']] = anthropic.json.loads(content.text)
            
        except Exception as e:
            state['error_log'].append(f'Outreach generation failed: {e}')
    
    state['outreach_plans'] = outreach_plans
    return state

def crm_sync_node(state: PipelineState) -> PipelineState:
    """Sync to HubSpot CRM"""
    hubspot_key = os.getenv('HUBSPOT_API_KEY')
    active_deals = {}
    
    for company, outreach in state['outreach_plans'].items():
        try:
            deal_payload = {
                'properties': {
                    'dealname': f'Partnership - {company}',
                    'dealstage': 'qualifiedtobuy',
                    'pipeline': 'partnerships',
                    'next_action': outreach.get('next_action', '')
                }
            }
            
            response = requests.post(
                'https://api.hubapi.com/crm/v3/objects/deals',
                headers={
                    'Authorization': f'Bearer {hubspot_key}',
                    'Content-Type': 'application/json'
                },
                json=deal_payload,
                timeout=10
            )
            
            if response.status_code == 201:
                deal_id = response.json()['id']
                active_deals[company] = deal_id
                
        except Exception as e:
            state['error_log'].append(f'CRM sync failed for {company}: {e}')
    
    state['active_deals'] = active_deals
    state['crm_synced'] = len(active_deals) > 0
    return state

def should_enrich(state: PipelineState) -> str:
    """Route based on discovery success"""
    if len(state['discovered_partners']) > 0:
        return 'enrich'
    return 'end'

def should_generate_outreach(state: PipelineState) -> str:
    """Route based on enrichment success"""
    if len(state['enriched_partners']) > 0:
        return 'outreach'
    return 'end'

# Build the graph
def build_partnership_graph():
    workflow = StateGraph(PipelineState)
    
    # Add nodes
    workflow.add_node('discover', discover_node)
    workflow.add_node('enrich', enrich_node)
    workflow.add_node('outreach', outreach_node)
    workflow.add_node('crm_sync', crm_sync_node)
    
    # Add edges
    workflow.set_entry_point('discover')
    workflow.add_conditional_edges(
        'discover',
        should_enrich,
        {
            'enrich': 'enrich',
            'end': END
        }
    )
    workflow.add_conditional_edges(
        'enrich',
        should_generate_outreach,
        {
            'outreach': 'outreach',
            'end': END
        }
    )
    workflow.add_edge('outreach', 'crm_sync')
    workflow.add_edge('crm_sync', END)
    
    return workflow.compile()

# Usage
partnership_graph = build_partnership_graph()

initial_state = PipelineState(
    criteria="SaaS companies in HR tech, 50-200 employees, Series A/B",
    discovered_partners=[],
    enriched_partners=[],
    outreach_plans={},
    active_deals={},
    crm_synced=False,
    error_log=[]
)

result = partnership_graph.invoke(initial_state)

print(f"✅ Discovered: {len(result['discovered_partners'])} partners")
print(f"✅ Enriched: {len(result['enriched_partners'])} partners")
print(f"✅ Outreach plans: {len(result['outreach_plans'])}")
print(f"✅ CRM deals: {len(result['active_deals'])}")
print(f"⚠️  Errors: {len(result['error_log'])}")

When to Level Up

1

Start: Simple API Calls

0-50 active deals

  • Sequential API calls, no framework needed
  • Manual CRM entry after generation
  • Basic error logging with console.log
2

Scale: Add Integrations

50-200 active deals

  • Clearbit enrichment for company data
  • Automatic HubSpot deal creation
  • Error handling with retries and logging
  • Slack notifications for high-priority deals
3

Production: Framework & Orchestration

200-500 active deals

  • LangGraph for complex workflows with conditional routing
  • Parallel processing for discovery + enrichment
  • State management across steps (no data loss)
  • Human-in-the-loop for deal review before outreach
4

Enterprise: Multi-Agent System

500+ active deals

  • Specialized agents (discovery, enrichment, outreach, negotiation)
  • Real-time deal health monitoring dashboard
  • Automated follow-up sequences with email/LinkedIn integration
  • Multi-CRM sync (HubSpot, Salesforce, Pipedrive)
  • Revenue forecasting and pipeline analytics

Strategy-Specific Gotchas

The code examples work. But partnership pipeline has unique challenges you need to handle.

Company Data Enrichment Rate Limits

Clearbit and similar APIs have strict rate limits (1 request/second for free tier). If you're enriching 100 companies, that's 100 seconds. Use batch processing and caching to avoid hitting limits.

import time
import redis
import json

class EnrichmentCache:
    def __init__(self):
        self.redis = redis.Redis(host='localhost', port=6379, db=0)
        self.ttl = 86400 * 7  # Cache for 7 days
    
    def get_enriched_data(self, company_name: str) -> dict:
        # Check cache first
        cached = self.redis.get(f'company:{company_name}')
        if cached:
            return json.loads(cached)
        
        # Rate limit: 1 req/sec
        time.sleep(1)
        
        # Fetch from Clearbit
        response = requests.get(
            'https://company.clearbit.com/v2/companies/find',
            params={'name': company_name},
            headers={'Authorization': f'Bearer {os.getenv("CLEARBIT_KEY")}'},
            timeout=10
        )
        
        if response.status_code == 200:
            data = response.json()
            # Cache the result
            self.redis.setex(
                f'company:{company_name}',
                self.ttl,
                json.dumps(data)
            )
            return data
        
        return {}

# Usage: Batch process with caching
cache = EnrichmentCache()
for company in discovered_partners:
    enriched = cache.get_enriched_data(company['name'])
    company['enriched'] = enriched

CRM Duplicate Detection

HubSpot and Salesforce will create duplicate deals if you don't check first. Always search by company domain before creating new deals. Use upsert pattern instead of create.

async function upsertDeal(
  companyName: string,
  dealData: any
): Promise<string> {
  const hubspotKey = process.env.HUBSPOT_API_KEY!;

  // Search for existing deal by company name
  const searchResponse = await fetch(
    'https://api.hubapi.com/crm/v3/objects/deals/search',
    {
      method: 'POST',
      headers: {
        Authorization: `Bearer ${hubspotKey}`,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        filterGroups: [
          {
            filters: [
              {
                propertyName: 'dealname',
                operator: 'CONTAINS_TOKEN',
                value: companyName,
              },
            ],
          },
        ],
      }),
    }
  );

  const searchData = await searchResponse.json();

  if (searchData.results && searchData.results.length > 0) {
    // Update existing deal
    const dealId = searchData.results[0].id;
    await fetch(`https://api.hubapi.com/crm/v3/objects/deals/${dealId}`, {
      method: 'PATCH',
      headers: {
        Authorization: `Bearer ${hubspotKey}`,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({ properties: dealData }),
    });
    return dealId;
  } else {
    // Create new deal
    const createResponse = await fetch(
      'https://api.hubapi.com/crm/v3/objects/deals',
      {
        method: 'POST',
        headers: {
          Authorization: `Bearer ${hubspotKey}`,
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({ properties: dealData }),
      }
    );
    const createData = await createResponse.json();
    return createData.id;
  }
}

Outreach Personalization at Scale

Generic outreach gets ignored. You need to personalize based on recent company news, mutual connections, or specific pain points. Use LinkedIn Sales Navigator API or news APIs to find personalization hooks.

import anthropic
import requests

def get_personalization_hooks(company_name: str, domain: str) -> dict:
    """Find recent news and signals for personalization"""
    
    # Get recent company news (using NewsAPI or similar)
    news_response = requests.get(
        'https://newsapi.org/v2/everything',
        params={
            'q': company_name,
            'sortBy': 'publishedAt',
            'pageSize': 3,
            'apiKey': os.getenv('NEWS_API_KEY')
        },
        timeout=10
    )
    
    recent_news = []
    if news_response.status_code == 200:
        articles = news_response.json().get('articles', [])
        recent_news = [a['title'] for a in articles[:3]]
    
    # Check for job postings (signals growth)
    jobs_response = requests.get(
        f'https://api.lever.co/v0/postings/{domain}',
        timeout=10
    )
    
    open_roles = []
    if jobs_response.status_code == 200:
        jobs = jobs_response.json()
        open_roles = [j['text'] for j in jobs[:5]]
    
    return {
        'recent_news': recent_news,
        'open_roles': open_roles,
        'hiring_signals': len(open_roles) > 0
    }

def generate_personalized_outreach(partner: dict) -> str:
    """Generate outreach with personalization hooks"""
    hooks = get_personalization_hooks(
        partner['company'],
        partner['enriched'].get('domain', '')
    )
    
    client = anthropic.Anthropic(api_key=os.getenv('ANTHROPIC_API_KEY'))
    
    prompt = f"""Create a personalized LinkedIn message for this partnership opportunity.

Target: {partner['company']}
Why good fit: {partner['why_good_fit']}
Recent news: {hooks['recent_news']}
Open roles: {hooks['open_roles']}

Make it:
1. Reference specific recent news or hiring
2. Show you understand their business
3. Clear value prop in 2 sentences
4. Soft ask for 15 min call

Keep under 150 words."""
    
    response = client.messages.create(
        model='claude-3-5-sonnet-20241022',
        max_tokens=512,
        messages=[{'role': 'user', 'content': prompt}]
    )
    
    content = response.content[0]
    if content.type != 'text':
        raise ValueError('Invalid response')
    
    return content.text

Deal Health Scoring Accuracy

AI-generated health scores are guesses without real signals. Track actual engagement: email opens, link clicks, meeting attendance, response time. Feed these signals back to improve scoring.

interface EngagementSignal {
  type: 'email_open' | 'link_click' | 'meeting_booked' | 'response';
  timestamp: Date;
  metadata?: any;
}

function calculateDealHealth(
  baseScore: number,
  signals: EngagementSignal[]
): number {
  let score = baseScore;

  // Recent signals matter more (decay over time)
  const now = new Date();
  const daysSinceLastSignal = signals.length
    ? (now.getTime() - signals[signals.length - 1].timestamp.getTime()) /
      (1000 * 60 * 60 * 24)
    : 999;

  // Penalize stale deals
  if (daysSinceLastSignal > 14) {
    score -= 20;
  } else if (daysSinceLastSignal > 7) {
    score -= 10;
  }

  // Positive signals in last 7 days
  const recentSignals = signals.filter(
    (s) =>
      (now.getTime() - s.timestamp.getTime()) / (1000 * 60 * 60 * 24) <= 7
  );

  recentSignals.forEach((signal) => {
    switch (signal.type) {
      case 'email_open':
        score += 5;
        break;
      case 'link_click':
        score += 10;
        break;
      case 'meeting_booked':
        score += 25;
        break;
      case 'response':
        score += 15;
        break;
    }
  });

  // Cap between 0-100
  return Math.max(0, Math.min(100, score));
}

// Usage: Update health score based on real engagement
const signals: EngagementSignal[] = [
  { type: 'email_open', timestamp: new Date('2025-09-01') },
  { type: 'link_click', timestamp: new Date('2025-09-02') },
  { type: 'meeting_booked', timestamp: new Date('2025-09-03') },
];

const healthScore = calculateDealHealth(75, signals);
console.log(`Updated health score: ${healthScore}`);

Contract Automation with DocuSign

Once a deal is closing, you need to send contracts. Integrate DocuSign API to auto-generate partnership agreements from templates. Pre-fill company details, send for signature, track completion.

import requests
import base64
import os

def send_partnership_contract(
    partner_name: str,
    partner_email: str,
    deal_terms: dict
) -> str:
    """Send partnership agreement via DocuSign"""
    
    docusign_account_id = os.getenv('DOCUSIGN_ACCOUNT_ID')
    docusign_access_token = os.getenv('DOCUSIGN_ACCESS_TOKEN')
    template_id = os.getenv('DOCUSIGN_PARTNERSHIP_TEMPLATE_ID')
    
    # Create envelope from template
    envelope_definition = {
        'templateId': template_id,
        'templateRoles': [
            {
                'email': partner_email,
                'name': partner_name,
                'roleName': 'Partner',
                'tabs': {
                    'textTabs': [
                        {
                            'tabLabel': 'PartnerName',
                            'value': partner_name
                        },
                        {
                            'tabLabel': 'DealValue',
                            'value': deal_terms.get('value', '')
                        },
                        {
                            'tabLabel': 'StartDate',
                            'value': deal_terms.get('start_date', '')
                        }
                    ]
                }
            }
        ],
        'status': 'sent'
    }
    
    response = requests.post(
        f'https://demo.docusign.net/restapi/v2.1/accounts/{docusign_account_id}/envelopes',
        headers={
            'Authorization': f'Bearer {docusign_access_token}',
            'Content-Type': 'application/json'
        },
        json=envelope_definition,
        timeout=10
    )
    
    if response.status_code == 201:
        envelope_id = response.json()['envelopeId']
        print(f'Contract sent! Envelope ID: {envelope_id}')
        return envelope_id
    else:
        raise Exception(f'DocuSign API error: {response.text}')

# Usage: Auto-send contract when deal reaches "Closing" stage
deal_terms = {
    'value': '$250,000 ARR',
    'start_date': '2025-10-01',
    'duration': '12 months'
}

envelope_id = send_partnership_contract(
    'Lattice',
    'partnerships@lattice.com',
    deal_terms
)

Cost Calculator

Manual Process

BD Manager time (partner research)
$150/hour × 10 hours/week
Sales time (outreach + follow-ups)
$120/hour × 15 hours/week
Missed opportunities (slow response)
~3 deals lost/quarter × $200K avg
Total:$6,600/week + $150K/quarter in lost deals
weekly

Limitations:

  • Can't track more than 20-30 active deals
  • 60% of follow-ups forgotten or delayed
  • No data-driven prioritization
  • Spreadsheet chaos across team

Automated Pipeline

Claude API (discovery + outreach)
$0.015/1K tokens × 2M tokens/month
Clearbit enrichment
$99/month for 1,000 lookups
HubSpot CRM (Professional)
$800/month for 5 users
DocuSign API
$40/month for 10 envelopes
Total:$969/month
monthly

Benefits:

  • Track 200+ active deals simultaneously
  • Automated follow-ups (0% missed)
  • Data-driven deal prioritization
  • Full team visibility in CRM
  • Contract automation saves 5 hours/deal
$847/day saved
96% cost reduction | $25,431/month | $305,172/year
💡 Pays for itself in first month with 1 closed deal
🤝

Want This Running in Your Partnership Pipeline?

We build custom partnership automation systems that scale from 10 to 500+ deals. From discovery to contract signing, fully automated.