Petunia Sequence System: Current Capabilities
Last Updated: December 2025 Status: Production Test Coverage: Verified by
/tests/lib/autoresponder/sequence-execution.test.ts
This document provides a comprehensive overview of what Petunia's sequence and automation system can and cannot do. Understanding these capabilities helps you design effective follow-up campaigns within current constraints.
Architecture Overview
The sequence system consists of:
- Production System (
/lib/autoresponder/productionSystem.ts) - Core touchpoint and sequence management - Sequences API (
/app/api/autoresponder/sequences/route.ts) - CRUD operations for sequences - Rules Engine (
/lib/automation/rules-engine.ts) - Business automation triggers
Important: The current implementation is CRUD-focused - sequences are stored and managed but execution relies on external scheduling mechanisms.
What Sequences CAN Do
Message Sending
- Send emails at scheduled times after sequence enrollment
- Send SMS messages at scheduled times after sequence enrollment
- Add delays between steps (minutes, hours, days)
- Use merge fields for basic personalization (name, company name, etc.)
Lifecycle Management
- Activate/deactivate sequences without losing enrolled contacts
- Pause sequences for individual contacts
- Resume sequences for paused contacts
- Remove contacts from sequences
Enrollment
- Manually enroll contacts via UI or API
- Auto-enroll new leads based on source (e.g., all Yelp leads)
- Bulk enroll multiple contacts at once
Tracking
- Track delivery status of messages
- Track opens for emails (with tracking pixel)
- Track link clicks within messages
- View sequence analytics (enrolled, completed, responded)
What Sequences CANNOT Do (Yet)
No Branching Logic
Cannot: "If lead is hot, send email A; if cold, send email B"
Current Limitation:
Sequences are LINEAR - every enrolled contact receives the same steps in the same order.
Workaround:
Create separate sequences for each scenario:
- "Hot Leads - Aggressive Follow-up"
- "Cold Leads - Nurture Campaign"
Manually assign contacts to appropriate sequence.
No Conditional Execution
Cannot: "Skip this step if previous email was opened" Cannot: "Only send if lead score > 50" Cannot: "Exit sequence if contact books appointment"
Current Limitation:
Steps execute regardless of previous interactions or external conditions.
Workaround:
- Monitor sequence analytics manually
- Remove contacts who shouldn't continue
- Use autoresponder for immediate response handling
No Dynamic Content
Cannot: Pull real-time data into message templates Cannot: Show different content based on contact attributes Cannot: Include dynamic pricing or availability
Current Limitation:
Message content is static at send time (only merge fields work).
Workaround:
- Use standard merge fields ({{firstName}}, {{companyName}})
- Create multiple sequences with different static content
- Update sequences regularly with current information
No A/B Testing
Cannot: Test different message variants automatically Cannot: Send winner to remaining contacts Cannot: Track variant performance automatically
Current Limitation:
No built-in A/B testing or experimentation framework.
Workaround:
- Create duplicate sequences with different content
- Manually split contacts between sequences
- Compare analytics manually
No External Triggers
Cannot: Start sequence when webhook fires Cannot: Trigger on calendar events Cannot: Start based on website behavior Cannot: Trigger from third-party app events
Current Limitation:
Sequences must be started via UI, API call, or auto-enroll rules.
Workaround:
- Use API to enroll contacts programmatically
- Build automation with external tools (Zapier, Make) that call our API
No Time-Zone Awareness
Cannot: Send at 9am in contact's local timezone Cannot: Respect contact's timezone for scheduling
Current Limitation:
All times are based on portal timezone, not contact timezone.
Workaround:
- Create timezone-specific sequences for major regions
- Schedule for times that work across timezones (e.g., 11am EST = 8am PST)
No Reply Detection Branching
Cannot: Automatically change path based on reply content Cannot: Route to different steps based on sentiment
Current Limitation:
Sequences pause on ANY reply, but don't analyze reply content.
Workaround:
- Sequence pauses when contact replies (good for human follow-up)
- Use autoresponder for immediate reply handling
- Manually review replies and take action
Common Use Cases & Solutions
Use Case: Follow up with new leads
Solution: Create a 5-step email sequence with increasing urgency
Step 1: Day 0 - Welcome email
Step 2: Day 2 - Value proposition
Step 3: Day 5 - Case study / social proof
Step 4: Day 8 - Direct call-to-action
Step 5: Day 14 - Final follow-up
Use Case: Different follow-up for different sources
Solution: Create source-specific sequences and use auto-enroll rules
Sequence: "Yelp Lead Nurture"
- Auto-enroll: source = "yelp"
- Messaging: Reference Yelp specifically
Sequence: "Google Lead Nurture"
- Auto-enroll: source = "google"
- Messaging: Reference Google specifically
Use Case: Re-engage cold leads
Solution: Create a re-engagement sequence, manually enroll cold leads
Sequence: "Re-engagement Campaign"
- Step 1: "We haven't heard from you..."
- Step 2: Special offer or incentive
- Step 3: Final "last chance" message
Manually filter leads by:
- No activity in 30+ days
- Not currently in any sequence
- Bulk enroll into re-engagement
Performance Considerations
Sending Limits
- Email: Subject to email provider limits (typically 500-1000/day for new senders)
- SMS: Subject to carrier limits and opt-out rules
- Recommendation: Ramp up sending volume gradually
Step Delays
- Minimum delay: 1 minute between steps
- Maximum contacts per step: No hard limit, but large batches are queued
- Delivery time: May vary by 1-5 minutes from scheduled time
- Delay precision: Tests verify delays are stored accurately in milliseconds
Database Impact
- Large sequences (1000+ contacts) may have slight delay in analytics updates
- Bulk enrollment processes in batches to avoid performance issues
Non-Persistent Scheduling
- Current implementation: Uses
setTimeoutfor delays (not persistent across restarts) - Implication: Server restart may affect pending scheduled messages
- Workaround: Critical sequences should use shorter delays or external scheduling
Technical Implementation Notes
Sequence Data Structure
interface Sequence {
id: string;
name: string;
description?: string;
steps: SequenceStep[];
isActive: boolean;
touchpoints?: Touchpoint[];
}
interface SequenceStep {
id: string;
type: 'email' | 'sms' | 'touchpoint';
content: unknown;
delay?: number; // milliseconds from previous step
condition?: unknown; // Stored but NOT evaluated
}
Touchpoint Data Structure
interface Touchpoint {
id: string;
name: string;
channel: 'sms' | 'email' | 'voice' | 'yelp' | 'facebook' | 'google' | 'whatsapp' | 'webchat';
conditions: TouchpointCondition[];
message: string;
isActive: boolean;
triggerDelay?: number;
triggerType?: 'event' | 'time';
scheduledTime?: string; // Portal timezone only
}
Test-Verified Behaviors
The following behaviors are verified by automated tests:
- Business Isolation: Sequences are isolated per business -
businessIdis required for all operations - Step Order Preservation: Steps maintain their order as defined
- Delay Accuracy: Step delays are stored and retrieved accurately
- Activation Toggle: Sequences can be activated/deactivated without data loss
- Touchpoint References: Sequences can reference touchpoints by ID
Autoresponder Integration
The autoresponder system (/lib/services/autoResponderService.ts) handles:
- Channel detection: Determines which platform the message came from
- Trigger evaluation: Checks business hours, channel enablement
- Response generation: AI or template-based responses
Test coverage: /tests/lib/autoresponder/trigger-evaluation.test.ts verifies:
- Channel enablement checks (per-channel toggle)
- Business hours validation (timezone-aware)
- Response type selection (AI vs template)
- Trigger priority ordering
Roadmap
These features are planned for future releases:
| Feature | Target | Status |
|---|---|---|
| Conditional branching | Q1 2026 | Planning |
| Lead score triggers | Q1 2026 | Planning |
| A/B testing | Q2 2026 | Backlog |
| Webhook triggers | Q2 2026 | Backlog |
| Time-zone awareness | Q2 2026 | Backlog |
| Dynamic content | Q3 2026 | Backlog |
FAQ
Q: Can I stop a sequence for all contacts at once? A: Yes, deactivating a sequence pauses it for all contacts. Reactivating resumes where they left off.
Q: What happens if a contact is in multiple sequences? A: They receive messages from all sequences. Be careful about over-messaging.
Q: Can I edit a sequence while contacts are enrolled? A: Yes, but changes only affect future step executions, not already-sent messages.
Q: How do I know if a sequence is working? A: Check sequence analytics for delivery rates, open rates, and reply rates.
API Usage Examples
Creating a Sequence via API
// POST /api/autoresponder/sequences
const response = await fetch('/api/autoresponder/sequences', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
businessId: 'your-business-id',
name: 'Welcome Sequence',
description: 'Automated follow-up for new leads',
steps: [
{ id: 'step-1', type: 'email', content: { subject: 'Welcome!', body: 'Hello {{firstName}}!' }, delay: 0 },
{ id: 'step-2', type: 'sms', content: { message: 'Following up on my email...' }, delay: 86400000 }, // 1 day
],
isActive: true,
}),
});
Calculating Cumulative Timing
// When displaying sequence timeline, calculate cumulative delays
function calculateSequenceTimeline(steps: SequenceStep[]): StepTiming[] {
let cumulativeTime = 0;
return steps.map(step => {
cumulativeTime += step.delay || 0;
return {
stepId: step.id,
executeAtMs: cumulativeTime,
executeAtHuman: formatDuration(cumulativeTime),
};
});
}
// Example output:
// Step 1: Immediately (0ms)
// Step 2: After 1 day (86400000ms)
// Step 3: After 3 days total (259200000ms)
Checking Trigger Eligibility
// Evaluate if a message should trigger autoresponder
function shouldTriggerAutoresponder(settings: AutoresponderSettings, message: IncomingMessage): boolean {
// 1. Check global enablement
if (!settings.enabled) return false;
// 2. Check channel enablement
if (!settings.channels[message.channel]) return false;
// 3. Check business hours (if enabled)
if (settings.businessHours.enabled) {
if (!isWithinBusinessHours(settings, message.timestamp)) return false;
}
return true;
}
Workaround Patterns
Pattern 1: Simulating Conditional Branching
Since sequences don't support branching, use multiple sequences with manual assignment:
Setup:
1. Create "Hot Leads - Fast Track" sequence (aggressive, 3-day cycle)
2. Create "Cold Leads - Nurture" sequence (gentle, 14-day cycle)
3. Create lead scoring criteria
Workflow:
1. New lead arrives
2. Score lead based on criteria (engagement, source, etc.)
3. Assign to appropriate sequence via API
4. Monitor and re-assign if engagement changes
Pattern 2: Simulating Exit Conditions
Since sequences don't auto-exit on events, use monitoring + API:
Setup:
1. Create sequence with all steps
2. Set up webhook for "appointment booked" event
Workflow:
1. Contact enrolled in sequence
2. Contact books appointment → webhook fires
3. Webhook handler calls API to remove contact from sequence
4. Contact receives no more sequence messages
Pattern 3: Time-Zone-Aware Sending
Since all times are portal timezone, use time slots that work across zones:
Strategy: "Safe Windows"
- 11:00 AM EST = 8:00 AM PST = 4:00 PM GMT
- Covers US business hours reasonably
- Add regional sequences for international audiences
Alternative: API-Based Scheduling
- Store contact timezone in metadata
- Use external scheduler (e.g., Cloud Functions)
- Call sequence API at appropriate local time
Pattern 4: Handling Server Restarts
Since delays use setTimeout, scheduled messages may be lost on restart:
Mitigation Strategies:
1. Use shorter delays (hours vs days) for critical messages
2. Implement a "catch-up" cron job that checks for overdue steps
3. Store scheduled execution times in database
4. On startup, re-schedule any pending messages
Example Catch-Up Query:
SELECT * FROM sequence_enrollments
WHERE next_step_at < NOW()
AND status = 'active';
Support
For questions about sequences or to request features:
- Email: support@petunia.ai
- In-app: Use the help widget
- Documentation: This file +
/features/12-autoresponder-sequences.md
Related Test Files
/tests/lib/autoresponder/sequence-execution.test.ts- Sequence CRUD, timing, validation/tests/lib/autoresponder/trigger-evaluation.test.ts- Trigger evaluation, channel checks/tests/lib/automation/rules-engine.test.ts- Business automation rules/tests/integration/flows/07-autoresponder-trigger-execute.test.ts- Integration tests