Version: 2.4
Last Updated: July 29, 2025
The Attack-a-Crack CRM is a comprehensive platform designed to manage every aspect of the business from lead generation to final payment. This roadmap outlines a phased development approach that prioritizes stability and foundational work before building advanced features.
- Production-Ready Architecture: Fully containerized with Docker/Docker Compose, Gunicorn, PostgreSQL
- Background Task Processing: Migrated from APScheduler to production-grade Celery/Redis stack
- Secure Webhooks: OpenPhone webhook endpoint with signature verification
- Real-time Updates: Live polling for new text messages on dashboard
- Centralized Database: SQLAlchemy models with comprehensive relationships
- Enhanced Database Models: Added support for all OpenPhone data types (media, recordings, AI summaries)
- Large Scale Import: Built robust import system handling 7000+ conversations with automatic resume
- Historical Data Import: Currently importing 7000+ conversations from OpenPhone (67% complete as of July 29)
- Contact Enrichment Data: Prepared CSV consolidation of 6,110 unique contacts ready for import
This roadmap is organized into distinct phases where each phase is a prerequisite for the next. This approach minimizes technical debt and prevents regressions by front-loading foundational work.
Goal: Re-establish the database as the absolute "single source of truth" with a schema that captures every available data point and supports future feature requirements.
Priority: CRITICAL
Estimated Effort: 2-3 days
The system uses a single Activity model to store all communication types (messages, calls, voicemails) along with their AI-generated enhancements (summaries, transcripts). This design enables:
- Simplified queries for the unified conversation view
- Consistent handling of all communication types
- Easy addition of new activity types
- Reduced database complexity
User Model (New)
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
openphone_user_id = db.Column(db.String(100), unique=True)
first_name = db.Column(db.String(50))
last_name = db.Column(db.String(50))
email = db.Column(db.String(120))PhoneNumber Model (New)
class PhoneNumber(db.Model):
id = db.Column(db.Integer, primary_key=True)
openphone_id = db.Column(db.String(100), unique=True)
phone_number = db.Column(db.String(20), unique=True)
name = db.Column(db.String(100), nullable=True)
is_active = db.Column(db.Boolean, default=True)Activity Model (Expanded)
class Activity(db.Model):
# Core fields
id = db.Column(db.Integer, primary_key=True)
openphone_id = db.Column(db.String(100), unique=True)
conversation_id = db.Column(db.Integer, db.ForeignKey('conversation.id'))
contact_id = db.Column(db.Integer, db.ForeignKey('contact.id'), nullable=True)
# Activity details
activity_type = db.Column(db.String(20)) # 'call', 'message', 'voicemail'
direction = db.Column(db.String(10)) # 'incoming', 'outgoing'
status = db.Column(db.String(50)) # 'answered', 'missed', 'delivered', 'completed', etc.
# Participants
from_number = db.Column(db.String(20), nullable=True)
to_numbers = db.Column(db.JSON, nullable=True) # Array for multiple recipients
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=True)
phone_number_id = db.Column(db.String(100), nullable=True) # OpenPhone number used
# Message content
body = db.Column(db.Text, nullable=True)
media_urls = db.Column(db.JSON, nullable=True) # Array of media attachment URLs
# Call-specific fields
duration_seconds = db.Column(db.Integer, nullable=True)
recording_url = db.Column(db.String(500), nullable=True)
voicemail_url = db.Column(db.String(500), nullable=True)
answered_at = db.Column(db.DateTime, nullable=True)
answered_by = db.Column(db.String(100), nullable=True) # User ID
completed_at = db.Column(db.DateTime, nullable=True)
initiated_by = db.Column(db.String(100), nullable=True) # User ID
forwarded_from = db.Column(db.String(100), nullable=True)
forwarded_to = db.Column(db.String(100), nullable=True)
# AI-generated content (stored in same model for unified view)
ai_summary = db.Column(db.Text, nullable=True) # Call summary
ai_next_steps = db.Column(db.Text, nullable=True) # Recommended actions
ai_transcript = db.Column(db.JSON, nullable=True) # Call transcript dialogue
ai_content_status = db.Column(db.String(50), nullable=True) # 'pending', 'completed', 'failed'
# Timestamps
created_at = db.Column(db.DateTime)
updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)Conversation Model (Enhanced)
class Conversation(db.Model):
id = db.Column(db.Integer, primary_key=True)
openphone_id = db.Column(db.String(100), unique=True, nullable=True)
contact_id = db.Column(db.Integer, db.ForeignKey('contact.id'), nullable=False)
# Conversation details
name = db.Column(db.String(200), nullable=True) # Display name
participants = db.Column(db.String(500), nullable=True) # Comma-separated phone numbers
phone_number_id = db.Column(db.String(100), nullable=True) # Associated OpenPhone number
# Activity tracking
last_activity_at = db.Column(db.DateTime, default=datetime.utcnow)
last_activity_type = db.Column(db.String(20), nullable=True) # 'message' or 'call'
last_activity_id = db.Column(db.String(100), nullable=True) # OpenPhone activity ID
activities = db.relationship('Activity', backref='conversation', lazy=True, cascade="all, delete-orphan")WebhookEvent Model (New - for reliability)
class WebhookEvent(db.Model):
id = db.Column(db.Integer, primary_key=True)
event_id = db.Column(db.String(100), unique=True) # OpenPhone event ID
event_type = db.Column(db.String(50)) # 'message.new', 'call.completed', etc.
api_version = db.Column(db.String(10)) # 'v1', 'v2', 'v4'
payload = db.Column(db.JSON) # Full webhook payload for reprocessing
processed = db.Column(db.Boolean, default=False)
processed_at = db.Column(db.DateTime, nullable=True)
error_message = db.Column(db.Text, nullable=True)
created_at = db.Column(db.DateTime, default=datetime.utcnow)Campaign Model (Enhanced)
class Campaign(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(200))
created_at = db.Column(db.DateTime, default=datetime.utcnow)
status = db.Column(db.String(20), default='draft') # 'draft', 'running', 'paused', 'complete'
template_a = db.Column(db.Text) # A/B test variant A
template_b = db.Column(db.Text, nullable=True) # A/B test variant B
quiet_hours_start = db.Column(db.Time, default=time(20, 0)) # 8 PM
quiet_hours_end = db.Column(db.Time, default=time(9, 0)) # 9 AM
on_existing_contact = db.Column(db.String(50), default='ignore') # 'ignore', 'flag_for_review', 'adapt_script'CampaignMembership Model (Enhanced)
class CampaignMembership(db.Model):
id = db.Column(db.Integer, primary_key=True)
contact_id = db.Column(db.Integer, db.ForeignKey('contact.id'))
campaign_id = db.Column(db.Integer, db.ForeignKey('campaign.id'))
status = db.Column(db.String(50), default='pending') # 'pending', 'sent', 'failed', 'replied_positive', 'replied_negative', 'suppressed'
variant_sent = db.Column(db.String(1), nullable=True) # 'A' or 'B'
sent_at = db.Column(db.DateTime, nullable=True)
reply_activity_id = db.Column(db.Integer, db.ForeignKey('activity.id'), nullable=True)Priority: CRITICAL
Estimated Effort: 3-4 days
Data Sources from OpenPhone API:
-
Phone Numbers (
/v1/phone-numbers)- Fetch all active phone numbers
- Store OpenPhone IDs for relationship mapping
-
Conversations (
/v1/conversations)- Paginated fetch with
pageToken - Extract participants, last activity info
- Link to contacts by phone number matching
- Paginated fetch with
-
Messages (
/v1/messages)- Fetch all historical messages
- Handle multiple recipients (group messages)
- Download and store media URLs
- Map to conversations and contacts
-
Calls (
/v1/calls)- Import complete call history
- Capture all metadata (duration, participants, forwarding)
- Store recording URLs
-
Call Enhancements (Business plan only)
- Call Summaries (
/v1/call-summaries/{callId}) - Call Transcripts (
/v1/call-transcripts/{callId}) - Store in Activity model's AI fields
- Call Summaries (
-
Users (from webhook events or API if available)
- Extract user IDs from call/message data
- Build user profiles from available data
Import Strategy:
# Pseudocode for import process
def import_openphone_data():
# 1. Import phone numbers first (needed for relationships)
import_phone_numbers()
# 2. Import conversations (creates conversation records)
import_conversations()
# 3. Import messages and calls (creates activity records)
import_messages() # Uses pagination
import_calls() # Uses pagination
# 4. Enhance calls with AI content (if available)
enhance_calls_with_ai()
# 5. Link activities to contacts
match_activities_to_contacts()
# 6. Update conversation last_activity fields
update_conversation_metadata()Error Handling & Resume Capability:
- Track import progress in database
- Store last successful page token
- Implement retry logic with exponential backoff
- Log all API errors for review
- Support resuming interrupted imports
Technical Considerations:
- Authentication: Use API key in
Authorizationheader (not Bearer token) - Rate Limiting: Respect OpenPhone's rate limits with throttling
- Webhook Versions: Handle different API versions (v1, v2, v4) in webhook events
- Data Integrity: Store webhook event IDs to prevent duplicate processing
- Media Storage: Decide whether to store media URLs only or download files locally
Goal: Enrich contact data from multiple sources to overcome OpenPhone API limitations and ensure high-quality contact information for campaigns.
Priority: CRITICAL
Estimated Effort: 3-4 days
OpenPhone's API doesn't provide contact details (names, emails, etc.) for contacts created via web app. We have 6,110 unique contacts but 4,818 (79%) are missing critical information.
1. CSV Import & Merge
- Import consolidated CSV with 6,110 deduplicated contacts
- Match by normalized phone number (+1 format)
- Merge fields: first_name, last_name, email, company, address
- Handle conflicts with source tracking
2. QuickBooks Customer Integration
- Connect to QuickBooks API
- Match customers by phone number
- Import: company name, billing address, payment history
- Flag as "verified customer" for campaign rules
3. Gemini AI Conversation Analysis
- Process conversations for contacts missing data
- Extract: names, addresses, emails, business context
- Confidence scoring for extracted data
- Human review queue for low-confidence extractions
4. Office Number Management
- Flag 23 identified office numbers
- Prevent mass campaign sends to these numbers
- Allow manual override with warning
class ContactEnrichmentService:
def enrich_from_csv(self, csv_path):
# Import and match contacts
def enrich_from_quickbooks(self):
# API integration for customer data
def enrich_from_conversations(self, contact_id):
# Gemini AI analysis of message history
def flag_office_numbers(self, office_numbers):
# Mark contacts to prevent mass sendsGoal: Build primary features for daily operations and growth, leveraging the rich, reliable dataset.
Priority: HIGH
Estimated Effort: 4-5 days
- Complete Timeline: Chronological view of every interaction (calls, messages, emails)
- Visual Distinction: Different icons and colors based on activity type and status
- Media Handling: Clickable thumbnails for MMS attachments with full-size view
- Call Integration: Embedded audio players for recordings, expandable transcripts
- Email Integration: Seamless email threads woven into timeline
- Redesign
contact_detail.htmltemplate - Fetch all Activity records sorted chronologically
- Implement media viewer component
- Add audio player for call recordings
- Integrate Gmail API for email display
Priority: CRITICAL
Estimated Effort: 5 days (aggressive timeline)
1. Blast Campaigns (Primary focus)
- One-time sends to selected contacts
- Cold outreach limited to 125/day per number
- Customer announcements with no limits
- Smart duplicate detection and script modification
2. Automated Campaigns (Event-triggered)
- Appointment reminders (T-1 day at 9am)
- Post-appointment follow-ups (T+2 days)
- QuickBooks event triggers (invoice paid, etc.)
- Google Calendar integration
3. A/B Test Campaigns
- Automatic winner detection with statistical significance
- Progressive traffic shifting to winning variant
- Min sample size: 100 per variant
- Auto-optimization after p<0.05
Smart Sending Logic:
- Pre-send validation (opt-outs, office numbers, recent contacts)
- Business hours only: Weekdays 9am-6pm ET
- Daily limit enforcement: 125 texts/day for cold outreach
- Duplicate handling:
- <2 months: Modify script slightly
- Recent: Flag for manual review
- Office numbers: Auto-skip
Compliance & Safety:
- Universal opt-out database (SMS + Email)
- STOP/UNSUBSCRIBE keyword detection
- Permanent do-not-contact flagging
- Reason tracking for all opt-outs
Campaign Analytics:
- Real-time response tracking
- Response sentiment analysis (positive/negative/neutral)
- A/B test performance metrics
- Time-of-day response patterns
- Conversion tracking
class Campaign(db.Model):
type = db.Column(db.String(20)) # 'blast', 'automated', 'ab_test'
audience_type = db.Column(db.String(20)) # 'cold', 'customer', 'mixed'
daily_limit = db.Column(db.Integer, default=125)
schedule_window = db.Column(db.JSON) # {days: [1-5], hours: [9,18], tz: 'US/Eastern'}
ab_config = db.Column(db.JSON) # {variants: [], threshold: 0.95, min_sample: 100}
class ContactFlag(db.Model):
contact_id = db.Column(db.Integer, db.ForeignKey('contact.id'))
flag_type = db.Column(db.String(50)) # 'opted_out', 'office_number', 'recently_texted'
flag_reason = db.Column(db.Text)
applies_to = db.Column(db.String(20)) # 'sms', 'email', 'both'
class CampaignRecipient(db.Model):
pre_send_flags = db.Column(db.JSON) # ['already_texted', 'opted_out']
override_action = db.Column(db.String(20)) # 'skip', 'modify_script', 'flag_review'
response_sentiment = db.Column(db.String(20)) # 'positive', 'negative', 'neutral'- {first_name} - With intelligent fallbacks
- {company} - For B2B outreach
- {neighborhood} - Location-based targeting
- {last_appointment} - For follow-ups
- Future: {property_year_built}, etc.
- Day 1-2: Core models, flagging system, daily limits
- Day 2-3: Campaign builder UI, recipient validation
- Day 3-4: A/B testing engine, sending queue
- Day 4-5: Analytics dashboard, response tracking
- Day 5: Testing, polish, documentation
Priority: HIGH
Estimated Effort: 3-4 days
- Metrics Cards: Total contacts, active campaigns, response rates, revenue
- Activity Timeline: Recent messages, calls, campaign events
- Quick Actions: New campaign, send message, view reports
- Real-time Charts: Message volume, response patterns, campaign performance
- Advanced Search: Filter by name, phone, tags, date range, campaign
- Bulk Operations: Select multiple → tag, assign, export
- Visual Indicators:
- 🔵 Unread messages
- 📎 Has attachments
- 🤖 AI summary available
- 🏢 Office number warning
- Infinite Scroll: Virtualized list for 7000+ conversations
- Split Layout: Contact sidebar + conversation timeline
- Rich Media Display:
- Inline image viewer with lightbox
- Audio player for recordings/voicemails
- Document preview for PDFs
- AI Content Showcase:
- Prominent call summaries
- Expandable transcripts
- Suggested next actions
- Quick Actions Bar:
- Call contact
- Add to campaign
- Schedule appointment
- Add note/tag
- Activity Tabs:
- Messages
- Calls
- Emails (future)
- Notes
- Campaign History
Goal: Provide real-time business overview and replace other financial software.
Estimated Effort: 5-6 days
- Quote Created in CRM → Push to QuickBooks
- Quote Sent from QuickBooks → Trigger SMS follow-up
- Quote Approved → Trigger scheduling algorithm
- Morning of Job → Auto-convert Quote to Invoice
- Invoice Paid → Update CRM status via webhook
Estimated Effort: 4-5 days
- Key metrics dashboard with callback tracking
- Revenue charts and trend analysis
- Monthly PDF report generation
- Bank statement import with AI categorization
- Profitability analysis and spending suggestions
Goal: Leverage cutting-edge AI for operational efficiency and ensure 100% reliable communication history.
Estimated Effort: 3-4 days
- Real-time Webhooks: Instant updates for critical events
- Nightly Reconciliation: Ensure data integrity
- System Health Monitoring: UI status indicator for data sync health
- Automatic Recovery: Handle missed events gracefully
Estimated Effort: 4-5 days
- Automatic Classification: Customer inquiry, vendor, bill, calendar, marketing/spam
- Smart Actions: Auto-archive, label, or flag for attention
- Background Processing: Celery task checks Gmail every few minutes
- Integration: Results visible in Unified Inbox
Estimated Effort: 5-6 days
- Automatic Trigger: Analysis on media message receipt
- Rich Context Prompting: Include property data and conversation history
- Structured Output: JSON with damage type, confidence, recommendations
- Confidence-Based Workflow:
- High: Send quote directly
- Medium: Create draft for review
- Low: Ask clarifying questions
Estimated Effort: 7-8 days
- Multi-Technician Support: Individual base locations and schedules
- Smart Duration Estimation: Based on job type and quote value
- Geographic Clustering: Minimize travel time week-by-week
- Customer Self-Scheduling: One-click scheduling links
Estimated Effort: 3-4 days
- Patchwork Strategy: Define source of truth for different data types
- Upsert Logic: Enrich existing contacts without overwriting
- Conflict Resolution: Flag conflicts for manual review
- Data Source Tracking: Track where each piece of data originated
- Target: >90% code coverage
- Tools: pytest, pytest-cov
- CI/CD: GitHub Actions for automated testing
- Authentication: Full user login system
- Webhook Security: Signature verification for all endpoints
- Input Validation: Systematic review of all forms and APIs
- Framework Migration: React or Vue for complex interfaces
- Phased Approach: Start with Marketing and Scheduling dashboards
- ✅ Database Schema Enhancement (Task 1.1) - COMPLETE
- 🚧 Historical Data Import (Task 1.2) - 69% COMPLETE (4,857/7,000 conversations)
- 📋 Contact Enrichment (Task 1A.1) - DATA PREPARED
- 🚨 Text Campaign System (Task 2.2) - DUE FRIDAY
- 📱 Contact CSV Import (Task 1A.1) - After import completes
- 🎨 UI/UX Enhancements (Task 2.3) - Dashboard & Conversations
- 👁️ Unified Conversation View (Task 2.1) - Enhanced display
- Enhanced Quote/Invoice Lifecycle (Task 3.1)
- Financial Dashboard (Task 3.2)
- Resilient Data Sync (Task 4.1)
- AI Email Triage (Task 4.2)
- AI Image Analysis (Task 4.3)
- Intelligent Scheduling (Task 5.1)
- Multi-Source Importers (Task 5.2)
- All models created and migrated successfully
- Historical data import completed with 100% accuracy
- Database serves as single source of truth
- Unified conversation view reduces context switching
- Marketing campaigns generate measurable ROI
- Campaign analytics provide actionable insights
- Quote-to-invoice workflow fully automated
- Financial dashboard provides real-time business health
- Monthly reporting automated
- Data sync reliability reaches 99.9%
- Email triage reduces manual email processing by 80%
- AI image analysis speeds up quoting process by 50%
- Database Migration Failures: Comprehensive backup and rollback procedures
- API Rate Limits: Implement exponential backoff and request queuing
- Data Loss: Multiple backup strategies and integrity checks
- Feature Complexity: MVP approach with iterative improvements
- User Adoption: Extensive documentation and training materials
- Performance Issues: Load testing and monitoring implementation
- Each phase should be developed in feature branches
- Database migrations must be thoroughly tested
- All new features require corresponding tests
- API integrations need comprehensive error handling
- Update this roadmap as features are completed
- Maintain technical documentation for each major feature
- Create user guides for new functionality
- Document all API integrations and webhook handlers
This roadmap is a living document and should be updated as development progresses and business requirements evolve.