> ## Documentation Index
> Fetch the complete documentation index at: https://docs.evomarketing.co/llms.txt
> Use this file to discover all available pages before exploring further.

# Background jobs

> SolidQueue background job processing in EVO Dialed

Background jobs are processed by **SolidQueue** and defined in `app/jobs/`. Recurring schedules are configured in `config/recurring.yml`.

## Job categories

```
app/jobs/
  brands/           # Brand lifecycle automation
  campaigns/        # Campaign processing
  creators/         # Creator sync and automation
  metrics/          # Data sync and reporting
  notifications/    # Notification delivery
  integrations/     # External service sync
  leaderboards/     # Monthly leaderboard processing
  ...
```

## Daily jobs

These jobs run once per day:

| Job                                | Schedule     | Purpose                |
| ---------------------------------- | ------------ | ---------------------- |
| `Metrics::SyncJob`                 | 10pm ET      | Nightly scrape sync    |
| `Creators::TierSyncJob`            | 11am ET      | Tier promotions        |
| `Creators::BadgeSyncJob`           | 11:30am ET   | Badge evaluations      |
| `Creators::BenchmarkSyncJob`       | 11:15pm ET   | Performance rollup     |
| `Brands::PipelineAutoAdvanceJob`   | 6:30am ET    | Auto stage transitions |
| `CreatorSocials::SyncFollowersJob` | 1am ET       | Follower count sync    |
| `Leaderboards::FinalizeMonthJob`   | 1st of month | Monthly winners        |
| `Metrics::SlackDailyReportJob`     | 9am ET       | Daily metrics Slack    |

<Warning>
  **Scrape timing:** The nightly scrape runs at 10pm ET. Avoid deploying between 10pm-10:15pm ET to prevent sync interruption.
</Warning>

## Frequent jobs

These jobs run multiple times per day:

| Job                                     | Schedule     | Purpose                |
| --------------------------------------- | ------------ | ---------------------- |
| `Campaigns::RolloverActiveContractsJob` | Every 15 min | Placement rebalancing  |
| `Creators::WelcomeDmBackstopJob`        | Every 15 min | Welcome DM delivery    |
| `Metrics::SyncWatchdogJob`              | Every 15 min | Sync health monitoring |
| `Integrations::TokenRefreshJob`         | Every 30 min | OAuth token refresh    |
| `Metrics::OfficialSyncJob`              | Hourly       | Official API sync      |

## Key job implementations

### Metrics::SyncJob

The main scraping job that syncs creator content daily:

```ruby theme={null}
class Metrics::SyncJob < ApplicationJob
  queue_as :default

  def perform
    CreatorSocial.syncable.find_each do |social|
      posts = scrape_posts(social)
      Metrics::Ingestor.new.ingest_for_social(social, posts)
    end
  end
end
```

### Brands::PipelineAutoAdvanceJob

Automatically advances brands through pipeline stages:

```ruby theme={null}
class Brands::PipelineAutoAdvanceJob < ApplicationJob
  def perform
    # Active → Ending Soon (14 days before contract end)
    advance_to_ending_soon!
    
    # Contract complete → Up for Renewal
    advance_to_up_for_renewal!
    
    # First post → Active
    advance_to_active!
  end
end
```

### Creators::WelcomeDmBackstopJob

Ensures welcome DMs are delivered within 15 minutes:

```ruby theme={null}
class Creators::WelcomeDmBackstopJob < ApplicationJob
  def perform
    User.creator
        .where(onboarded_at: 15.minutes.ago..Time.current)
        .where(welcome_dm_sent: false)
        .find_each do |creator|
      Notifications::WelcomeDmSender.new(creator).perform!
    end
  end
end
```

## Job configuration

Jobs are configured in `config/recurring.yml`:

```yaml theme={null}
production:
  metrics_sync:
    class: Metrics::SyncJob
    schedule: "0 22 * * *"  # 10pm ET daily
    
  tier_sync:
    class: Creators::TierSyncJob
    schedule: "0 11 * * *"  # 11am ET daily
    
  rollover_contracts:
    class: Campaigns::RolloverActiveContractsJob
    schedule: "*/15 * * * *"  # Every 15 minutes
```

## Queue management

### Queue names

| Queue      | Purpose                       |
| ---------- | ----------------------------- |
| `default`  | Standard priority jobs        |
| `critical` | High priority (notifications) |
| `low`      | Background tasks              |

### Priority

```ruby theme={null}
class NotificationJob < ApplicationJob
  queue_as :critical
end

class ReportJob < ApplicationJob
  queue_as :low
end
```

## Error handling

Jobs use standard Rails error handling:

```ruby theme={null}
class Metrics::SyncJob < ApplicationJob
  retry_on StandardError, wait: :polynomially_longer, attempts: 3
  discard_on ActiveRecord::RecordNotFound

  def perform(social_id:)
    social = CreatorSocial.find(social_id)
    # ...
  end
end
```

## Monitoring

### Sync health

`Metrics::SyncWatchdogJob` monitors sync health:

* Checks for stuck syncs
* Alerts via Slack when issues detected
* Pings Rafael and Brandon on outages

### Kill switch

If the sync is causing issues:

```bash theme={null}
# Set environment variable to pause all syncs
SCRAPE_SYNC_PAUSED=1
```

## Running jobs locally

```bash theme={null}
# Run a specific job
bin/rails runner "Metrics::SyncJob.perform_now"

# Run SolidQueue worker
bin/rails solid_queue:work
```

## Testing jobs

Jobs are tested in `test/jobs/`:

```ruby theme={null}
class Brands::PipelineAutoAdvanceJobTest < ActiveSupport::TestCase
  test "advances brand to ending_soon 14 days before end" do
    brand = create_brand(
      pipeline_status: "active",
      contract_end: 14.days.from_now
    )
    
    Brands::PipelineAutoAdvanceJob.perform_now
    
    assert_equal "ending_soon", brand.reload.pipeline_status
  end
end
```
