Billionmail/BillionMail · error

download file failed:

Error message

download file failed: 

What it means

The DownloadFile helper in core/internal/service/public/common.go performs an HTTP GET and checks the response status. When the status code is not in the 2xx range (below 200 or >= 300), it returns "download file failed: <resp.Status>" instead of writing the body to dst. The remote response body is discarded, so the original server error text is only available via resp.Status.

Solutions

  1. Check the URL is correct and the asset still exists (curl -I the URL to see the status).
  2. For 403/429, add required auth headers or wait for the rate limit; consider passing an authenticated client to the download call.
  3. Verify network/proxy settings; use the returned resp.Status string to identify the exact HTTP code before retrying.
  4. For 301/302, ensure the final redirect target is reachable and does not loop.

Example fix

// before
err := public.DownloadFile("https://example.com/old-file.tar.gz", dst) // 404
// after
url := "https://example.com/new-file-v2.tar.gz"
if err := public.DownloadFile(url, dst); err != nil {
    log.Printf("download %s failed: %v", url, err)
    return fmt.Errorf("check asset exists at %s: %w", url, err)
}
Defensive patterns

Strategy: retry

Validate before calling

resp, err := http.Head(url)
if err != nil || resp.StatusCode != http.StatusOK {
    return fmt.Errorf("asset not downloadable, status: %d", resp.StatusCode)
}

Try / catch

var lastErr error
for attempt := 0; attempt < 3; attempt++ {
    err := public.DownloadFile(url, dst)
    if err == nil {
        return nil
    }
    lastErr = err
    if strings.Contains(err.Error(), "4") && !strings.Contains(err.Error(), "429") {
        break // client errors won't fix themselves
    }
    time.Sleep(time.Duration(attempt+1) * 2 * time.Second)
}
return lastErr

Prevention

When it happens

Trigger: Calling DownloadFile(url, dst) where the server replies 404 Not Found, 403 Forbidden, 500, 301/302 terminated as non-2xx, or any non-2xx status; also firewalled/proxied endpoints returning HTML error pages with 4xx/5xx codes.

Common situations: Downloading an installer/asset from a URL that was renamed or deleted, GitHub release asset URLs that moved, rate-limited endpoints returning 429, or self-signed/intercepting proxies returning 403.

Related errors


AI-assisted analysis of Billionmail/BillionMail@fc36c76c05 (2026-09-05). Data as JSON: /api/errors/d45586d1d4169639. Report an issue: GitHub.

Appendix: source

Thrown at core/internal/service/public/common.go:2050

	// Set request headers
	if headers != nil {
		for k, v := range headers {
			req.Header.Set(k, v)
		}
	}

	// Send request
	var resp *http.Response

	if resp, err = client.Do(req.WithContext(ctx)); err != nil {
		return
	}

	defer resp.Body.Close()

	// Download failed
	if resp.StatusCode < http.StatusOK && resp.StatusCode >= http.StatusMultipleChoices {
		return errors.New("download file failed: " + resp.Status)
	}

	// Create directory
	dir := filepath.Dir(dst)
	if !FileExists(dir) {
		if err = os.MkdirAll(dir, 0755); err != nil {
			return
		}
	}

	// Create temporary file
	tmpFile := dst + ".tmp"

	// Open file
	var fp *os.File
	if fp, err = os.OpenFile(tmpFile, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644); err != nil {
		return
	}

View on GitHub (pinned to fc36c76c05)