Hangfire AutomaticRetry for Sume: stable key, no double bill

Hangfire retries failed jobs 10 times by default. Cap attempts on a Sume submit job and send one stable Idempotency-Key so a retry never makes a second job.

5 min readSume
All posts

Hangfire retries a failed background job automatically, so a Sume submit method needs two things: a stable Idempotency-Key built from your own record and an explicit [AutomaticRetry] limit. Hangfire's docs say the filter applies globally with 10 retry attempts by default and that [AutomaticRetry(Attempts = 0)] turns retries off for a method (read 2026-10-04). Ten tries at a 402 is ten pointless requests, and ten tries at a timeout without an idempotency key is a path to duplicate jobs.

With the same key on each attempt, Sume replays the original submit instead of creating a second job, per Jobs and results.

What does the job class look like?

Throw for retryable statuses so Hangfire retries, and return normally for permanent ones so it does not.

using System.Net;
using System.Net.Http;
using System.Net.Http.Json;
using Hangfire;

public class SumeSubmit(HttpClient http)
{
    [AutomaticRetry(Attempts = 4, DelaysInSeconds = new[] { 10, 30, 120, 300 })]
    public async Task SubmitHero(string sku)
    {
        var key = Environment.GetEnvironmentVariable("SUME_API_KEY")
            ?? throw new InvalidOperationException("SUME_API_KEY missing");
        using var req = new HttpRequestMessage(HttpMethod.Post,
            "https://api.sume.com/v1/image-1.0/generate");
        req.Headers.Add("x-api-key", key);
        req.Headers.Add("Idempotency-Key", $"hero-{sku}-v1");
        req.Content = JsonContent.Create(new { prompt = $"Product hero shot, {sku}", mode = "async" });
        using var res = await http.SendAsync(req);
        if (res.IsSuccessStatusCode) return;
        var transient = res.StatusCode == (HttpStatusCode)429 || (int)res.StatusCode >= 500;
        if (transient) throw new HttpRequestException($"sume {(int)res.StatusCode}");
        // 400, 402, 403: retrying cannot help; record it and stop
        Console.Error.WriteLine($"sume {(int)res.StatusCode}: {await res.Content.ReadAsStringAsync()}");
    }
}

Which retries are safe?

Hangfire retry behavior for a Sume submit, from Hangfire and Sume docs read 2026-10-04
OutcomeThrow?Reason
2xxNoJob created; store request_id
429 or 5xxYesTransient; the same key replays safely
409 idempotency_key_in_useYesThe earlier request is still in flight
402 or 403NoFunding or scope; a person must act
400NoThe same body will fail again
Timeout or network errorYesThe earlier request may have been accepted; the key protects you

Where do results come from?

Keep this job to the submit. Take the result from a signed webhook or from a separate polling job that reads /v1/jobs/{id}/status using next_poll_after_seconds. Mixing a long wait into a retried job means a retry can start while the first attempt is still waiting.

What should you configure around it?

  • Register HttpClient through IHttpClientFactory with a 30 second timeout.
  • Use a different attribute value, or Attempts = 0, on any method that calls Sume without a key.
  • Log the x-sume-request-id header on failures to quote to support.

Sources

Related posts

More in Developers

All Developers posts

Written by Sume