Skip to content

fix(newsletters): harden ActiveCampaign sends against API timeouts#269

Open
adekbadek wants to merge 6 commits into
releasefrom
nppm-2878-newsletter-send-times-out-via-bluelena-activecampaign
Open

fix(newsletters): harden ActiveCampaign sends against API timeouts#269
adekbadek wants to merge 6 commits into
releasefrom
nppm-2878-newsletter-send-times-out-via-bluelena-activecampaign

Conversation

@adekbadek

@adekbadek adekbadek commented Jun 10, 2026

Copy link
Copy Markdown
Member

All Submissions:

Changes proposed in this Pull Request:

Hardens ActiveCampaign newsletter sends against ActiveCampaign-side API hangs, and makes a retried send safe.

Background: a publisher hit cURL error 28: Operation timed out after 45002ms with 0 bytes received when sending to their main list. Root cause is on ActiveCampaign's side – specific campaigns got into a state where AC's V1 /admin/api.php calls referencing them never respond, tripping our 45s request timeout. It self-resolved with no Newspack change. We can't fix the AC hang, but we can stop it from corrupting our send flow, stop surfacing a raw cURL string to publishers, and – most importantly – make sure a retry never sends the newsletter twice.

The send is a sequence of side-effectful steps (delete old campaign → upload message → create campaign → trigger send), and any step can time out. The core insight: a timed-out campaign_status (the trigger) is ambiguous – AC may have started sending before the HTTP response hung. So the fix makes the sequence verify state before acting.

  1. Verify the stored campaign's state before resending (double-send safety). On entry, send() reads the campaign's actual status from AC rather than blindly deleting and recreating it. If it's already scheduled/sending/sent, the send is treated as done (no resend). If it's a confirmed draft, it's safe to recreate and send. If its state can't be verified (the status check itself times out) or it's in an indeterminate paused/stopped state, send() fails safe with a publisher-facing notice and does not resend – a stranded "needs review" is far better than two newsletters to the list.

  2. Bounded cleanup timeout. Deleting a previous campaign is best-effort cleanup, so it uses a short, bounded timeout (15s) instead of the 45s default – a stuck prior campaign can't consume the request budget. Required plumbing a caller-supplied timeout through the v1/v3 request methods, which previously silently dropped it.

  3. Publisher-friendly timeout error. A cURL-28 / "timed out" transport failure is rephrased into actionable guidance instead of the raw cURL message; the original error is preserved as WP_Error data for logging/support tooling. Other transport errors pass through unchanged.

Scope note: this is the synchronous, idempotent foundation. It does not auto-retry through a hang – the publisher still sees a failure on the spot – but the next attempt (manual re-publish, or the existing scheduled-send retry) now resumes safely instead of risking a duplicate send. A fully async/queued send with automatic retry is a larger follow-up that can sit on top of this.

Closes NPPM-2878.

How to test the changes in this Pull Request:

  1. Run the suite: from plugins/newspack-newsletters, n test-php --filter ActiveCampaignResilienceTest – all tests pass.
  2. The suite is a self-proving spec covering: caller-timeout plumbing (v1 + v3), bounded cleanup timeout, cURL-28 humanization (v1 + v3) with the original error preserved, non-timeout errors passing through unchanged, and the send-state gate: an already-dispatched campaign is not resent, an unverifiable campaign fails safe, a paused/stopped campaign is flagged for review, and a confirmed draft is recreated and sent.
  3. Regression: n test-php --filter ActiveCampaignApiRequestsTest still passes.
  4. The refactored request layer (incl. the new caller-timeout path) was also exercised against the live ActiveCampaign API, read-only.

Other information:

  • Have you added an explanation of what your changes do and why you'd like us to include them?
  • Have you written new tests for your changes, as applicable?
  • Have you successfully run tests with your changes locally?

Copilot AI review requested due to automatic review settings June 10, 2026 11:07

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR hardens the Newspack Newsletters ActiveCampaign integration against upstream API hangs/timeouts by ensuring campaign cleanup can’t consume the full request budget and by surfacing a publisher-friendly timeout message instead of raw cURL output.

Changes:

  • Add DEFAULT_REQUEST_TIMEOUT (45s) and CLEANUP_REQUEST_TIMEOUT (15s), and plumb caller-supplied timeout through api_v1_request() and api_v3_request().
  • Humanize transport timeouts (e.g., cURL error 28) returned from wp_safe_remote_request() for both v1 and v3 endpoints.
  • Add a PHPUnit spec covering timeout plumbing, bounded cleanup behavior, and timeout humanization.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

File Description
plugins/newspack-newsletters/includes/service-providers/active_campaign/class-newspack-newsletters-active-campaign.php Adds configurable timeouts, bounded cleanup requests, and timeout-specific error humanization.
plugins/newspack-newsletters/tests/test-active-campaign-resilience.php Adds regression tests asserting timeout propagation, cleanup timeout bounds, and user-friendly timeout errors.

adekbadek and others added 2 commits June 10, 2026 13:21
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@adekbadek adekbadek force-pushed the nppm-2878-newsletter-send-times-out-via-bluelena-activecampaign branch from ae0498d to 6b8efcf Compare June 10, 2026 11:21
@adekbadek adekbadek changed the base branch from main to release June 10, 2026 11:21
@adekbadek

Copy link
Copy Markdown
Member Author

Retargeted to release — this is a hotfix for NPPM-2878. The two commits were rebased cleanly onto origin/release (no conflicts; the edited methods are identical between main and release). As a fix: it will ship as a patch release and promote back to main/alpha via the normal release flow.

adekbadek and others added 4 commits June 10, 2026 16:54
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@adekbadek adekbadek marked this pull request as ready for review June 11, 2026 09:23
@adekbadek adekbadek requested a review from a team as a code owner June 11, 2026 09:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants