Why does my LinkedIn account show "Connected" but not send?
What this page is for
If a LinkedIn account in your WarmySender Settings shows the green "Connected" pill while campaigns produce no sends, expire daily limits, or surface a contradictory "Reconnect needed" warning — you've hit a state we call "split-state". On May 7, 2026 we shipped a database-level fix (PRD V15 P0-5) that makes this state physically impossible going forward. This page explains what split-state was, why it happened to a small number of accounts between May 2-7, what we did about it, and why it can no longer recur.
WarmySender is a 4-pillar outreach platform — Cold Emailing, Email Warmup, LinkedIn Outreach, and Multichannel sequences. This page is part of the LinkedIn Outreach pillar troubleshooting set.
What is split-state?
Every LinkedIn account in WarmySender carries two pieces of state that should always agree with each other: a status (one of Connecting, Connected, Needs Verification, Disconnected, Restricted, Error) and an optional last-error message. If the status is Connected, last-error should be empty or describe a transient blip; if the status is Disconnected, last-error should describe the terminal reason for the disconnect (account de-registered with our integration partner Unipile, OAuth token expired, account restricted by LinkedIn, etc.).
"Split-state" was the term we used internally for the impossible combination of status = Connected AND last-error = "Account no longer registered with Unipile — please reconnect" on the same row. The status said "fine," the last-error said "go reconnect," and the UI couldn't decide which one to surface. Customers who hit this state saw their dashboards show Connected accounts that produced zero sends and an unexplained "Reconnect needed" pill that didn't match the green status badge.
Why did it happen?
Per the Unipile API documentation, an HTTP 404 response from a Unipile account-status check means the account is no longer registered with our integration partner. Permanently. The customer needs to reconnect. We have a 3-strike tolerance window to absorb transient 404s during Unipile-side blips before flipping the row to Disconnected — the so-called "mass-404 burst" guard, which prevents a single Unipile incident from mass-disconnecting hundreds of accounts at once.
The bug: the sub-threshold (strikes 1 and 2 of 3) writer stamped the harsh terminal copy "Account no longer registered with Unipile — please reconnect" onto the row's last-error field while leaving status = Connected. The intent was "warn the user that we just got a 404, but don't flip yet because it might be transient." The implementation accidentally used the same terminal copy that the strike-3 hard-flip path used, producing the contradiction the UI couldn't resolve.
Two LinkedIn accounts on a single customer's workspace got stuck in this state on May 2, 2026 at 11:09 UTC and stayed there until May 7. They were driving daily-limit expiries (about 73 jobs/day each) against a Unipile-side account that no longer existed — burning the cap counter without producing any actual LinkedIn delivery. The customer correctly reported "InMail crashed" and our initial triage misdiagnosed it as an InMail-specific bug; the actual root cause was split-state masking the fact that the underlying Unipile registration was gone.
What we fixed on May 7, 2026
Three independent layers, each redundant of the other two, so a regression in any single layer is caught by the next.
Layer 1 — sub-threshold writer now uses soft copy
The sub-threshold path (strikes 1 and 2 of 3) now stamps "Unipile temporarily unreachable (HTTP 404, strike N/3). Auto-retry; account remains connected." This is honest: the row IS still connected at strike 1 or 2; we're just observing the strike count climb. The soft copy is never flagged as terminal by the UI, so no "Reconnect needed" pill appears on a still-connected row. If the strike count reaches 3, the row flips to Disconnected through our canonical state-flip helper, which atomically updates status, disconnected_at, disconnected_by, and last-error in lockstep — there is no longer a window in which status and last-error can disagree.
Layer 2 — every state flip routes through one helper
Our canonical state-flip helper (flipLinkedinAccountStatus) now owns every transition between LinkedIn account states. It enforces a bidirectional invariant — status = Connected if and only if the disconnect timestamp is null — and writes a forensic audit row for every flip. Inline UPDATEs that mutate the status column are blocked at the database layer by an audit-pairing trigger; any code path that bypasses the helper raises an immediate, loud error rather than silently producing a split-state row.
Layer 3 — database-level CHECK constraint
The most important defense is the new database CHECK constraint linkedin_accounts_no_split_state_chk. It refuses any row where status = Connected and last-error matches a regex covering all known harsh-disconnect phrases ("no longer registered", "please reconnect", "account not found … reconnect"). The constraint is enforced on every INSERT and UPDATE; even a buggy code path that skipped the canonical helper and ran a raw UPDATE would fail at the database layer. The split-state combination is now physically impossible at the storage layer, regardless of which code path writes the row.
What we did about already-affected rows
We ran a one-shot repair script repair-selva-selvakumar-split-state-2026-05-07.ts against production. It found the two affected rows, routed them through the canonical state-flip helper to flip status from Connected to Disconnected (atomically with the disconnect timestamp), and wrote an entry into our admin alerts table so the support team could follow up with the customer. The repair was strictly safety-improving: flipping the rows to Disconnected prevented further cap-counter increment against a Unipile account that no longer existed. There is no scenario in which the repair could have caused a duplicate send.
Affected customers received a corrected message acknowledging the misdiagnosis (this was misread as an InMail-specific bug for several days) and explaining the actual fix. If you were affected and your dashboard still shows the orphaned account as Connected, hard-refresh once and check the LinkedIn Settings page; the row should now show Disconnected with a clear next action to reconnect from the Settings UI.
Common questions
Is my account at risk now? Was anything sent that shouldn't have been?
No. Split-state was a UI / counter contradiction, not an over-send. The accounts in question were producing zero deliveries while the cap counter incremented — the opposite of an over-send. Per CLAUDE.md's account-safety principle ("a banned LinkedIn account is unrecoverable"), our engine errs on the side of refusing to send when state is uncertain. Split-state caused under-delivery, never over-delivery.
Will I get my cap budget back?
Daily and weekly caps reset on schedule (daily caps reset at midnight UTC; weekly caps roll on a 7-day window from your account's first invite of the week). The cap counter increments that occurred against the orphaned account on May 2-7 were lost — they didn't produce any actual LinkedIn API calls, so there's no way to "refund" budget that wasn't actually consumed on the LinkedIn side. Reconnect the affected account from Settings; the next ramp tick will resume sends within your account's normal safety limits.
How do I reconnect a Disconnected account?
Open the LinkedIn Settings page, find the affected row (it will now display "Disconnected"), and click the Reconnect button. The flow walks you through reauthorizing the account with our integration partner Unipile. After reconnect, status flips back to Connected, the disconnect markers clear, and the next campaign tick (within ~5 minutes) picks up scheduled prospects. Per Unipile's provider limits guidance, your existing daily / weekly caps and ramp schedule are preserved across the reconnect.
How do I tell if an account is in split-state today?
You can't be — it's not a state the system can write any more. If you see a Connected account with a contradictory pill or zero sends, it's almost certainly a different cause: a campaign paused for a separate reason (subscription inactive, high failure rate, template fix needed), a sending window closed for the day, or an enrollment-pool exhaustion. See the Why isn't my LinkedIn campaign sending? guide for the full troubleshooting tree.
Was this related to the InMail credit-exhausted issue?
Adjacent but distinct. Split-state and InMail credit-exhaustion both presented as "Connected account, zero sends," and both affected the same customer in the same week, which is why they were initially conflated. They are different bugs with different fixes: split-state is fixed by the May 7 CHECK constraint; InMail credit exhaustion is handled by a separate per-account circuit breaker (see Why no InMails sent — credits exhausted if it's enabled in your environment).
Account safety
Zero new Unipile API calls. The May 7 fix is database-only — a CHECK constraint, a refactored writer that uses soft copy, a one-shot repair script that routes through our existing canonical state-flip helper, and observability rows to make future regressions visible. We did not call Unipile to "verify" anything during the repair because re-polling at scale would have risked tripping LinkedIn's automation detection. The MASS-404-BURST circuit breaker remained untouched throughout the fix and continues to gate flip operations against Unipile-side incidents.
Per-account daily and weekly invite/message/InMail caps, sending windows, and timezone scheduling are unchanged. The repair script itself routes through the canonical state-flip helper, which means the same audit trail, mass-disconnect rate limiter, and bidirectional invariant checks that protect every other state flip in the system also protected the repair.
Related guides
- Why isn't my LinkedIn campaign sending? — Seven common patterns and their fixes
- If you suspect a missed LinkedIn accept — How accept-detection works (webhook, polling, message-inferred)
- Why was my LinkedIn InMail count corrected? — The May 2 chatId-required invariant
- LinkedIn rate limits — Unipile-cited per-account daily and weekly caps
- LinkedIn campaign documentation — How LinkedIn campaigns send, why acceptance rates can appear to lag, and what happens on disconnect
- Full documentation — All 90+ guides
- Support — How to get in touch
Still seeing something off? Email hello@warmysender.com with your account display-name and a screenshot — we'll cross-check against the underlying state-flip audit log and confirm what's going on.