# Cmg Werknl Outreach Service (`obedient_stars/cmg-werknl-outreach-service`) Actor

Does outreach on werk.nl

- **URL**: https://apify.com/obedient_stars/cmg-werknl-outreach-service.md
- **Developed by:** [Synthwave Solutions](https://apify.com/obedient_stars) (community)
- **Categories:** Automation, Developer tools
- **Stats:** 2 total users, 1 monthly users, 0.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

Pay per usage

This Actor is paid per platform usage. The Actor is free to use, and you only pay for the Apify platform usage, which gets cheaper the higher subscription plan you have.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-usage

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## ConnectMeGroep Werk.nl Outreach Service - Comprehensive Documentation

### 1. Introduction

The Werk.nl Outreach Service is an automated solution that facilitates connecting with and messaging candidates on Werk.nl, which is a Dutch employment platform. Similar to LinkedIn's outreach capabilities but specifically adapted for the Werk.nl ecosystem, this service enables recruiters and employers to efficiently engage with potential candidates.

### 2. System Architecture

#### 2.1 High-Level Component Overview

This system is built as an Apify Actor (cloud-based automation platform) with these key components:

1. **Core Scraper Engine**: Browser automation to interact with Werk.nl
2. **Authentication System**: Handles login including two-factor authentication via Telegram
3. **Session Management**: Persists sessions using Firebase
4. **Outreach Workflow Engine**: Manages the state of candidate outreach
5. **Input/Output Processing**: Handles structured data for candidates

#### 2.2 Directory Structure

```
/
├── src/                       # Main source code
│   ├── lib/                   # Utility libraries and settings
│   ├── models/                # Data models for input/output
│   ├── services/              # Core service implementations
│   │   ├── BrowserAgentWerkNL/   # Browser automation implementation
│   │   ├── SeleniumAgentWerkNL/  # Selenium-based implementation
│   │   ├── WerkNLOutreachScraper.py # Main scraper implementation
│   │   ├── FirebaseManager.py # Manages cookie persistence
│   │   └── TelegramAuthHandler.py # Handles 2FA via Telegram
│   └── main.py                # Entry point for the Apify Actor
├── extension_unpacked/        # Unpacked Chrome extension
├── Dockerfile                 # Container configuration
├── requirements.txt           # Python dependencies
└── example_input.json/output.json # Example data formats
```

### 3. Core Components

#### 3.1 Data Models

The system uses Pydantic for data validation with these key models:

- **MessageData**: Stores subject and content for messages

- **CandidateInput**: Input structure for candidate outreach with fields:
  - `candidateId`: Unique identifier for the candidate
  - `outreach_status`: Current state of outreach ("start", "connected")
  - `fullName`: Candidate's name
  - `werkNlUrl`: URL to candidate's profile
  - `message`: Message data for communication

- **CandidateOutput**: Output structure tracking outreach results

#### 3.2 WerkNLOutreachScraper

This is the primary engine for interacting with Werk.nl using Playwright (browser automation). Key functionalities include:

##### 3.2.1 Authentication

- **perform_login()**: Handles the complete login flow
- Supports two-factor authentication through Telegram integration
- Navigates through Werk.nl's multi-step login process

##### 3.2.2 Profile Status Management

- **define_profile_status()**: Determines the current relationship status with a candidate:
  - `connection_unknown`: Status couldn't be determined
  - `connection_not_sent`: No connection has been sent
  - `connection_accepted`: Connected with the candidate
  - `connection_pending`: Connection request sent but not accepted
  - `connection_received`: Received a connection request from the candidate

##### 3.2.3 Messaging Capabilities

- **can_send_message()**: Checks if messaging is possible for a profile
- **send_message_to_profile()**: Sends personalized messages to connected candidates
- **\_type_with_delays()**: Types messages with random delays for natural interaction

##### 3.2.4 Connection Management

- **send_connection_request()**: Initiates connection with candidates
- **accept_connection()**: Accepts incoming connection requests
- **run_connect_message()**: Orchestrates the complete outreach workflow (connect and message)

#### 3.3 Firebase Integration

The `FirebaseManager` handles persistent storage of browser cookies to maintain login sessions between runs. This avoids the need for frequent logins and 2FA.

#### 3.4 Telegram Integration

The `TelegramAuthHandler` provides:

- Integration with a Telegram bot for 2FA code retrieval
- Notification capabilities to inform users about authentication status
- Secure handling of verification codes

#### 3.5 Main Entry Point

The `main.py` file orchestrates the workflow:

- Initializes the Actor environment
- Configures proxy settings for reliable access
- Loads input data containing candidates
- Initializes and manages the browser automation
- Handles cookie persistence with Firebase
- Processes candidates and returns results

### 4. Workflow Process

#### 4.1 Outreach Sequence

1. **Initialization**: Load candidate data and authentication information
2. **Authentication**: Login to Werk.nl (using stored cookies or credentials + 2FA if needed)
3. **Candidate Processing**:
   - For each candidate, determine current connection status
   - Based on status and required action, perform:
     - Send connection request
     - Send message to connected candidate
     - Accept incoming connection request
4. **Status Tracking**: Update candidate status after each action
5. **Result Collection**: Compile results and return updated candidate statuses

#### 4.2 Outreach States

The system tracks these states for each candidate:

- `start`: Initial state
- `connection_not_sent`: No connection initiated yet
- `connection_pending`: Connection request sent but pending approval
- `connection_received`: Connection request received from candidate
- `connection_accepted`: Connected and ready for messaging

### 5. Technical Implementation Details

#### 5.1 Browser Automation

The system uses:

- **Playwright**: For primary browser automation
- **Selenium**: Alternative implementation available in the codebase

#### 5.2 Security Features

- **Proxy Configuration**: Uses residential proxies from the Netherlands
- **Secure 2FA**: Telegram integration for receiving verification codes
- **Environment Variables**: Sensitive data stored in environment variables

#### 5.3 Container Support

The system is containerized with Docker for deployment on Apify platform.

#### 5.4 Input/Output Interface

- Accepts structured JSON input defining candidates and actions
- Returns JSON output with updated candidate statuses
- Compatible with Apify Actor ecosystem

### 6. Operational Considerations

#### 6.1 ChromeDriver Version Management

The system now includes automatic ChromeDriver version management to address version mismatch issues between Chrome and ChromeDriver when using `undetected-chromedriver`. This is implemented through:

1. **Auto-Download Compatible Versions**:
   - The `auto-download-undetected-chromedriver` package is integrated to dynamically fetch the correct ChromeDriver version matching the installed Chrome browser.
   - The implementation in `SeleniumAgentWerkNL/__init__.py` automatically:
     - Detects the OS and Chrome version
     - Downloads and patches the appropriate ChromeDriver version
     - Uses the compatible driver for browser automation

2. **Fallback Mechanism**:
   - If the auto-download process fails, the system falls back to the default ChromeDriver initialization without specifying a version.
   - This provides a more resilient approach to handling version compatibility.

3. **Cache Management**:
   - ChromeDriver executables are cached in a `chromedriver_cache` directory.
   - If you encounter persistent issues, manually delete cached ChromeDriver executables at:
     - Linux/macOS: `~/.undetected_chromedriver`
     - Windows: `%APPDATA%\undetected_chromedriver`

4. **Dependency Management**:
   - The required packages are specified in both `requirements.txt` and `pyproject.toml`.
   - The Docker container will automatically install these dependencies.

#### 6.2 Prerequisites

- Werk.nl credentials
- Telegram bot configuration for 2FA
- Firebase configuration for cookie persistence
- Apify proxy configuration

#### 6.3 Limitations

- Dependent on Werk.nl's interface structure
- Subject to Werk.nl's rate limiting and automation policies
- Requires proper configuration of Telegram bot for 2FA
- Network reliability affected by proxy quality

### 7. Usage Examples

#### 7.1 Basic Outreach Flow

```json
{
  "salesObject": {
    "candidates": [
      {
        "candidateId": "7329638",
        "outreach_status": "start",
        "fullName": "Jan Jansen",
        "werkNlUrl": "https://www.werk.nl/werkgevers/mijn-werknl/cvs-zoeken/7329638",
        "message": {
          "subject": "Connectieverzoek",
          "content": "Hallo Jan, ik ben geïnteresseerd in je profiel. Wanneer zou je tijd hebben voor een gesprek?"
        }
      }
    ],
    "user_id": "recruiter1"
  }
}
```

This documentation provides a comprehensive overview of the Werk.nl Outreach Service, explaining its architecture, components, workflow, and operational aspects in detail.

# Actor input Schema

## `salesObject` (type: `object`):

Sales to start with

## Actor input object example

```json
{
  "salesObject": {
    "candidates": [
      {
        "candidateId": "candidate id",
        "outreach_status": "'start','messaged'",
        "fullName": "Full name of the candidate",
        "werkNlUrl": "Werk NL profile url of the candidate",
        "message": {
          "subject": "Subject of the message",
          "content": "Content of the message"
        }
      }
    ],
    "user_id": {
      "title": "User ID",
      "type": "string",
      "description": "User ID to use for the outreach"
    }
  }
}
```

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "salesObject": {
        "candidates": [
            {
                "candidateId": "candidate id",
                "outreach_status": "'start','messaged'",
                "fullName": "Full name of the candidate",
                "werkNlUrl": "Werk NL profile url of the candidate",
                "message": {
                    "subject": "Subject of the message",
                    "content": "Content of the message"
                }
            }
        ],
        "user_id": {
            "title": "User ID",
            "type": "string",
            "description": "User ID to use for the outreach"
        }
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("obedient_stars/cmg-werknl-outreach-service").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = { "salesObject": {
        "candidates": [{
                "candidateId": "candidate id",
                "outreach_status": "'start','messaged'",
                "fullName": "Full name of the candidate",
                "werkNlUrl": "Werk NL profile url of the candidate",
                "message": {
                    "subject": "Subject of the message",
                    "content": "Content of the message",
                },
            }],
        "user_id": {
            "title": "User ID",
            "type": "string",
            "description": "User ID to use for the outreach",
        },
    } }

# Run the Actor and wait for it to finish
run = client.actor("obedient_stars/cmg-werknl-outreach-service").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "salesObject": {
    "candidates": [
      {
        "candidateId": "candidate id",
        "outreach_status": "'\''start'\'','\''messaged'\''",
        "fullName": "Full name of the candidate",
        "werkNlUrl": "Werk NL profile url of the candidate",
        "message": {
          "subject": "Subject of the message",
          "content": "Content of the message"
        }
      }
    ],
    "user_id": {
      "title": "User ID",
      "type": "string",
      "description": "User ID to use for the outreach"
    }
  }
}' |
apify call obedient_stars/cmg-werknl-outreach-service --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,obedient_stars/cmg-werknl-outreach-service"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/VfVGZap6sKLfoh2ws/builds/9u4dc0is9hklblXts/openapi.json
