How to Automate LinkedIn Messages (2026): What's Possible, What's Safe, and How to Do It in Code
If you send the same LinkedIn connection requests and follow-up messages by hand every week, you have already hit the ceiling: it does not scale, and copy-pasting into the message box is exactly the work a computer should do. This guide covers what you can automate on LinkedIn, the safe ways to do it, and – for teams who want it wired into their own product or CRM – the exact connect-to-reply flow in code.
The short version. Standard LinkedIn messaging has no native way to schedule or sequence outbound DMs (LinkedIn documents scheduled outreach only for its paid recruiting tools – Recruiter and Recruiter Lite can schedule initial InMail, and Recruiter alone can schedule one automated follow-up; Premium's away message is just an auto-reply), so automating everyday outreach means a tool or an API acting on your own account. The real automation is a branching loop: if you are already connected, send a message; if not, either send a connection request → wait until it is accepted → message, or use Sales Navigator InMail to reach a non-connection directly. Then read the reply, or monitor your whole inbox for incoming messages at scale. Keep it on your own account, at a human pace, within LinkedIn's limits.
Can you automate LinkedIn messages?
Yes – but for regular direct messages, not with a native LinkedIn button. LinkedIn's own Message content automation page is about inbound processing – how it renders and suggests replies to messages you receive – not about scheduling messages you send.
The one place LinkedIn documents scheduled outbound is its paid recruiting product: per LinkedIn's help center, you can schedule initial outreach InMail in Recruiter and Recruiter Lite, and schedule one automated follow-up InMail in Recruiter only. Everyday Messaging has no equivalent, and the Premium away message is an automatic reply shown to people who message you – not a way to send scheduled outreach.
So for standard outreach, "automating LinkedIn messages" means a tool or an API that performs the clicks and keystrokes on your own account. The rest of this guide is how to do that well.
The three ways to automate LinkedIn messages
There are three approaches, and the right one depends on how much control you need and whether you write code.
| Approach | How it runs | Best for | Honest trade-off |
|---|---|---|---|
| Browser (Chrome) extension | On your own computer, in your logged-in browser | Quick, occasional personal use | Only works while your machine and browser are on; more exposed than a cloud setup |
| Cloud no-code tool | On the vendor's cloud, on a schedule, through a visual campaign builder | Non-technical users running standard campaigns | A closed UI – you get the vendor's features, not your own logic; you live inside their product |
| API / code | You call an API from your own backend, on your own account | Teams embedding messaging in a product, CRM, or agent | You write a little code – in exchange you own the whole flow |
The first two are covered well elsewhere (see our roundup of LinkedIn automation tools for the "which tool" question). This guide focuses on the third, because it is the one no listicle actually shows you: the real flow, in code, on your own account.
What you can automate: messages, InMail, and connection notes
Before the code, get the routing right – who you can message depends on your relationship to them.
| Message type | Who it reaches | Method | Notes |
|---|---|---|---|
| Standard message | People you are already connected to (1st-degree) | sendMessage | Text up to 1,900 characters |
| Sales Navigator InMail | People you are not connected to | nvSendMessage | Needs a Sales Navigator seat and a subject line |
| Connection request + note | Someone you want to connect with | sendConnectionRequest | The note is a single field (200 chars free / 300 Premium), not a sequence |
Two honest boundaries that pages blurring "reach anyone instantly" skip: a standard message only works once someone is a 1st-degree connection, and reaching a non-connection means either connecting first or spending a Sales Navigator InMail. There is no button that DMs a stranger for free.
How to automate the full flow (in code)
Here is the branching loop with Linked API, the LinkedIn automation API that runs these actions on your own account. Install the SDK (npm install -S @linkedapi/node or pip install linkedapi) and initialise the client with your tokens.
The core decision is the acceptance gate – the step generic messaging relays skip. You check where you stand, and only message a first-degree connection; if you are not connected, you send a request and message after it is accepted. Handle one edge as you go: some people require a verified email to accept a request, which sendConnectionRequest reports as emailRequired. Email is an optional input for that case only – Linked API never returns email as data.
import LinkedApi from '@linkedapi/node';
const linkedapi = new LinkedApi({
linkedApiToken: process.env.LINKED_API_TOKEN,
identificationToken: process.env.IDENTIFICATION_TOKEN,
});
const NOTE = 'Hi! I work on LinkedIn tooling and would love to connect.';
async function reach(personUrl: string, text: string, knownEmail?: string): Promise<void> {
// 1. Where do we stand? -> connected | pending | notConnected
const check = await linkedapi.checkConnectionStatus.execute({ personUrl });
const { data } = await linkedapi.checkConnectionStatus.result(check.workflowId);
// 2. Already connected -> message them directly.
if (data?.connectionStatus === 'connected') {
const sent = await linkedapi.sendMessage.execute({ personUrl, text });
await linkedapi.sendMessage.result(sent.workflowId);
return;
}
// 3. Not connected -> send a request; message only once it is accepted.
if (data?.connectionStatus === 'notConnected') {
const req = await linkedapi.sendConnectionRequest.execute({ personUrl, note: NOTE });
const { errors } = await linkedapi.sendConnectionRequest.result(req.workflowId);
// Edge: this person needs a verified email. Retry with one if you have it.
if (errors?.some((e) => e.type === 'emailRequired') && knownEmail) {
const retry = await linkedapi.sendConnectionRequest.execute({
personUrl, note: NOTE, email: knownEmail,
});
await linkedapi.sendConnectionRequest.result(retry.workflowId);
}
// Re-run checkConnectionStatus on a schedule (cron or webhook);
// when it returns 'connected', call sendMessage. Do not busy-wait.
}
}To reach someone you are not connected to without waiting, send a Sales Navigator InMail instead. This route has no acceptance gate, but it needs a Sales Navigator seat and a subject line.
const inmail = await linkedapi.nvSendMessage.execute({
personUrl,
subject: 'Quick question about your team',
text: 'Hi Dana, saw your team is hiring SDRs. Mind if I share how we help?',
});
await linkedapi.nvSendMessage.result(inmail.workflowId);Finally, read the reply. syncConversation turns on message capture once, then pollConversations returns the messages, each tagged with a sender of us or them, so you can detect a real response. A standard message and a Sales Navigator InMail live in different threads, so match the sync method and the type to the route: syncConversation with type: 'st' for a connection, nvSyncConversation with type: 'nv' for an InMail.
// Standard reply (connection route): sync once, then poll the 'st' thread.
const s1 = await linkedapi.syncConversation.execute({ personUrl });
await linkedapi.syncConversation.result(s1.workflowId);
const { data } = await linkedapi.pollConversations([{ personUrl, type: 'st' }]);
const theirReplies = data?.[0]?.messages.filter((m) => m.sender === 'them');
console.log(theirReplies?.at(-1)?.text);
// InMail reply (Sales Navigator route): use nvSyncConversation and type 'nv'.
const s2 = await linkedapi.nvSyncConversation.execute({ personUrl });
await linkedapi.nvSyncConversation.result(s2.workflowId);
const inmail = await linkedapi.pollConversations([{ personUrl, type: 'nv' }]);Prefer the shell? The CLI runs the same flow, and the AI-agent surfaces – the MCP server and ready-made skills – expose it to Claude Code, Cursor, and Codex.
linkedin connection status https://www.linkedin.com/in/john-doe # connected | pending | notConnected
linkedin connection send https://www.linkedin.com/in/john-doe --note "Hi! Would love to connect."
linkedin message send https://www.linkedin.com/in/john-doe "Thanks for connecting - quick question..."
linkedin navigator message send https://www.linkedin.com/in/john-doe "Hi..." --subject "Quick question"
linkedin message get https://www.linkedin.com/in/john-doe --json # wraps sync + poll to read repliesWhere this flow ends is deliberate: it gets a message sent and the reply back into your system. What the follow-up should say, and when to send it, is outreach strategy – see our guide to LinkedIn cold outreach for the copy, timing, and sequence design. For method-by-method reference, see the docs for sending messages, connection requests, checking connection status, and working with conversations, or the SDK pages for sendMessage, nvSendMessage, sendConnectionRequest, checkConnectionStatus, syncConversation, nvSyncConversation, and pollConversations.

Automate the whole inbox (monitor every reply)
The loop above reads a reply when you already know the person. To automate messaging at the inbox level – catch every incoming message across all your conversations, then route, tag, or reply to it – switch on whole-inbox monitoring instead of syncing people one by one. Enable it once per account with syncInbox (or nvSyncInbox for the Sales Navigator inbox), then pollInbox returns every new message across every thread, each with the personUrl to reply to and a sender of us or them.
// Enable whole-inbox monitoring once per account.
const sync = await linkedapi.syncInbox.execute();
await linkedapi.syncInbox.result(sync.workflowId);
// Poll only what arrived since your last run, using a cursor you persist.
// Without `since`, pollInbox returns everything captured - so you would
// reprocess (and re-reply to) old messages on every run.
const since = await loadCursor(); // your storage; undefined on first run
const { data } = await linkedapi.pollInbox({ type: 'st', since });
for (const m of data?.messages ?? []) {
if (m.sender !== 'them') continue; // inbound only
const reply = await linkedapi.sendMessage.execute({ personUrl: m.personUrl, text: 'Thanks for the reply!' });
await linkedapi.sendMessage.result(reply.workflowId);
}
// Advance the cursor to the newest message (pollInbox returns newest-first).
if (data?.messages.length) await saveCursor(data.messages[0].time);On the shell it is linkedin inbox sync once (add --nv for the Sales Navigator inbox), then linkedin inbox get --since <timestamp>. Poll with an advancing since, or skip polling entirely and subscribe to inbox webhook events for push delivery. This stays the mechanics layer – detecting and retrieving the message; what an auto-reply should actually say still belongs to your outreach strategy.
LinkedIn message and connection limits (staying safe)
LinkedIn does not publish exact message or connection-request limits, so treat the numbers you see online as guardrails, not gospel, and expect them to move with your account's age and standing. Third-party trackers land in a similar ballpark. For connection requests, ZELIQ's 2026 automation guide suggests roughly 20-40 a day (100-200 a week), while PhantomBuster, as of its July 2026 update, puts free and Premium accounts nearer 100 a week. For direct messages, ZELIQ's 2026 guide suggests about 30-60 a day, and PhantomBuster, as of July 2026, cites roughly 100 a week on a free account and 150 on Premium – all unofficial observations, not published caps. Our own breakdowns of the connection request limit and LinkedIn's limits go deeper.
The safer way to automate is to stay well inside those bounds on your own account. Linked API is built for exactly that: every action runs on your own account through a human-paced cloud browser, not a blast queue, and it surfaces LinkedIn's own limit signals instead of steamrolling them. Your code receives messagingNotAllowed when you are not connected or messaging is restricted, noteLimitExceeded when a free account runs out of personalized invitation notes, and requestNotAllowed when you approach LinkedIn's request limits – so you can back off automatically. It works within our safety model, and it returns profile, company, and post data – not emails or phone numbers.
When a no-code tool is the better choice
Code is not always the answer. If you want a point-and-click campaign builder, a visual sequence editor, and a shared team inbox – and you never want to touch an SDK – a no-code tool (the Dripify / HeyReach / Waalaxy class) will get you live faster. Those products are a good fit when the standard "connect, then follow up on a schedule" campaign is all you need and a closed UI is acceptable.
Linked API is the better choice when you want messaging as a primitive inside your own stack: triggered by events in your CRM, branched on your own logic, wrapped in your product, or driven by an AI agent. You trade a visual builder for full control and embeddability. Start from the installation guide and the CLI quickstart.
Frequently Asked Questions (FAQ)
Yes, for standard direct messages – but not with a native LinkedIn feature. LinkedIn only offers scheduled outbound in its paid Recruiter products; everyday Messaging has none. Automating standard outreach means using a tool or an API, like Linked API, that performs the actions on your own account.
Any automation carries some risk, and how you use it decides how much. Aggressive volume and speed are what push accounts toward restriction, so the safer approach is to stay well within LinkedIn's limits and keep a human pace. Linked API runs each action on your own account through a human-paced cloud browser and surfaces LinkedIn's own limit signals – messagingNotAllowed, noteLimitExceeded, requestNotAllowed – so your code can back off before it hits a wall.
Not for standard direct messages – LinkedIn has no native scheduler for them. LinkedIn Recruiter and Recruiter Lite can schedule initial InMail, and Recruiter can schedule one automated follow-up, but that is a paid recruiting feature. For everything else you schedule the send in your own code or in a third-party tool.
There is no safe "blast to thousands" button, and trying to fake one is how accounts get restricted. The scalable-but-safe approach is to loop over your list programmatically and send each message on your own account at a human pace, respecting LinkedIn's limits – which is exactly what the flow above does.
Yes, two ways. Send a connection request and message them once they accept (a standard 1st-degree message), or send a Sales Navigator InMail with nvSendMessage, which reaches a non-connection directly but needs a Sales Navigator seat and a subject line.
LinkedIn publishes no exact cap. Third-party trackers put safe connection requests around 20-40 a day, and direct messages around 30-60 a day, per ZELIQ's 2026 guide; PhantomBuster, as of July 2026, cites nearer 100 messages a week on a free account. They are unofficial and move with your account standing, so treat them as guardrails and stay comfortably under them. See our connection limit guide for detail.
You can send messages and connection requests on a free LinkedIn account, but free accounts hit tighter limits – for example, a capped number of personalized invitation notes (Linked API returns noteLimitExceeded when you reach it). Automation tools and APIs are paid products; Linked API starts at $49/mo (Core, billed annually), flat per seat.
Yes. Turn on whole-inbox monitoring once with syncInbox (or nvSyncInbox for the Sales Navigator inbox), then pollInbox returns every new message across all your conversations, each tagged with the personUrl and a sender of us or them – so you can route, tag, or reply to incoming messages in code. Prefer push over polling? Subscribe to inbox webhook events. On the CLI it is linkedin inbox sync once, then linkedin inbox get.
A cloud setup is generally the safer of the two: it runs independently of your own machine and browser session, at a controlled pace. A Chrome extension only runs while your computer and browser are on, and ties the automation directly to your everyday browsing session. An API on a cloud browser, like Linked API, gives you the cloud's consistency plus full control over pacing.
Want to run the connect-to-reply flow on your own account, inside your own stack? Start with Linked API – the same actions across the API and SDKs, CLI, MCP server, and ready-made skills, on your own account and at a human pace.