Savannah Cloud

Worker Templates

Pre-built job types available out of the box and how to implement custom handlers.

Worker templates are pre-defined job types that can be submitted directly from the dashboard's Templates page or via POST /jobs. Each template maps a Type string to a specific worker handler in worker.go.

Built-in Templates

TemplateJob TypeKey Payload Fields
Send Emailsend_emailto, subject, body
WhatsApp Messagesend_whatsappto, message
Webhookwebhookurl, payload
Generate PDFgenerate_pdftemplate, data
Image Compressioncompress_imageurl, width, quality
AI Summarizerai_summarizetext, length
M-Pesa STK Pushmpesa_stkphone, amount, ref
Slack Notifyslack_notifychannel, text

Submitting a Template Job

# Send an email
curl -X POST http://localhost:8080/jobs \
  -H "X-API-Key: rtq_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "ID": "email-001",
    "Type": "send_email",
    "Payload": {
      "to": "user@example.com",
      "subject": "Welcome to Savannah Cloud",
      "body": "Your account is ready."
    },
    "MaxRetries": 3
  }'

# M-Pesa STK Push
curl -X POST http://localhost:8080/jobs \
  -H "X-API-Key: rtq_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "ID": "mpesa-001",
    "Type": "mpesa_stk",
    "Payload": {
      "phone": "254700000000",
      "amount": 100,
      "ref": "INV-001"
    },
    "MaxRetries": 2
  }'

Implementing Handlers

Template handlers live in worker.go inside the Process function switch statement. Add a case for each job type you want to execute:

// worker.go — inside the Process function switch
case "send_email":
    to := payload["to"].(string)
    subject := payload["subject"].(string)
    body := payload["body"].(string)
    // Call Resend / SMTP here
    err = sendEmailViaResend(to, subject, body)

case "send_whatsapp":
    to := payload["to"].(string)
    message := payload["message"].(string)
    // Call Twilio / WhatsApp Cloud API here
    err = sendWhatsApp(to, message)

case "mpesa_stk":
    phone := payload["phone"].(string)
    amount := payload["amount"].(float64)
    ref := payload["ref"].(string)
    // Call Safaricom Daraja API here
    err = triggerMpesaSTK(phone, int(amount), ref)

case "slack_notify":
    channel := payload["channel"].(string)
    text := payload["text"].(string)
    err = postToSlack(channel, text)

If no case matches the job type, the worker logs an unknown-type warning and marks the job as failed with 0 retries consumed.


Adding a Custom Job Type

  1. Choose a unique Type string (e.g., resize_video)
  2. Add a case in worker.go
  3. Push jobs using that type via POST /jobs
case "resize_video":
    videoURL := payload["url"].(string)
    width := int(payload["width"].(float64))
    err = resizeVideo(videoURL, width)
curl -X POST http://localhost:8080/jobs \
  -H "X-API-Key: rtq_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "ID": "video-001",
    "Type": "resize_video",
    "Payload": {"url": "https://cdn.example.com/video.mp4", "width": 720},
    "MaxRetries": 1
  }'

Retry Behaviour

  • MaxRetries: 0 — the job runs once; failure marks it failed immediately
  • MaxRetries: N — the job is re-enqueued up to N additional times on failure
  • Each attempt is logged as a separate entry in job_logs
  • The current retry count is visible in the job detail view on the dashboard

On this page