Skip to main content
IHP includes built-in functionality for creating and running background jobs, perfect for tasks that can be split into small units and run in parallel.

Creating a Job

Use the “Background Job” option in the IHP IDE codegen tool to generate job code.

Implementing a Job

Jobs are created at <Application>/Job/<Name>.hs:
module Web.Job.EmailCustomers where
import Web.Controller.Prelude

instance Job EmailCustomersJob where
    perform EmailCustomersJob { .. } = do
        putStrLn "Hello World!"
The job type is generated from a table added to Schema.sql. Add columns to the table to include additional data in your job.

Example: Email Marketing Job

module Web.Job.EmailCustomers where
import Web.Controller.Prelude
import IHP.Mail (sendMail)

instance Job EmailCustomersJob where
    perform EmailCustomersJob { .. } = do
        customers <- query @Customer |> fetch
        forEach customers sendToCustomer
      where
        sendToCustomer customer = sendMail (MarketingMail customer)

Running Jobs

Immediate Execution

newRecord @EmailCustomersJob |> create

Scheduled Execution

Schedule a job for future execution:
import Data.Time.Clock (addUTCTime, getCurrentTime, nominalDay)

now <- getCurrentTime
newRecord @EmailCustomersJob 
    |> set #runAt (addUTCTime nominalDay now)  -- 24 hours from now
    |> create
IHP polls for scheduled jobs approximately every minute and executes jobs whose runAt time has passed.

Registering Workers

In Main.hs, register the workers:
import Web.Worker

instance Worker RootApplication where
    workers _ = workers WebApplication

Development vs Production

1

Development

Workers start automatically with the dev server.
2

Production

Build the RunJobs binary:
nix build .#optimized-prod-server
# or
nix build .#unoptimized-prod-server
Deploy result/bin/RunJobs alongside your app.

Viewing Job Status

Inspect job status in the database:
  • #status - Current job status
  • #lastError - Error message if the job failed

Configuration

Maximum Attempts

Default: 10 attempts
instance Job EmailCustomersJob where
    perform EmailCustomersJob { .. } = do
        customers <- query @Customer |> fetch
        forEach customers sendToCustomer
      where
        sendToCustomer customer = sendMail (MarketingMail customer)
    
    maxAttempts = 3

Backoff Strategy

Linear backoff (default: 30 seconds):
backoffStrategy :: BackoffStrategy
backoffStrategy = LinearBackoff { delayInSeconds = 5 }
Exponential backoff:
backoffStrategy :: BackoffStrategy
backoffStrategy = ExponentialBackoff { delayInSeconds = 5 }
With exponential backoff, delays are: 5s, 25s, 625s, etc.

Timeout

Default: No timeout
instance Job EmailCustomersJob where
    perform EmailCustomersJob { .. } = do
        -- job implementation
        pure ()
    
    timeoutInMicroseconds = Just $ 1000000 * 60  -- 1 minute
Timed out jobs are retried unless maxAttempts is set to 0.

Concurrency

Default: 16 concurrent jobs
instance Job EmailCustomersJob where
    perform EmailCustomersJob { .. } = do
        -- job implementation
        pure ()
    
    maxConcurrency = 1  -- Process one at a time

Scheduling with Cron

Create a script to trigger jobs:
#!/usr/bin/env run-script
module Application.Script.RunEmailCustomersJob where

import Application.Script.Prelude

run :: Script
run = do
    newRecord @EmailCustomersJob |> create
    pure ()
Build the script:
nix build .#optimized-prod-server
The script binary will be at result/bin/RunEmailCustomersJob. Add to crontab:
# Run daily at 5am
0 5 * * * root /path/to/your/app/result/bin/RunEmailCustomersJob
Use the full path to the binary in your cron entry.

Jobs Dashboard

IHP includes a built-in dashboard for viewing and managing jobs.

Quick Start

In Web/FrontController.hs:
import IHP.Job.Dashboard

type MyJobDashboard = JobsDashboardController
    (BasicAuthStatic "jobs" "jobs")
    '[]

instance FrontController WebApplication where
    controllers =
        [ startPage HomeView
        , parseRoute @ProductsController
        , parseRoute @MyJobDashboard  -- Mount at /jobs/
        ]
Link to the dashboard:
import IHP.Job.Dashboard

<a href={ListJobsAction}>Jobs</a>

Authentication Options

No Authentication:
type MyJobDashboard = JobsDashboardController NoAuth '[]
HTTP Basic Auth (static):
type MyJobDashboard = JobsDashboardController
    (BasicAuthStatic "username" "password")
    '[]
HTTP Basic Auth (environment variables):
type MyJobDashboard = JobsDashboardController BasicAuth '[]
Custom Authentication:
data CustomAuth
instance AuthenticationMethod CustomAuth where
    authenticate = do
        myCustomAuthenticateFunction

type MyJobDashboard = JobsDashboardController CustomAuth '[]

Custom Job Views

Specify jobs to display:
type MyJobDashboard = JobsDashboardController
    (BasicAuthStatic "jobs" "jobs")
    '[UpdateUserJob, CleanDatabaseJob, EmailCustomersJob]
Customize display by implementing DisplayableJob:
instance DisplayableJob EmailCustomersJob where
    makeDashboardSection = -- custom dashboard section
    makePageView = -- custom list page
    makeDetailView job = -- custom detail view
    makeNewJobView = -- custom creation form
    createNewJob = -- custom creation logic
See the API documentation for full details on customization.

Build docs developers (and LLMs) love