Cron scripts that loop profile_ids break at 50+ accounts — duplicate launches, orphaned browsers, no retry semantics. A queue + worker pool with profile leasing fixes that. This recipe uses Redis; swap for SQS, RabbitMQ, or PostgreSQL SKIP LOCKED with the same job schema.

Architecture

API / dashboard → LPUSH mlx:jobs
Worker pool     → BRPOP → lease profile → start → CDP job → stop → webhook
CMDB            → profile_id, tier, lease_until, ban_status

Job schema (JSON)

{
  "job_id": "uuid",
  "profile_id": "mlx-profile-uuid",
  "task": "sync_orders",
  "payload": {"shop_id": "acme_vn"},
  "attempt": 1,
  "max_attempts": 3,
  "callback_url": "https://ops.example/hooks/mlx"
}

Profile lease (prevent double launch)

import redis
import json
import time

r = redis.Redis(decode_responses=True)
LEASE_TTL = 900  # 15 min

def try_lease(profile_id: str, worker_id: str) -> bool:
    key = f"mlx:lease:{profile_id}"
    return r.set(key, worker_id, nx=True, ex=LEASE_TTL)

def release_lease(profile_id: str, worker_id: str) -> None:
    key = f"mlx:lease:{profile_id}"
    if r.get(key) == worker_id:
        r.delete(key)

Worker loop (asyncio + httpx)

import asyncio
import httpx

MLX = "https://api.multilogin.com"
TOKEN = "Bearer ..."
SEM = asyncio.Semaphore(4)

async def run_job(job: dict):
    profile_id = job["profile_id"]
    if not try_lease(profile_id, WORKER_ID):
        raise RetryLater("profile leased")

    async with SEM:
        async with httpx.AsyncClient(timeout=90) as client:
            start = await client.post(
                f"{MLX}/profile/start",
                json={"profile_id": profile_id, "headless": True},
                headers={"Authorization": TOKEN},
            )
            start.raise_for_status()
            cdp = start.json().get("cdp_url")
            try:
                await execute_task(cdp, job)  # your Playwright fn
            finally:
                await client.post(
                    f"{MLX}/profile/stop",
                    json={"profile_id": profile_id},
                    headers={"Authorization": TOKEN},
                )
                release_lease(profile_id, WORKER_ID)

async def worker_loop():
    while True:
        _, raw = r.brpop("mlx:jobs", timeout=5)
        if not raw:
            continue
        job = json.loads(raw)
        try:
            await run_job(job)
            post_webhook(job["callback_url"], {"job_id": job["job_id"], "status": "ok"})
        except RetryLater:
            r.lpush("mlx:jobs", raw)  # re-queue with delay in prod
        except Exception as e:
            if job["attempt"] < job["max_attempts"]:
                job["attempt"] += 1
                r.lpush("mlx:jobs", json.dumps(job))
            post_webhook(job["callback_url"], {"job_id": job["job_id"], "status": "fail", "error": str(e)})

Webhook callback (idempotent)

def post_webhook(url: str, body: dict):
    if not url:
        return
    httpx.post(url, json=body, timeout=10, headers={"X-MLX-Signature": sign(body)})

Receivers should dedupe on job_id. Sign payloads with HMAC secret shared with your ops dashboard. Full receiver: webhook receiver recipe.

Operational rules

Related

Disclosure: MLX-MMO affiliated with Multilogin.