BOSSTORQUE · Sperry Tree Care · Q2 2026 Campaign

Kit Campaign Automation — Friction Log & Workflow Fixes

Compiled April 7, 2026 · Updated May 5, 2026 · Emails 1, 2A, 3 · Broadcast IDs: 23610364, 23610791, 23610365
14
Friction Points Found
6
Critical / High Severity
100%
Resolved with Workarounds

Bugs, Limitations & Workarounds

Critical
Kit API PUT clears send_at on scheduled broadcasts

Any PUT /v4/broadcasts/{id} request to a scheduled broadcast silently removes the scheduled send time. The broadcast reverts to "send now" state with no warning. This is a data-loss issue — the schedule disappears without any API error or confirmation.

Affected operations: updating content, changing the subject line, changing the sender — any PUT wipes the schedule.

Workaround

Never use the API to edit scheduled broadcasts. Make all edits through the Kit UI only. If API edits are required, read the current send_at value first, then include it explicitly in your PUT payload to preserve it.

Critical
tel: links universally rejected by Kit's email editor

The Kit email editor rejects any anchor tag with href="tel:..." with the error "Unsupported value 'tel:5414611737' passed to href." This happens in both the visual editor and the Edit HTML modal — there is no way to insert a clickable phone link in a Kit email.

Fix (permanent)

Use plain text for all phone numbers: 541-461-1737. Do not attempt tel:, tel:+1..., or any variant. Strip all tel: anchors from HTML before pasting into Kit.

High
Kit API blocked from Claude sandbox (network error exit 56)

All Kit API calls made via curl or Python from the Claude Cowork sandbox return exit code 56 (network unreachable / connection reset). The sandbox cannot reach api.kit.com. This eliminates all API-driven automation workflows for content updates and scheduling.

Workaround

All Kit campaign operations must be performed via the Kit web UI using Claude's Chrome browser tools. Reserve API calls for read-only verification run from a local machine or external automation environment.

High
React state vs DOM mismatch in Kit's Slate.js editor

Kit's email editor is built on Slate.js with React. Direct manipulation of the DOM (innerHTML, textContent) updates what's visible on screen, but React's internal state still holds the old content. When the Save button is clicked, React serializes its own state — not the DOM — and the edit is lost or rejected.

This is why pasting content via innerHTML appeared to work visually but failed on save, and why a lingering tel: link continued to block saving even after it appeared to be removed from the visible editor.

Fix

Use document.execCommand('insertHTML', false, newHTML) instead of direct DOM manipulation. This command fires through the browser's editing pipeline, which Slate.js intercepts and uses to update its own React state. The edit survives saves and page navigations correctly.

Medium
execCommand('insertHTML') triggers Kit's Edit HTML modal as a side effect

When document.execCommand('insertHTML', false, html) is called on Kit's Slate.js editor, it simultaneously updates React state correctly AND opens the "Edit HTML" modal overlay. If you click Save inside the modal, Kit validates the HTML again — which may differ from what was inserted.

Workaround

Always click Cancel on the Edit HTML modal after calling insertHTML. The main editor already has the correct updated state. After canceling, make a trivial keystroke (space + backspace) to trigger React's onChange handler, then navigate to the Share page — Kit auto-saves on that transition.

Medium
Chrome MCP type action sends literal Unicode escape sequences

The Chrome MCP's type action types the literal characters rather than the actual Unicode character (right single quotation mark '). This corrupts any email content with curly quotes, em dashes, or other non-ASCII characters.

Fix

Never use type for content with Unicode characters. Embed them directly in JavaScript string literals and inject via execCommand('insertHTML'):

// Works — Unicode in JS string literal, not typed
document.execCommand('insertHTML', false,
  '<p>You’re getting this...</p>');
Medium
Schedule change requires Unschedule + re-schedule (no edit-in-place)

Kit's share page shows a scheduled broadcast's time with an "Unschedule" button but no pencil/edit button to modify just the time. To change the send time, you must: (1) click Unschedule to revert to "Sending now" state, (2) click the pencil icon that appears, (3) reselect the date and time from scratch in the react-datepicker.

Workflow

Unschedule → pencil icon appears → click it → select date via find ref → select time via find ref. The find tool is more reliable than coordinate clicks for the date picker.

Info
Kit auto-saves draft content on Share page navigation

When navigating from the draft edit page to the share page (via the "Continue" button or direct URL), Kit automatically saves the current editor state. The "Saved" indicator in the bottom bar confirms the save. There is no need to click a separate Save button before leaving the editor.

Tip

After inserting HTML via execCommand and canceling the modal: make a no-op keystroke (space + backspace) to trigger React's onChange, then navigate to /share. Watch for the "Saved" indicator before proceeding to the schedule step.

Info
Kit UI dropdowns require element-ref clicks, not coordinate clicks

The "From address" dropdown and other Kit UI controls do not reliably respond to coordinate-based clicks. The dropdown opens but options don't register, or the click lands outside the target. This is likely due to dynamic positioning and React portal overlays.

Fix

Use the Chrome MCP find tool to get element references, then click by ref rather than coordinate. Works consistently across dropdowns, date pickers, and overlay buttons.

High
CF7 Redirect "Add Action" AJAX doesn't fire via DOM click

When duplicating a CF7 form, the redirect action is not copied. Clicking the "Redirect" option in the "Add Action" dropdown via Chrome MCP JS click does not trigger the AJAX call that injects the action fields into the page. The dropdown closes but no fields appear.

Fix

POST the full CF7 admin form directly via fetch(form.action, {method:'POST', body: new FormData(form)}), appending wpcf7-redirect[actions][new_1][...] fields to FormData. This creates the action server-side. Reload the page and configure the newly created action ID normally.

High
WP Webhooks Pro scope doesn't extend to duplicated CF7 forms

When a CF7 form is duplicated, the new form ID is not automatically added to any existing WP Webhooks Pro webhook triggers. Form submissions from the duplicate silently bypass all Zapier zaps — no error, no log, no delivery.

Fix

After duplicating any CF7 form that feeds a webhook, immediately navigate to WP Webhooks Pro → Receive Data, find the relevant webhook, and add the new form ID to the wpwhpro_cf7_forms[] multi-select. Save and verify both form IDs appear on reload.

Info
document.elementFromPoint returns null for off-screen form inputs

When trying to locate form inputs via document.elementFromPoint(x, y), the method returns null if the target element is above or below the current viewport — even if the element exists in the DOM. This caused failed element lookups on the CF7 admin form editor.

Fix

Call element.scrollIntoView() before using coordinate-based lookup. Once the element is within the viewport, elementFromPoint resolves correctly and inputs are fully interactive.

Critical → RESOLVED
Kit broadcast content API completely broken via V3 and initial V4 testing — fixed with correct V4 key and auth

Kit V3 API broadcast content writes are silently ignored. POST /v3/broadcasts returns 201 but creates "New Broadcast" with null content. PUT /v3/broadcasts/{id} returns 200 but makes no changes. This was the original blocker for automated email sends throughout Q2.

Initial V4 testing also appeared broken — earlier tests with an older V4 key returned 200 but content did not persist. This led to the conclusion that the entire API write surface was unusable, which drove the manual paste workflow.

Root cause: The older V4 API key did not have write permissions for broadcast content. A new key ("Cowork Send Automation") created with correct scopes resolves the issue entirely.

Resolved — 2026-05-05

PUT /v4/broadcasts/{id} with {"content": "...", "subject": "...", "send_at": "..."} writes all fields correctly when authenticated via X-Kit-Api-Key: kit_c6d81b86936b3b3c468b674cd1f3295c (Cowork Send Automation key). V3 remains broken — use V4 only.

Automation script: kit_send.py in ~/Documents/sperry-kit-automation/. Full V4, no manual paste required. Usage:

# Update existing broadcast (content + subject + schedule)
python3 kit_send.py \
  --broadcast-id 23982498 \
  --subject "Enter to win + bring a friend" \
  --html-file email_body.html \
  --send-at "2026-05-06T13:00:00Z"

# Create new broadcast from scratch
python3 kit_send.py --new \
  --subject "Your subject" \
  --html-file email_body.html \
  --send-at "2026-05-20T13:00:00Z"

Note on snippets: A V4 snippets workaround was explored simultaneously (Snippet ID 135163, "sperry_email_body"). Snippets also write correctly via V4. Kept as a backup mechanism but no longer needed for broadcast sends.

Medium
Kit UI schedule not reflected in V4 API send_at field

A broadcast visually confirmed as scheduled in the Kit UI (Share page shows date/time) can return "send_at": null from GET /v4/broadcasts/{id}. The API and the UI are out of sync.

Confirmed 2026-05-05: broadcast 23982498 was scheduled via UI for May 6 6am PDT but returned null send_at from the V4 API. The email sent correctly — Kit’s internal scheduling state is stored separately from what the API exposes.

Implications

Never rely on API send_at as ground truth for schedule status. Always confirm in the Kit UI. kit_send.py’s schedule-preservation logic reads send_at from the API — if the API returns null for a UI-scheduled broadcast, the preserved value will also be null. Do not run kit_send.py against a broadcast scheduled via the Kit UI without first verifying the API reports the schedule correctly via --preview.

Q2 2026 — All Broadcasts

BroadcastIDAudienceScheduledSenderStatus
Email 1 — Pruning timing23610364Residential (tagged)Apr 9 · 8:10 AM PDTMicheleConfirmed
Email 2A — Pruning timing23610791Residential (tagged)Apr 16 · 8:10 AM PDTMicheleConfirmed
Email 2B — Commercial23610793CommercialDeleted
Email 3 — 5 things to look for23610365All Club MembersApr 23 · 8:10 AM PDTMicheleConfirmed

Inserting HTML Content into a Kit Broadcast

✓ API automation now available — As of May 5, 2026, kit_send.py handles broadcast content, subject, and scheduling end-to-end via the V4 API. The UI workflow below is kept for reference and edge cases, but programmatic sends are preferred. See Friction #13 for full details.

1
Strip tel: links before writing any HTMLPhone numbers must be plain text. Remove all <a href="tel:..."> anchors — Kit will reject the entire edit if any are present.
2
Navigate to the broadcast draft edit pageURL pattern: https://app.kit.com/campaigns/{id}/edit
3
Click into the editor body to focus itUse find("email body editor content area") → click by ref. The editor must have focus before execCommand will work.
4
Select all existing contentPress Cmd+A to select everything, then call execCommand('insertHTML', false, newHTML) to replace it. Unicode characters must be in JS string literals, not typed.
5
Cancel the Edit HTML modal that opensThe insertHTML call triggers the modal as a side effect. Click Cancel — do not Save inside it. The main editor state is already correct.
6
Trigger React onChange with a no-op keystrokeType a space and then backspace to ensure React registers the change and will include it in the auto-save.
7
Navigate to the Share page and confirm "Saved"Click Continue or navigate to /share. Wait for the "Saved" indicator. Content is now persisted.
8
Set sender and schedule on the Share pageUse find refs for the From dropdown, date picker (day + time). Never use the API to update a scheduled broadcast — it clears the send time.