- Set up identities and connect LinkedIn accounts
- Source and manage leads
- Send connection requests and messages at scale
- Handle replies, de-duplication, and errors
- Respect rate limits and best practices
Recommended setup: Use Engagement Identities for outreach actions. They’re credit-free for connection requests, messages, and profile visits.
Section 1: Prerequisites & Key Concepts
Before building your sequence, familiarize yourself with these core concepts:| Concept | What it is | Documentation |
|---|---|---|
| Workspace | Isolated environment with its own API key and identities | Core Concepts |
| Identity | Represents a user whose LinkedIn account will perform actions | Add & Manage Identities |
| Engagement Identity | Special identity type with credit-free outreach actions | Engagement Identities |
| Identity Modes | direct, auto, managed - controls which account performs actions | Identity Modes |
| Smart Limits | Per-action, per-identity daily limits to protect accounts | LinkedIn Smart Limits |
| Execution Modes | live (sync), async (background), schedule (recurring) | Execute Actions |
| Callbacks | Webhook delivery for async/scheduled action results | Callbacks |
Identity Setup Checklist
1
Create an Engagement Identity
Use the Create Identity API with
is_engagement: true2
Connect LinkedIn
Use Connect Integration with cookies, email/password, or login links
3
Set up Webhooks
Monitor integration status changes like
AUTH_EXPIRED. See LinkedIn Integration WebhooksSection 2: Lead Sourcing
Leads can come from Edges search actions or external sources like CSV uploads.Option A: Edges Search
Option B: External Sources
Import leads from your CRM, CSV, or other sources. Required fields:linkedin_profile_url(required) - e.g.,https://www.linkedin.com/in/johndoefull_name(recommended for personalization)company_name(recommended for personalization)
Section 3: Get User’s LinkedIn Profile ID
The user’slinkedin_profile_id is needed to detect replies (comparing who sent the last message).
Alternative: If you need additional profile details (headline, company, etc.), you can use linkedin-me which returns full profile information.
Section 4: Database Schema (Pseudocode)
Track leads, conversations, and outreach history:Section 5: Lead Lifecycle State Machine
Visualize how leads progress through your sequence:Part A: Main Outreach Flow
The happy path from new lead to reply:Part B: Edge Cases (Already Connected / Existing Conversation)
Handle leads who are already in your network or have messaged first:Part C: Identity Failure Recovery
When LinkedIn auth expires, pause affected leads and resume when fixed:Edge Cases Summary
| Scenario | Detection | Action |
|---|---|---|
| Lead already connected | extract-connections contains lead | Skip connect, go to ReadyToMessage |
| Recent conversation exists | last_message within 3 days | Wait for cooldown before messaging |
| Lead messaged first | extract-conversations shows lead initiated | Mark as REPLIED, human takes over |
| Identity auth expired | Webhook AUTH_EXPIRED event | PAUSE all leads for that identity |
Section 6: Action Limits Quick Reference
Each action in the sequence has specific daily limits. Link to the action doc for full response schema.| Step | Action | Limit (Classic / Sales Nav) | Action Doc |
|---|---|---|---|
| Visit | linkedin-visit-profile | 80 / 500 per day | View → |
| Connect | linkedin-connect-profile | 25 / 30 per day | View → |
| Message | linkedin-message-profile | 50 / 250 per day | View → |
| InMail | linkedin-inmail-profile | Based on subscription | View → |
| Extract Conversations | linkedin-extract-conversations | Free with Engagement | View → |
| Extract Connections | linkedin-extract-connections | 30,000 per day | View → |
Section 7: Error Labels for Outreach Actions
Each action returns specific error labels. Handle these in your code.| Error Label | Actions Affected | Meaning | How to Handle |
|---|---|---|---|
LIMIT_REACHED | All | Daily Smart Limit hit | Schedule retry for tomorrow, use postponed_until from response |
NOT_CONNECTED | message-profile | Can’t message non-connection | Send connection request first |
ALREADY_CONNECTED | connect-profile | Already 1st degree connection | Skip to message step |
INVITATION_PENDING | connect-profile | Connection request already sent | Wait for accept or withdraw |
PROFILE_NOT_ACCESSIBLE | All | Profile deleted, blocked, or URL changed | Remove from sequence or re-enrich |
AUTH_EXPIRED | All | Identity LinkedIn session expired | Pause sequence, trigger re-auth webhook |
INVALID_INPUT | All | Bad parameters (wrong URL format, etc.) | Fix input, check against action docs |
STATUS_429 | All | LinkedIn raw rate limit (not guarded) | Exponential backoff, wait and retry |
Section 8: Optimized Sync Flow (last_message optimization)
Thelast_message field in extract-conversations contains the sender’s linkedin_profile_id. This lets you detect replies without calling extract-messages.
extract-messages:
- Building a Lemlist-like inbox UI - Display full conversation threads to users in your app
- Full conversation history for CRM sync
- Message analytics/sentiment analysis
- Compliance/audit requirements
Section 9: Sending Outreach Actions (Complete Examples)
Send a Connection Request
Send a Message
Section 10: De-duplication Logic
Before sending any outreach, check for existing conversations and pending requests.Section 11: Business Hours & Timezone Sending
Send messages during the lead’s business hours for better response rates.Section 12: Callback Correlation with custom_data
When using async execution, passcustom_data to correlate callbacks with your leads.
Section 13: Rate Limits & Scaling
Understand both types of limits:1. LinkedIn Smart Limits (per identity, per action)
These protect individual LinkedIn accounts from restrictions:- 25-30 connections/day
- 50-250 messages/day
- See LinkedIn Smart Limits →
2. API Rate Limits (per workspace)
Based on your Edges plan tier. See API Rate Limits →Choosing the Right Execution Mode
Choose based on when and how you need the action executed:| Mode | Use Case | Example |
|---|---|---|
live | Real-time user action, need immediate result | User clicks “Send Message” in your UI |
async | Background bulk operations, process callback later | Import 500 leads and message them all |
schedule | Recurring automation on a schedule | Daily sync of connections at 9am |
Section 14: When to Sync Data
Knowing when to sync connections and conversations is critical for accurate outreach.Connections Extraction
| When | Why | Mode |
|---|---|---|
| After identity connects LinkedIn | Initial cache of existing connections | live or async |
| Daily (e.g., 9am) | Catch newly accepted requests | schedule |
| Before starting a new lead | Check if already connected | Part of lead import flow |
Conversations Extraction
| When | Why | Mode |
|---|---|---|
| Before sending any message | Check for replies (sync-before-send) | live |
| Every 1-4 hours | Update conversation cache, detect new replies | schedule or cron |
| On demand (inbox UI) | Show user their latest conversations | live |
Section 15: Sync-Before-Send Pattern (Race Condition Prevention)
Problem: If you sync at 8:00am but send at 8:05am, lead could have replied at 8:03am. Solution: Always sync immediately before sending:Section 16: Identity Failure Recovery
Prerequisite: Set up integration webhooks. See Monitor LinkedIn Integration Status → When identity auth expires (webhook event:AUTH_EXPIRED):
Section 17: Handling Edge Cases (Already Connected, Lead Messaged First)
When adding a lead to a sequence, check for existing relationships:Section 18: Complete Orchestrator Example
This ties everything together. A daily cron job that processes all leads for all identities.- Set up a cron job:
0 8 * * * python run_daily_outreach.py - Or use a job queue (Bull, Celery, etc.) for better control
Section 19: Testing Strategy
Challenge: Can’t spam real LinkedIn users during development.Approach 1: Test Accounts
- Create 2-3 LinkedIn test accounts (personal accounts you control)
- Use these as “leads” for end-to-end testing
- Verify messages arrive, connections work
Approach 2: Dry Run Mode
Approach 3: Staging Environment
- Use a separate Edges workspace for testing
- Connect test identities only
- Isolates production data
Approach 4: Unit Test Mocks
Section 20: API Reference & SDK
TypeScript SDK
For a cleaner developer experience, use the official TypeScript SDK:Key Actions Reference
Don’t duplicate schemas - refer to live action docs for full request/response examples:| Action | Key Fields You Need | Live Docs |
|---|---|---|
linkedin-me | linkedin_profile_id (user’s own ID) | View → |
linkedin-extract-conversations | last_message.linkedin_profile_id, linkedin_thread_id | View → |
linkedin-extract-connections | linkedin_profile_id, connected_at | View → |
linkedin-message-profile | linkedin_thread_id (returned on success) | View → |
linkedin-connect-profile | Success/failure status | View → |
linkedin-visit-profile | Success/failure status | View → |
linkedin-search-people | linkedin_profile_url, full_name, headline | View → |
Summary
You now have everything needed to build a production-ready LinkedIn outreach sequence:| Feature | Section |
|---|---|
| Identity setup & concepts | 1 |
| Lead sourcing | 2 |
| User profile ID for reply detection | 3 |
| Database schema | 4 |
| State machine for lead lifecycle | 5 |
| Action limits | 6 |
| Error handling per action | 7 |
Optimized sync with last_message | 8 |
| Sending messages & connections | 9 |
| De-duplication logic | 10 |
| Business hours sending | 11 |
| Callback correlation | 12 |
| Rate limits & scaling | 13 |
| Sync-before-send pattern | 14 |
| Identity failure recovery | 15 |
| Edge case handling | 16 |
| Complete orchestrator | 17 |
| Testing strategies | 18 |
| API reference & SDK | 19 |

