Skip to content

FluentCRM — Trigger Campaign Send via CLI

Created 2026-04-29
Status planned
Tags plansfluentcrmcliautomation

On 2026-04-29, a campaign draft (ID 440) was successfully created via WP-CLI + PHP eval on crm.baseworks.com. Creating and updating campaigns via CLI works perfectly. The missing piece is triggering the send programmatically so the full workflow (draft creation → recipient queuing → dispatch) can run without opening the FluentCRM UI.

  • Creating a campaign record (all fields: subject, preheader, body, sender, template, settings) via wp eval-file — confirmed
  • Updating any field on an existing campaign (body, sender name, status) — confirmed
  • Querying subscribers by tag to verify recipient count — confirmed
  • Creating individual CampaignEmail rows directly via CampaignEmail::create() — confirmed

FluentCRM’s Campaign::subscribeBySegment() finds subscribers correctly (8 contacts returned) but queues 0 emails. Root cause: the method passes an Eloquent query builder to subscribe() with $isModel = true, and iterating that builder after count() has already been called on it produces no results in the wp eval context. This is an internal method designed to run inside an HTTP request (FluentCRM REST API), not a CLI process.

do_action('fluentcrm_campaign_status_active', $campaign) has no registered listeners in this plugin version — it’s a hook for external plugins and does not change campaign status on its own.

Bypass subscribeBySegment() entirely. The equivalent logic is:

  1. Query all subscribers with tag 98 (or the relevant tag) directly:
    $subscribers = FluentCrm\App\Models\Subscriber::filterByTags([98])
    ->where('status', 'subscribed')->get();
  2. Loop through subscribers and insert a CampaignEmail row per subscriber (proven to work):
    foreach ($subscribers as $sub) {
    FluentCrm\App\Models\CampaignEmail::create([
    'campaign_id' => $campaign_id,
    'status' => 'scheduled',
    'subscriber_id' => $sub->id,
    'email_address' => $sub->email,
    'email_subject' => $campaign->email_subject,
    'email_body' => $campaign->email_body,
    'email_headers' => $mail_headers,
    'email_hash' => FluentCrm\App\Services\Helper::generateEmailHash($inserted->id),
    'created_at' => fluentCrmTimestamp(),
    'updated_at' => fluentCrmTimestamp(),
    ]);
    }
  3. Update recipients_count on the campaign.
  4. Set campaign status to 'working'.
  5. Trigger the mailer handler: do_action('fluentcrm_process_campaign_emails') — this fires the Scheduler, which picks up all working campaigns and dispatches queued emails via FluentCRM’s mailer stack (SES).

Alternative: Call FluentCRM’s REST API Directly

Section titled “Alternative: Call FluentCRM’s REST API Directly”

FluentCRM exposes its own WP REST API at /wp-json/fluent-crm/v2/. The UI wizard calls:

  1. POST /campaigns/{id}/subscribe — queues emails for all matching subscribers
  2. The scheduler then processes them

An authenticated HTTP call (using an Application Password or FluentCRM API key) would replicate exactly what the UI does. This is cleaner than the PHP route and doesn’t depend on internal method signatures.

The PHP eval route (Option 1) is self-contained and needs no new credentials. The REST API route (Option 2) requires generating a FluentCRM API key or WP Application Password, but is more maintainable and mirrors the UI exactly. Either path would make the full campaign workflow (create → populate → send) scriptable from Claude Code on any machine with SSH access to the server.

When this plan is executed, the output should be a Claude Code skill (slash command) — either a new /send-admin-email skill or an extension to the existing /create-email skill. The /create-email skill currently handles content drafting and HTML generation but stops short of the send step. The gap to close is: given a drafted email and a recipient tag, create the FluentCRM campaign record and trigger the send, all from CLI.

Open question: Check whether the /create-email skill’s scope should expand to include the send step, or whether a separate skill (e.g. /send-crm-email) makes more sense given the different context (content creation vs. infrastructure operation).

The 2026-04-29 session used a campaign send. FluentCRM also supports transactional emails, which behave differently:

Campaign emails:

  • Sent to a segment/tag in bulk
  • Tracked as a campaign with open/click metrics
  • Subject to list unsubscribe headers
  • Recipients must be subscribed contacts

Transactional emails:

  • Typically triggered per-contact (confirmation, receipt, notification)
  • In FluentCRM, currently require creating a template and then sending to individual contacts — no native bulk-transactional path in the UI
  • May have deliverability advantages (less likely to be caught by spam filters, since they’re not flagged as marketing mail)

Open questions to research when building the skill:

  1. Does FluentCRM’s API support sending a transactional email to a group (tag/segment) in a single call, or does it require looping per contact?
  2. What is the deliverability difference in practice for Baseworks sends — does sending as transactional meaningfully improve inbox placement given our SES setup and sending volume?
  3. If a transactional path does improve deliverability, should cohort reminder emails (like the Session 5 email) be sent transactionally rather than as campaigns?
  4. No research needed now — these questions should be picked up when the skill is being built.