Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/scooller/Leben-site/llms.txt

Use this file to discover all available pages before exploring further.

Every time a visitor submits the public contact form, iLeben creates a ContactSubmission record and immediately dispatches a CreateSalesforceCaseJob to push it to Salesforce as a Lead. The job runs in the background on the database queue and includes multi-attempt retry logic that handles invalid field errors, OAuth reconnection, and flow-triggered owner errors — all without losing the submission or requiring manual intervention in the common case.

Contact Channels (ContactChannel)

Contact channels define the source context of a submission (for example, a sale inquiry vs. an informational request). The ContactChannel model is synced from Salesforce and surfaced in the Filament panel as color-coded badges on contact submission rows. Each channel can carry domain_patterns — a list of domain strings that SalesforceCaseMapper uses to automatically resolve the Website field when mapping a lead payload. This means the correct originating website is included in the Salesforce lead without any manual input from the user.

Lead Creation Flow

POST /api/v1/contact-submissions


ContactSubmission created in DB


CreateSalesforceCaseJob dispatched

        ├─► Check: isSalesforceOAuthMarkedDisconnected() OR !Forrest::hasToken()
        │       └─► tryAutoReconnect() → restore backup → Forrest::refresh()

        ├─► SalesforceCaseMapper::mapLead($submission)
        │       ├─ Resolve project (by salesforce_id, name, or slug)
        │       ├─ Map UTM fields (source, medium, campaign, content, term)
        │       ├─ Resolve Website from channel domain_patterns or utm_site
        │       └─ Build Lead payload

        ├─► SalesforceService::createLead($payload)
        │       ├─ sanitizeLeadPayloadWithCreatableFields() (Forrest::describe cache)
        │       ├─ sanitizeLeadPayloadWithKnownUnavailableFields()
        │       ├─ POST → Forrest::sobjects('Lead')
        │       ├─ On "No such column" error → remove field → retry
        │       ├─ On CANNOT_EXECUTE_FLOW_TRIGGER (blank owner) → inject OwnerId → retry
        │       └─ On token error → throw SalesforceTokenExpiredException

        └─► ContactSubmission updated with salesforce_case_id or salesforce_case_error
CreateSalesforceCaseJob skips retries and records an error immediately when OAuth is marked as disconnected and tryAutoReconnect() fails. This prevents the queue from accumulating hundreds of failed retry attempts while the token is genuinely invalid. Once you reconnect from /admin/site-settings, you can re-sync submissions individually from the Filament panel.

UTM Mapping

SalesforceCaseMapper maps the following UTM parameters from the fields JSON of the submission to the Salesforce Lead payload:
Submission FieldAliasesSalesforce Field
utm_sourcelead_sourceutm_source__c, Medio_de_Llegada__c
utm_mediumaudiencia, medio_de_llegadautm_medium__c, Audiencia__c
utm_campaigncampana, nombre_de_la_campanautm_campaign__c, Nombre_de_la_Campa_a__c
utm_contentpieza_graficautm_content__c, Pieza_Grafica__c
utm_termaudienciautm_term__c
utm_sitewebsite, sitio_web, referrerWebsite
Default values for utm_source, utm_medium, utm_campaign, and utm_content can be configured in SiteSetting.extra_settings (keys: utm_source_default, utm_medium_default, utm_campaign_default, utm_content_default). These defaults are bypassed for CSV import submissions.

Case vs Lead: Enabling Each Type

Two environment variables independently control whether Leads and/or Cases are created:
SF_LEAD_ENABLED=true
SF_CASE_ENABLED=true
CreateSalesforceCaseJob reads SF_LEAD_ENABLED (with SF_CASE_ENABLED as a fallback) and exits immediately if both are false. When SF_LEAD_ENABLED=true, the job calls SalesforceCaseMapper::mapLead() and SalesforceService::createLead().

Lead Field Configuration

The following environment variables configure the Salesforce fields used when creating Leads and Cases:
VariablePurpose
SF_LEAD_OWNER_IDSalesforce User ID to assign as lead owner
SF_LEAD_STATUSDefault lead status (e.g., En Contacto)
SF_CASE_RECORD_TYPE_IDRecord Type ID for Case creation
SF_CASE_OWNER_IDSalesforce User or Queue ID for Case ownership (also used as Lead owner fallback)
SF_CASE_SOURCE_IDCase source reference
SF_LEAD_OWNER_ID=005xxxxxxxxxxxxxxx
SF_LEAD_STATUS='En Contacto'
SF_CASE_RECORD_TYPE_ID=012xxxxxxxxxxxxxxx
SF_CASE_OWNER_ID=005xxxxxxxxxxxxxxx
SF_CASE_SOURCE_ID=

SalesforceCaseMapper: Field Resolution

SalesforceCaseMapper::mapLead() resolves each field through ordered alias lists, falling back gracefully when fields are absent. Key behaviors:
  • Proyect_ID__c (legacy field): Receives the human-readable project name (not the Salesforce ID) for backward compatibility with legacy Salesforce flows that rely on this field.
  • Proyecto__c and ID_Proyecto__c: Receive the normalized Salesforce ID of the resolved project.
  • Company: Always set to an empty string. The field is required by Salesforce’s Lead object, but iLeben captures B2C consumers without a company affiliation.
  • GenderIdentity and Genero__c: Always default to OTRO unless overridden by a field in the submission.
  • Custom __c fields (except those in the exclusion list): Values are normalized by replacing spaces with underscores — a legacy compatibility measure for Salesforce picklist values.

Contact Import (CSV Wizard)

The Filament panel provides a CSV import wizard for bulk-loading historical contact submissions. The wizard enforces the following canonical field names and normalizations:
Field key normalization — all incoming CSV column headers are normalized to lowercase ASCII with underscores (_) replacing spaces and special characters.Canonical key: rango_renta — any legacy aliases (e.g., rango de renta, renta_liquida) are mapped to rango_renta during import. Existing records with legacy keys can be backfilled with:
# Preview changes without persisting
php artisan contact:normalize-rango-renta-key --dry-run

# Apply normalization to existing records
php artisan contact:normalize-rango-renta-key
Canonical key: apellido — the single canonical key for last name. The alias apellidos is merged into apellido during import.Phone normalization — phone numbers are stored as digits only, stripping +, spaces, and all non-numeric characters.UTM enrichment — during import, UTM fields (utm_source, utm_medium, utm_campaign, utm_content, utm_term) are resolved from marketing aliases in the CSV and attached to the submission before the Salesforce sync job is dispatched.
The wizard requires selecting a ContactChannel before defining column mappings. The channel selection affects how the lead payload is built — specifically the Website field resolution and lead source defaults.

Activity Log and Re-sync

Every ContactSubmission record in the Filament panel displays:
  • salesforce_case_id — the Salesforce Lead ID returned on success.
  • salesforce_case_error — the error message if the last sync attempt failed.
  • salesforce_synced_at — the timestamp of the last sync attempt.
  • salesforce_sync_trigger — whether the sync was automatic (queue job on form submit) or manual (triggered from the panel).
From the Filament Contact Submissions resource, operators can re-trigger the Salesforce sync for one or more submissions using the Bulk Sync to Salesforce action. This dispatches a fresh CreateSalesforceCaseJob with syncTrigger = 'manual' for each selected record.
Use the bulk re-sync action after reconnecting OAuth to flush any submissions that were recorded with a salesforce_case_error during the disconnection window.

Build docs developers (and LLMs) love