Skip to main content
Glama

Send Replies (queued — returns job ids, does not wait)

li_send_reply
Destructive

Answer ONE person with profileUrl + message, or up to 25 in a single call with replies. Either way it returns IMMEDIATELY with a job id per recipient instead of holding the call open. This is the tool for answering replies; li_send_message stays the blocking one for when you genuinely need the outcome inline.

Why it returns early: LinkedIn work runs one browser session at a time, so a perfectly healthy send can sit queued for many minutes before it even starts — that is normal, not a fault. A client that gives up waiting reports an error for a message that is about to be delivered, the caller sends again, and the person receives it twice.

A returned job id is NOT a failure and NOT a timeout: the message is queued and the worker will send it. NEVER call this again for the same recipient to "make sure".

⚠️ DO NOT VERIFY BY DEFAULT. Queued means it is going out; polling spends time on news that was never in doubt. Say it is queued and finish. Verify only when somebody ASKS whether it landed, or when something later suggests it did not: the surest sign is a person still in li_replies_to_answer an hour on, because a delivered reply leaves that queue by itself. Then it is ONE call — li_job_result with jobIds set to the ids this returned, plus waitMs if you genuinely need to wait inside that call. One call covers the whole batch however many accounts it spans; stillRunning: 0 is how you know they have all landed.

The array form queues ONE ORDINARY SEND PER PERSON rather than a single bulk run, which is why it cannot time out and why each reply is recoverable on its own. Each reply is delivered in its own browser session addressed to one profile URL, so no part of a batch can reach the wrong person. Every recipient is checked BEFORE anything is queued: if one is refused, nothing at all is queued, so you can drop that row and call again safely.

✅ A BATCH MAY SPAN ACCOUNTS. Leave the account unnamed and every reply is sent from the account whose conversation it actually is — so the queue from li_replies_to_answer can be answered exactly as it came back, in one call, however many accounts it covers. Each queued row reports the accountId that sent it, and the reply carries an accounts summary.

Naming an account with sessionLabel still pins the WHOLE call to it, and any row whose conversation belongs elsewhere is then refused — use that when you mean it. Either way a row that cannot be placed (untracked, or on two of your own accounts) is refused BY NAME and nothing at all is queued, so you can drop it and call again safely. Answering someone from an account they never heard from is worse than answering them late.

Input Schema

NameRequiredDescriptionDefault
messageNoExact message text to send (supplied, never generated at send time)
repliesNoSeveral replies in ONE call, max 25. Each becomes its own queued send with its own job id. Two rows naming the same person are REFUSED — that is two answers to one conversation, and it would arrive as two messages seconds apart.
sessionIdNoSending account id (or use sessionLabel)
profileUrlNoOne recipient, by profile URL (https://www.linkedin.com/in/<slug>); the fsd-id URL from li_list_conversations also resolves. Use `replies` instead for several.
confirmSendYesMust be true to queue the send; false returns a dry-run preview
bookingCloseNoThese messages CLOSE the booking thread for people li_booking_due listed with nextAction "close". Same refusals, plus: only after the nudges are used. On queueing, the booking becomes closed, the verdict no_outcome and any remaining steps are cancelled. Applies to every reply in the call; never together with bookingNudge.
bookingNudgeNoThese messages are BOOKING NUDGES for people li_booking_due listed with nextAction "nudge". Each is REFUSED unless that person agreed (li_track_booking), their check date has arrived, they have nudges left, no nudge went out in the last 20 hours, they have not written since the link/agreement, they are not on the do-not-contact list and the account is not paused. The booking state moves in the same step as the queueing (nudge count +1, next check = send time + the account's close-after days), and outside the account's office hours the send is queued for when they open. With no account named, each person is sent from the account their agreed booking is on (li_booking_due's accountId), even when they are in campaigns on more than one of your accounts. Applies to every reply in the call.
sessionLabelNoSending account label
calendarCheckedNoREQUIRED (true) for anyone with our booking link out (bookingPending / bookingCheckRequired in li_replies_to_answer) or a call already booked: it says you searched the calendar for THIS person just before this call. This server reads no calendar, so a booking made minutes ago is invisible here until li_mark_booked records it. If they booked: send NOTHING (no "grab a slot", no "see you then") and record it with li_mark_booked. Without it such a row is refused and nothing is queued. Implied by bookingNudge / bookingClose (their routine sweeps the calendar first), but search again right before those too. Applies to every reply in the call.

Schema Changelog

Changes observed during successful MCP inspections.

  1. Changed1 schema field changed
    • changedInput schema / properties / bookingNudge / description
      Previous value: -"These messages are BOOKING NUDGES for people li_booking_due listed with nextAction \"nudge\". Each is REFUSED unless that person agreed (li_track_booking), their check date has arrived, they have nudges left, no nudge went out in the last 20 hours, they have not written since the link/agreement, they are not on the do-not-contact list and the account is not paused. The booking state moves in the same step as the queueing (nudge count +1, next check = send time + the account's close-after days), and outside the account's office hours the send is queued for when they open. Applies to every reply in the call."New value: +"These messages are BOOKING NUDGES for people li_booking_due listed with nextAction \"nudge\". Each is REFUSED unless that person agreed (li_track_booking), their check date has arrived, they have nudges left, no nudge went out in the last 20 hours, they have not written since the link/agreement, they are not on the do-not-contact list and the account is not paused. The booking state moves in the same step as the queueing (nudge count +1, next check = send time + the account's close-after days), and outside the account's office hours the send is queued for when they open. With no account named, each person is sent from the account their agreed booking is on (li_booking_due's accountId), even when they are in campaigns on more than one of your accounts. Applies to every reply in the call."
  2. Changed1 schema field changed
    • addedInput schema / properties / calendarChecked
      Added value: +{
      +  "description": "REQUIRED (true) for anyone with our booking link out (bookingPending / bookingCheckRequired in li_replies_to_answer) or a call already booked: it says you searched the calendar for THIS person just before this call. This server reads no calendar, so a booking made minutes ago is invisible here until li_mark_booked records it. If they booked: send NOTHING (no \"grab a slot\", no \"see you then\") and record it with li_mark_booked. Without it such a row is refused and nothing is queued. Implied by bookingNudge / bookingClose (their routine sweeps the calendar first), but search again right before those too. Applies to every reply in the call.",
      +  "type": "boolean"
      +}
  3. First observed

TDQS

A4.8/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations declare the safety profile (destructive, non-idempotent, open-world) but say nothing about queueing semantics, atomic batch refusal, account pinning, or double-send risk. The description supplies all of that: why it returns early, that a job id is not a failure, that nothing is queued if any row is refused, and that an unnamed account sends each reply from its own conversation's account.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded with the decisive fact (returns immediately, job id per recipient) and organized with emphasis markers, but it runs long and repeats itself — the 'a row that cannot be placed is refused BY NAME and nothing at all is queued, so you can drop it and call again safely' idea appears twice in near-identical wording.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

There is no output schema, and the description compensates fully: it explains what comes back (a job id per recipient, per-row accountId, an `accounts` summary), how to poll for completion, and the edge cases (refusals, account mismatch, untracked recipients). Nothing an agent needs to call this correctly is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is already 100%, so the baseline is 3, but the description adds real meaning beyond the schema: sessionLabel pins the whole call (and rows belonging elsewhere are refused), bookingNudge/bookingClose/calendarChecked apply to every reply in the call, and the replies array is one ordinary send per person rather than a bulk run.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb (answer/send replies), the two input shapes (one person via profileUrl+message, up to 25 via `replies`), and the defining behavior (returns immediately with a job id per recipient). It explicitly distinguishes itself from the blocking sibling li_send_message, so an agent can route without opening either schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicit when-to-use versus li_send_message (blocking vs queued), explicit when-NOT-to-verify ('DO NOT VERIFY BY DEFAULT', verify only when asked or when someone lingers in li_replies_to_answer), and the exact recovery path (one li_job_result call with jobIds, stillRunning:0). Alternatives and exclusions are all named.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Try in Browser

Glama MCP Gateway

Add one secure layer between your agents and this server.

Resources