$npx -y skills add Lonsdale201/wp-agent-skills --skill fluentcrm-automation-sequence-modelsWork with FluentCRM 3.x automation subscriber state and FluentCampaign Pro email sequences. Covers FunnelSubscriber, FunnelSequence, FunnelProcessor, FunnelHelper, FunnelMetric, Pro Sequence, SequenceMail, and SequenceTracker. Use when enrolling a contact into an automation funne
| 1 | # FluentCRM: automation and email sequence models |
| 2 | |
| 3 | Use this skill when code needs to start or inspect FluentCRM automations, or enroll contacts into FluentCampaign Pro email sequences. Keep these two systems separate: `FunnelSequence` is an automation step; `FluentCampaign\App\Models\Sequence` is a Pro email sequence stored in `fc_campaigns`. |
| 4 | |
| 5 | Verification note: local source was FluentCRM core 3.1.8 and FluentCampaign Pro 3.1.8. Core 3.1.8 declares `FLUENTCRM_MIN_PRO_VERSION` as 3.1.8. |
| 6 | |
| 7 | ## When to use this skill |
| 8 | |
| 9 | - Starting an automation funnel for a known contact or event. |
| 10 | - Resuming a funnel from a benchmark sequence point. |
| 11 | - Reading `fc_funnel_subscribers` progress, statuses, next sequence, or source metadata. |
| 12 | - Enrolling or removing contacts from a Pro email sequence. |
| 13 | - Reviewing code that writes directly to `fc_funnel_subscribers`, `fc_funnel_sequences`, or `fc_sequence_tracker`. |
| 14 | |
| 15 | ## Automation data model |
| 16 | |
| 17 | `FluentCrm\App\Models\FunnelSubscriber` maps `fc_funnel_subscribers` and tracks one contact inside one automation funnel. |
| 18 | |
| 19 | Core fillable fields: |
| 20 | |
| 21 | ```php |
| 22 | [ |
| 23 | 'funnel_id', |
| 24 | 'subscriber_id', |
| 25 | 'status', |
| 26 | 'type', |
| 27 | 'next_sequence', |
| 28 | 'next_sequence_id', |
| 29 | 'last_sequence_id', |
| 30 | 'last_sequence_status', |
| 31 | 'last_executed_time', |
| 32 | 'next_execution_time', |
| 33 | 'starting_sequence_id', |
| 34 | 'source_trigger_name', |
| 35 | 'source_ref_id', |
| 36 | 'notes', |
| 37 | ] |
| 38 | ``` |
| 39 | |
| 40 | Common main statuses are `draft`, `pending`, `active`, `waiting`, `completed`, `cancelled`, and `skipped`. `active()` only scopes to `status = active`. Do not confuse the main `status` with `last_sequence_status`; `FunnelHelper::changeFunnelSubSequenceStatus()` writes `last_sequence_status = complete` when a normal step is processed. The benchmark direct-entry path can seed `last_sequence_status = completed` while creating a synthetic starting row; don't treat that as a value to write from action handlers. |
| 41 | |
| 42 | Relations: |
| 43 | |
| 44 | - `funnel()` -> `FluentCrm\App\Models\Funnel` |
| 45 | - `subscriber()` -> `FluentCrm\App\Models\Subscriber` |
| 46 | - `next_sequence_item()` -> `FunnelSequence` |
| 47 | - `last_sequence()` -> `FunnelSequence` |
| 48 | - `metrics()` -> `FunnelMetric` rows for the same contact |
| 49 | |
| 50 | `FluentCrm\App\Models\FunnelSequence` maps `fc_funnel_sequences` and represents a step in the automation builder. Important fields are `funnel_id`, `parent_id`, `action_name`, `condition_type`, `type`, `status`, `conditions`, `settings`, `delay`, `c_delay`, and `sequence`. The model serializes and unserializes `settings` and `conditions`. |
| 51 | |
| 52 | ## Start a funnel safely |
| 53 | |
| 54 | Do not directly insert `FunnelSubscriber` rows from a companion plugin. Use `FunnelProcessor::startFunnelSequence()`, which creates or finds the contact, handles pending/double-opt-in state, checks duplicates, creates the funnel subscriber, records the start hook, and processes immediate steps. |
| 55 | |
| 56 | ```php |
| 57 | use FluentCrm\App\Models\Funnel; |
| 58 | use FluentCrm\App\Services\Funnel\FunnelProcessor; |
| 59 | |
| 60 | if (!function_exists('FluentCrmApi')) { |
| 61 | return; |
| 62 | } |
| 63 | |
| 64 | $contact = FluentCrmApi('contacts')->getContactByUserRef($userId); |
| 65 | $funnel = Funnel::where('id', (int) $funnelId) |
| 66 | ->where('status', 'published') |
| 67 | ->where('type', 'funnels') |
| 68 | ->first(); |
| 69 | |
| 70 | if ($contact && $funnel) { |
| 71 | (new FunnelProcessor())->startFunnelSequence($funnel, [], [ |
| 72 | 'source_trigger_name' => 'my_plugin_event', |
| 73 | 'source_ref_id' => (int) $eventId, |
| 74 | ], $contact); |
| 75 | } |
| 76 | ``` |
| 77 | |
| 78 | When you do not yet have a contact, pass subscriber data as the second argument: |
| 79 | |
| 80 | ```php |
| 81 | (new FunnelProcessor())->startFunnelSequence($funnel, [ |
| 82 | 'email' => sanitize_email($email), |
| 83 | 'first_name' => sanitize_text_field($firstName), |
| 84 | 'status' => 'subscribed', |
| 85 | ], [ |
| 86 | 'source_trigger_name' => 'my_plugin_event', |
| 87 | 'source_ref_id' => (int) $eventId, |
| 88 | ]); |
| 89 | ``` |
| 90 | |
| 91 | Core manual attach uses the same pattern in `SubscriberController`: it filters contacts not already in the funnel and starts them with `source_trigger_name => fcrm_manual_attach`. |
| 92 | |
| 93 | ## Duplicate and status guards |