jackwener/OpenCLI · error · CommandExecutionError

Sales Navigator InMail blocked by recipient restriction

Error message

Sales Navigator InMail blocked by recipient restriction

What it means

The library fetched the recipient's Sales Navigator profile and found that the profile's inmail_restriction field is not NO_RESTRICTION, meaning LinkedIn blocks sending an InMail to this recipient. It throws before consuming any InMail credit, because the send would fail or be silently disallowed.

Source

Thrown at clis/linkedin/salesnav-message.js:285

    if (!page) throw new CommandExecutionError('Browser session required for linkedin salesnav-message');
    const recipientArg = requireStringArg(args, 'recipient', '--recipient');
    const subject = requireStringArg(args, 'subject', '--subject');
    const body = String(args.body ?? '').trim();
    if (!body) throw new ArgumentError('--body is required');

    await page.goto(SALES_HOME);
    await page.wait(4);
    const csrf = await getCsrf(page);
    const recipient = await resolveRecipient(page, parseRecipient(recipientArg), csrf);

    let summary = { recipient: '', title: '', company: '', degree: '', inmail_restriction: '', open_link: false };
    const profileUrl = profileApiUrl(recipient);
    if (profileUrl) {
      const profileResult = requireFetchResult(unwrapEvaluateResult(await page.evaluate(fetchJsonScript(profileUrl, csrf))), 'LinkedIn Sales Navigator profile API');
      summary = requireProfileSummary(profileResult.json);
    }
    if (summary.inmail_restriction && summary.inmail_restriction !== 'NO_RESTRICTION') {
      throw new CommandExecutionError('Sales Navigator InMail blocked by recipient restriction', summary.inmail_restriction);
    }

    const creditsResult = requireFetchResult(unwrapEvaluateResult(await page.evaluate(fetchJsonScript(CREDITS_URL, csrf))), 'LinkedIn Sales Navigator credits API');
    const creditsRemaining = extractRemainingCredits(creditsResult?.json);

    const payload = buildCreateMessagePayload({ recipientUrn: recipient.entityUrn, subject, body, copyToCrm: args['copy-to-crm'] });
    if (!args.send) {
      return [{
        status: 'validated_dry_run',
        recipient: summary.recipient,
        title: summary.title,
        company: summary.company,
        credits_remaining: creditsRemaining,
        credits_before: creditsRemaining,
        credits_after: '',
        sent_in_salesnav: false,
        message_chars: body.length,
        subject_chars: subject.length,

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Pick a different recipient or contact path (regular LinkedIn message/connection request) — the restriction is on LinkedIn's side and cannot be bypassed.
  2. Re-fetch the profile to confirm the restriction value is current, then skip or queue the recipient in your outreach tooling.
  3. Check the thrown detail (the restriction value) and map it in your caller to decide whether retrying later is meaningful (usually not — restrictions are persistent).

Example fix

// before
await sendSalesNavInMail({ recipient, subject, body });
// after
const profile = await fetchProfileSummary(recipient);
if (profile.inmail_restriction && profile.inmail_restriction !== 'NO_RESTRICTION') {
  console.warn(`Skipping ${recipient.id}: ${profile.inmail_restriction}`);
  return { status: 'skipped', reason: profile.inmail_restriction };
}
await sendSalesNavInMail({ recipient, subject, body });
Defensive patterns

Strategy: try-catch

Validate before calling

const summary = await fetchProfileSummary(recipient);
if (summary.inmail_restriction && summary.inmail_restriction !== 'NO_RESTRICTION') {
  throw new SkipError(`Recipient restricted: ${summary.inmail_restriction}`);
}

Type guard

function canSendInMail(summary) {
  return !!summary && (summary.inmail_restriction ?? 'NO_RESTRICTION') === 'NO_RESTRICTION';
}

Try / catch

try {
  await sendSalesNavInMail(recipient, { subject, body });
} catch (e) {
  if (e.detail && e.detail !== 'NO_RESTRICTION') {
    markRecipientBlocked(recipient, e.detail);
    return { status: 'skipped' };
  }
  throw e;
}

Prevention

When it happens

Trigger: Calling the salesnav-message CLI for a recipient whose LinkedIn profile has an InMail restriction (e.g. recipient opted out of InMails, is outside your network with restricted messaging, or has a 'NO_INMAIL' style restriction value other than NO_RESTRICTION).

Common situations: Messaging open-to-work-only or InMail-opted-out candidates; contacting recipients whose accounts limit commercial messages; sending to profiles the operator's seat cannot reach (tier/seat restrictions); stale profile summary cached before recipient changed settings.

Related errors


AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29). Data as JSON: /api/errors/0aac8b528f58920a. Report an issue: GitHub.