Configuration

June 20, 2026 · View on GitHub

Configuration is split between environment variables (.env file), Django models (managed via interactive onboarding or Django Admin), and hardcoded defaults in linkedin/conf.py.

LLM Configuration (.env)

LLM settings are stored in .env (project root). Any OpenAI-compatible provider works. These are prompted during interactive onboarding if missing.

VariableDescriptionDefault
LLM_API_KEYAPI key for an OpenAI-compatible provider.(required)
AI_MODELModel identifier for qualification, follow-up, and search keyword generation.(required)
LLM_API_BASEBase URL for the API endpoint.(none)

These can also be set as environment variables directly.

Campaign Settings (Django Model)

Campaign data is stored in the Campaign Django model (with name and users M2M), managed via Django Admin (/admin/) or created during interactive onboarding.

FieldTypeDescription
product_docstextProduct/service description. Used by LLM qualification, follow-up agent, and search keyword generation.
campaign_objectivetextCampaign goal. Used by LLM qualification, follow-up agent, and search keyword generation.
booking_linkstringURL included in follow-up messages when suggesting a meeting.
is_freemiumbooleanWhether this is a freemium campaign (uses KitQualifier instead of BayesianQualifier).
action_fractionfloatTarget fraction of total connections for freemium campaigns.

Account Settings (Django Model)

Account data is stored in the LinkedInProfile Django model (1:1 with auth.User), managed via Django Admin or created during interactive onboarding.

FieldTypeDescriptionDefault
linkedin_usernamestringLinkedIn login email.(required)
linkedin_passwordstringLinkedIn password.(required)
activebooleanEnable/disable this account.true
subscribe_newsletterbooleanReceive OpenOutreach updates.true
connect_daily_limitintegerMax connection requests per day.20
follow_up_daily_limitintegerMax follow-up messages per day.30
legal_acceptedbooleanWhether the user accepted the legal notice.false

Rate limiting is enforced by LinkedInProfile methods (can_execute(), record_action(), mark_exhausted()) backed by the ActionLog model, surviving daemon restarts.

GDPR Location Detection

On the first run, the daemon checks the logged-in user's LinkedIn country code against a static set of ISO-2 codes for jurisdictions with opt-in email marketing laws (EU/EEA, UK, Switzerland, Canada, Brazil, Australia, Japan, South Korea, New Zealand).

  • Non-GDPR location: subscribe_newsletter is auto-set to true for that account.
  • GDPR-protected location: the existing value is preserved (no override).
  • Unknown/empty location: defaults to GDPR-protected (errs on the side of caution).

This check runs once per account (a database marker record prevents re-runs).

Email Channel Settings

The email channel (LinkedIn for discovery, email for outreach) is optional — with nothing configured, every qualified lead routes to the LinkedIn connection channel. A per-launch onboarding nudge (emails/nudge.py) walks you through the two pieces below until both exist.

Finder key (SiteConfig singleton)

The email finder is configured by a single key on the SiteConfig DB singleton, editable via Django Admin or captured by the onboarding nudge.

FieldTypeDescriptionDefault
bettercontact_api_keystringBetterContact API key for LinkedIn→work-email resolution. Blank disables the paid finder.(empty)

When set, a qualified lead's work email is resolved on demand (emails/bettercontact.py); a hit forks the deal onto the email channel, a miss leaves it on the LinkedIn channel. Misses are free to retry — the provider bills only usable hits. The first 50 lookups are free with the subscription, so you can try enrichment at no cost. Enrichment only runs when a sending mailbox exists — with no mailbox to send from, qualified leads route straight to LinkedIn and neither the hub lookup nor the paid finder is called.

Sending mailboxes (Mailbox Django model)

Each Mailbox is one SMTP outbox. Boxes are imported by pasting the IceMail Export Mailboxes sheet during onboarding (emails/icemail.py); each is auth-checked (emails/smtp.py) before it is stored.

FieldTypeDescriptionDefault
hoststringSMTP host.smtp.gmail.com
portintegerSMTP port.587
usernamestringSMTP login (unique).(required)
passwordstringSMTP password.(required)
from_addressstringEnvelope/from address for outgoing mail.(required)
daily_limitintegerWarm-safe sends per day for this box, enforced per box at send time.DEFAULT_EMAIL_DAILY_LIMIT

Sending is raw smtplib (emails/sender.py); the email queue drains eagerly, capped only by the pool-wide per-box daily headroom.

Hardcoded Defaults (conf.py:CAMPAIGN_CONFIG)

Timing and ML defaults are hardcoded in linkedin/conf.py. These are not user-configurable.

KeyValueDescription
check_pending_recheck_after_hours24Base interval (hours) before first pending check. Doubles per profile via exponential backoff.
enrich_min_delay_seconds6Min pause (seconds) between enrichment API calls during auto-discovery.
enrich_max_delay_seconds10Max pause (seconds) — actual delay is random.uniform(min, max).
enrich_max_per_page10Max profiles enriched per discovered page (DOM order, LinkedIn relevance).
burst_min_seconds2700Min work burst (45 min) before the daemon takes a human-rhythm break.
burst_max_seconds3900Max work burst (65 min). Actual burst is random.uniform(min, max).
break_min_seconds600Min break length (10 min) after each burst.
break_max_seconds1200Max break length (20 min).
min_action_interval120Minimum seconds between major actions.
qualification_n_mc_samples100Monte Carlo samples for BALD computation.
min_ready_to_connect_prob0.9GP probability threshold for promoting QUALIFIED to READY_TO_CONNECT.
min_positive_pool_prob0.20P(f > 0.5) threshold for positive pool check in exploit mode.
embedding_modelBAAI/bge-small-en-v1.5FastEmbed model for 384-dim profile embeddings.
connect_delay_seconds10Delay between connect tasks.
connect_no_candidate_delay_seconds300Delay when candidate pool is empty.
check_pending_jitter_factor0.2Multiplicative jitter factor for backoff.

Other constants: MIN_DELAY (5s) / MAX_DELAY (8s) for human-like wait timing.

See Templating for follow-up messaging configuration.