Skip to content

Commit 2fc3f55

Browse files
Add sync & poll inbox
1 parent b8b7b29 commit 2fc3f55

10 files changed

Lines changed: 247 additions & 6 deletions

File tree

examples/messaging.ts

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,11 @@ async function messagingExample(): Promise<void> {
2020

2121
await pollConversations(linkedapi, targetPersonUrl, targetPersonUrl2);
2222

23+
await syncInbox(linkedapi);
24+
await salesNavigatorSyncInbox(linkedapi);
25+
26+
await pollInbox(linkedapi);
27+
2328
} catch (error) {
2429
if (error instanceof LinkedApiError) {
2530
console.error('🚨 Linked API Error:', error.message);
@@ -155,6 +160,68 @@ async function pollConversations(linkedapi: LinkedApi, standardPersonUrl: string
155160
});
156161
}
157162

163+
async function syncInbox(linkedapi: LinkedApi): Promise<void> {
164+
console.log('\n🔄 Enabling inbox monitoring...');
165+
166+
const workflow = await linkedapi.syncInbox.execute({});
167+
console.log('🔄 Sync inbox workflow started:', workflow.workflowId);
168+
console.log('💬 Workflow message:', workflow.message);
169+
170+
const syncResult = await linkedapi.syncInbox.result(workflow.workflowId);
171+
if (syncResult.errors.length > 0) {
172+
console.error('🚨 Errors:', JSON.stringify(syncResult.errors, null, 2));
173+
} else {
174+
console.log('✅ Inbox monitoring enabled successfully');
175+
console.log(' 📥 Whole inbox is now ready for polling');
176+
}
177+
}
178+
179+
async function salesNavigatorSyncInbox(linkedapi: LinkedApi): Promise<void> {
180+
console.log('\n🎯 Enabling Sales Navigator inbox monitoring...');
181+
182+
const workflow = await linkedapi.nvSyncInbox.execute({});
183+
console.log('🎯 Sales Navigator sync inbox workflow started:', workflow.workflowId);
184+
console.log('💬 Workflow message:', workflow.message);
185+
186+
const nvSyncResult = await linkedapi.nvSyncInbox.result(workflow.workflowId);
187+
if (nvSyncResult.errors.length > 0) {
188+
console.error('🚨 Errors:', JSON.stringify(nvSyncResult.errors, null, 2));
189+
} else {
190+
console.log('✅ Sales Navigator inbox monitoring enabled successfully');
191+
console.log(' 📥 Whole Sales Navigator inbox is now ready for polling');
192+
}
193+
}
194+
195+
async function pollInbox(linkedapi: LinkedApi): Promise<void> {
196+
console.log('\n📥 Polling inbox...');
197+
198+
const pollResponse = await linkedapi.pollInbox({
199+
type: 'st',
200+
since: '2025-01-01T00:00:00Z',
201+
});
202+
203+
if (pollResponse.errors.length > 0) {
204+
console.error('🚨 Errors:', JSON.stringify(pollResponse.errors, null, 2));
205+
return;
206+
}
207+
208+
const messages = pollResponse.data?.messages ?? [];
209+
console.log('✅ Inbox polled successfully');
210+
console.log(`📊 Found ${messages.length} messages`);
211+
212+
messages.slice(0, 5).forEach((message) => {
213+
const senderIcon = message.sender === 'us' ? '👤' : '👋';
214+
console.log(` ${senderIcon} ${message.sender.toUpperCase()} in thread ${message.threadId}: "${message.text}"`);
215+
console.log(` 🔗 Type: ${message.type === 'st' ? 'Standard' : 'Sales Navigator'} | 🕐 ${message.time}`);
216+
217+
// Reply straight into the thread using threadId (no personUrl needed).
218+
void linkedapi.sendMessage.execute({
219+
threadId: message.threadId,
220+
text: 'Thanks for your message!',
221+
});
222+
});
223+
}
224+
158225
if (require.main === module) {
159226
messagingExample();
160227
}

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@linkedapi/node",
3-
"version": "2.1.0",
3+
"version": "2.1.1",
44
"description": "Control your LinkedIn accounts and retrieve real-time data, all through this Node.js SDK.",
55
"main": "dist/index.js",
66
"types": "dist/index.d.ts",

src/core/operation.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ export const OPERATION_NAME = {
1717
customWorkflow: 'customWorkflow',
1818
sendMessage: 'sendMessage',
1919
syncConversation: 'syncConversation',
20+
syncInbox: 'syncInbox',
2021
checkConnectionStatus: 'checkConnectionStatus',
2122
sendConnectionRequest: 'sendConnectionRequest',
2223
withdrawConnectionRequest: 'withdrawConnectionRequest',
@@ -37,6 +38,7 @@ export const OPERATION_NAME = {
3738
retrievePerformance: 'retrievePerformance',
3839
nvSendMessage: 'nvSendMessage',
3940
nvSyncConversation: 'nvSyncConversation',
41+
nvSyncInbox: 'nvSyncInbox',
4042
nvSearchCompanies: 'nvSearchCompanies',
4143
nvSearchPeople: 'nvSearchPeople',
4244
nvFetchCompany: 'nvFetchCompany',

src/index.ts

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import {
1616
NvSearchPeople,
1717
NvSendMessage,
1818
NvSyncConversation,
19+
NvSyncInbox,
1920
ReactToPost,
2021
RemoveConnection,
2122
RetrieveConnections,
@@ -28,6 +29,7 @@ import {
2829
SendConnectionRequest,
2930
SendMessage,
3031
SyncConversation,
32+
SyncInbox,
3133
WithdrawConnectionRequest,
3234
} from './operations';
3335
import {
@@ -38,6 +40,8 @@ import {
3840
TApiUsageParams,
3941
TConversationPollRequest,
4042
TConversationPollResult,
43+
TInboxPollRequest,
44+
TInboxPollResult,
4145
TLinkedApiActionErrorType,
4246
TLinkedApiErrorType,
4347
} from './types';
@@ -92,6 +96,7 @@ class LinkedApi {
9296
this.customWorkflow = new CustomWorkflow(this.httpClient);
9397
this.sendMessage = new SendMessage(this.httpClient);
9498
this.syncConversation = new SyncConversation(this.httpClient);
99+
this.syncInbox = new SyncInbox(this.httpClient);
95100
this.checkConnectionStatus = new CheckConnectionStatus(this.httpClient);
96101
this.sendConnectionRequest = new SendConnectionRequest(this.httpClient);
97102
this.withdrawConnectionRequest = new WithdrawConnectionRequest(this.httpClient);
@@ -112,6 +117,7 @@ class LinkedApi {
112117
this.retrievePerformance = new RetrievePerformance(this.httpClient);
113118
this.nvSendMessage = new NvSendMessage(this.httpClient);
114119
this.nvSyncConversation = new NvSyncConversation(this.httpClient);
120+
this.nvSyncInbox = new NvSyncInbox(this.httpClient);
115121
this.nvSearchCompanies = new NvSearchCompanies(this.httpClient);
116122
this.nvSearchPeople = new NvSearchPeople(this.httpClient);
117123
this.nvFetchCompany = new NvFetchCompany(this.httpClient);
@@ -121,6 +127,7 @@ class LinkedApi {
121127
this.customWorkflow,
122128
this.sendMessage,
123129
this.syncConversation,
130+
this.syncInbox,
124131
this.checkConnectionStatus,
125132
this.sendConnectionRequest,
126133
this.withdrawConnectionRequest,
@@ -141,6 +148,7 @@ class LinkedApi {
141148
this.retrievePerformance,
142149
this.nvSendMessage,
143150
this.nvSyncConversation,
151+
this.nvSyncInbox,
144152
this.nvSearchCompanies,
145153
this.nvSearchPeople,
146154
this.nvFetchCompany,
@@ -242,6 +250,29 @@ class LinkedApi {
242250
*/
243251
public syncConversation: SyncConversation;
244252

253+
/**
254+
* Enable whole-inbox monitoring for standard LinkedIn messaging.
255+
*
256+
* This method enables monitoring of your entire standard LinkedIn inbox, preparing it for future
257+
* message polling with {@link pollInbox}. Unlike {@link syncConversation}, it is not scoped to a single
258+
* person: once enabled, all conversations in the inbox are monitored. This action takes no parameters
259+
* and returns no data.
260+
*
261+
* @param params - No parameters are required
262+
* @returns Promise resolving to the sync action
263+
*
264+
* @see {@link https://linkedapi.io/docs/working-with-conversations/ Working with Conversations Documentation}
265+
*
266+
* @example
267+
* ```typescript
268+
* const workflow = await linkedapi.syncInbox.execute({});
269+
*
270+
* await linkedapi.syncInbox.result(workflow.workflowId);
271+
* console.log("Inbox monitoring enabled and ready for polling");
272+
* ```
273+
*/
274+
public syncInbox: SyncInbox;
275+
245276
/**
246277
* Send a message to a LinkedIn user via Sales Navigator.
247278
*
@@ -293,6 +324,30 @@ class LinkedApi {
293324
*/
294325
public nvSyncConversation: NvSyncConversation;
295326

327+
/**
328+
* Enable whole-inbox monitoring for Sales Navigator messaging.
329+
*
330+
* This method enables monitoring of your entire Sales Navigator inbox, preparing it for future
331+
* message polling with {@link pollInbox}. Unlike {@link nvSyncConversation}, it is not scoped to a
332+
* single person: once enabled, all Sales Navigator conversations are monitored. This action takes no
333+
* parameters and returns no data. It can fail with a `noSalesNavigator` error if the account does not
334+
* have Sales Navigator.
335+
*
336+
* @param params - No parameters are required
337+
* @returns Promise resolving to the sync action
338+
*
339+
* @see {@link https://linkedapi.io/docs/working-with-conversations/ Working with Conversations Documentation}
340+
*
341+
* @example
342+
* ```typescript
343+
* const workflow = await linkedapi.nvSyncInbox.execute({});
344+
*
345+
* await linkedapi.nvSyncInbox.result(workflow.workflowId);
346+
* console.log("Sales Navigator inbox monitoring enabled and ready for polling");
347+
* ```
348+
*/
349+
public nvSyncInbox: NvSyncInbox;
350+
296351
/**
297352
* Poll multiple conversations to retrieve message history and new messages.
298353
*
@@ -374,6 +429,56 @@ class LinkedApi {
374429
}
375430
}
376431

432+
/**
433+
* Poll the monitored inbox to retrieve message history and new messages.
434+
*
435+
* This method reads messages from the inbox previously enabled for monitoring via {@link syncInbox}
436+
* and/or {@link nvSyncInbox}, using a direct HTTP request. Unlike {@link pollConversations}, it is not
437+
* scoped to specific person URLs: it returns messages across the whole monitored inbox, newest-first.
438+
* You can optionally filter by `since` timestamp, messaging `type`, and `threadId`.
439+
*
440+
* @param request - Optional filters: `since` timestamp, `type` ("st" | "nv"), and `threadId`
441+
* @returns Promise resolving to a response containing the inbox messages
442+
*
443+
* @see {@link https://linkedapi.io/docs/working-with-conversations/ Working with Conversations Documentation}
444+
*
445+
* @example
446+
* ```typescript
447+
* const pollResponse = await linkedapi.pollInbox({
448+
* type: "st",
449+
* since: "2025-01-01T00:00:00Z",
450+
* });
451+
*
452+
* if (pollResponse.data) {
453+
* pollResponse.data.messages.forEach((message) => {
454+
* console.log(`${message.sender} in ${message.threadId}: ${message.text}`);
455+
* });
456+
* } else {
457+
* console.error("Polling failed:", pollResponse.errors);
458+
* }
459+
* ```
460+
*/
461+
public async pollInbox(
462+
request: TInboxPollRequest = {},
463+
): Promise<TMappedResponse<TInboxPollResult>> {
464+
const response = await this.httpClient.post<TInboxPollResult>('/inbox/poll', request);
465+
if (response.success && response.result) {
466+
return {
467+
data: response.result,
468+
errors: [],
469+
};
470+
}
471+
return {
472+
data: undefined,
473+
errors: [
474+
{
475+
type: response.error?.type as TLinkedApiActionErrorType,
476+
message: response.error?.message ?? '',
477+
},
478+
],
479+
};
480+
}
481+
377482
/**
378483
* Retrieve detailed information about a LinkedIn person profile.
379484
*

src/operations/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ export * from './send-message';
77
export * from './nv-send-message';
88
export * from './sync-conversation';
99
export * from './nv-sync-conversation';
10+
export * from './sync-inbox';
11+
export * from './nv-sync-inbox';
1012
export * from './nv-fetch-person';
1113
export * from './nv-search-companies';
1214
export * from './fetch-post';

src/operations/nv-sync-inbox.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import { Operation, TOperationName } from '../core';
2+
import { VoidWorkflowMapper } from '../mappers';
3+
import { TNvSyncInboxParams } from '../types';
4+
5+
export class NvSyncInbox extends Operation<TNvSyncInboxParams, void> {
6+
public override readonly operationName: TOperationName = 'nvSyncInbox';
7+
protected override readonly mapper = new VoidWorkflowMapper<TNvSyncInboxParams>('nv.syncInbox');
8+
}

src/operations/sync-inbox.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import { Operation, TOperationName } from '../core';
2+
import { VoidWorkflowMapper } from '../mappers';
3+
import { TSyncInboxParams } from '../types';
4+
5+
export class SyncInbox extends Operation<TSyncInboxParams, void> {
6+
public override readonly operationName: TOperationName = 'syncInbox';
7+
protected override readonly mapper = new VoidWorkflowMapper<TSyncInboxParams>('st.syncInbox');
8+
}

src/types/actions/message.ts

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,30 @@
11
import { TBaseActionParams } from '../params';
22

33
export interface TSendMessageParams extends TBaseActionParams {
4-
personUrl: string;
4+
personUrl?: string;
55
text: string;
6+
threadId?: string;
67
}
78

89
export interface TSyncConversationParams extends TBaseActionParams {
910
personUrl: string;
1011
}
1112

13+
export interface TSyncInboxParams extends TBaseActionParams {}
14+
1215
export interface TNvSendMessageParams extends TBaseActionParams {
13-
personUrl: string;
16+
personUrl?: string;
1417
text: string;
15-
subject: string;
18+
subject?: string;
19+
threadId?: string;
1620
}
1721

1822
export interface TNvSyncConversationParams extends TBaseActionParams {
1923
personUrl: string;
2024
}
2125

26+
export interface TNvSyncInboxParams extends TBaseActionParams {}
27+
2228
export interface TConversationPollRequest {
2329
personUrl: string;
2430
since?: string;
@@ -30,6 +36,7 @@ export interface TMessage {
3036
sender: TMessageSender;
3137
text: string;
3238
time: string;
39+
threadId: string | null;
3340
}
3441

3542
export interface TConversationPollResult {
@@ -39,6 +46,26 @@ export interface TConversationPollResult {
3946
messages: TMessage[];
4047
}
4148

49+
export interface TInboxPollRequest {
50+
since?: string;
51+
type?: TConversationType;
52+
threadId?: string;
53+
}
54+
55+
export interface TInboxMessage {
56+
id: string;
57+
type: TConversationType;
58+
threadId: string;
59+
personUrl: string;
60+
sender: TMessageSender;
61+
text: string;
62+
time: string;
63+
}
64+
65+
export interface TInboxPollResult {
66+
messages: TInboxMessage[];
67+
}
68+
4269
export const CONVERSATION_TYPE = {
4370
st: 'st',
4471
nv: 'nv',

src/types/errors.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ import { TOperationName } from '../core';
2323
* - jobNotFound (fetchJob)
2424
* - commentingNotAllowed (commentOnPost)
2525
* - noPostingPermission (createPost, reactToPost, commentOnPost)
26-
* - noSalesNavigator (nvSendMessage, nvSyncConversation, nvSearchCompanies, nvSearchPeople, nvFetchCompany, nvFetchPerson)
26+
* - noSalesNavigator (nvSendMessage, nvSyncConversation, nvSyncInbox, nvSearchCompanies, nvSearchPeople, nvFetchCompany, nvFetchPerson)
2727
* - conversationsNotSynced (pollConversations)
2828
*/
2929
export const LINKED_API_ACTION_ERROR = {

0 commit comments

Comments
 (0)