Billionmail/BillionMail · error

Failed to write DKIM sign config: %v

Error message

Failed to write DKIM sign config: %v

What it means

Thrown when public.WriteFile fails to persist the modified rspamd DKIM signing config after inserting the domain's DKIM block before #BT_DOMAIN_DKIM_END. The library aborts the signing-config update (and skips the rspamd restart) because leaving the file unwritten would desync signing configuration.

Source

Thrown at core/internal/service/domains/domains.go:628

			if public.FileExists(signConfPath) {
				signContent, err = public.ReadFile(signConfPath)
				if err != nil {
					err = fmt.Errorf("Failed to read DKIM sign config: %v", err)
					return
				}
			}

			// Remove old config block if it exists
			pattern := fmt.Sprintf(`(?s)#%s_DKIM_BEGIN.*?#%s_DKIM_END\s*`, domain, domain)
			signContent, err = gregex.ReplaceString(pattern, "", signContent)
			if err != nil {
				return
			}

			signContent = strings.Replace(signContent, "#BT_DOMAIN_DKIM_END", signConf+"\n#BT_DOMAIN_DKIM_END", 1)
			_, err = public.WriteFile(signConfPath, signContent)
			if err != nil {
				err = fmt.Errorf("Failed to write DKIM sign config: %v", err)
				return
			}

			// Restart rspamd service
			err = dk.RestartContainerByName(context.Background(), consts.SERVICES.Rspamd)
			if err != nil {
				err = fmt.Errorf("Failed to restart rspamd container: %v", err)
				return
			}
		}
	}

	// DKIM public key is typically stored in a specific location in the container or host
	// Assuming we use docker exec to read the DKIM public key from the rspamd container
	dkimPub, err := public.ReadFile(dkimPubPath)
	if err != nil {
		err = fmt.Errorf("Cannot read DKIM public key: %v", err)
		return

View on GitHub (pinned to fc36c76c05)

Solutions

  1. Free disk space (df -h) if ENOSPC, then re-run RepairDKIMSigningConfig
  2. Fix ownership/permissions on the config file and its directory so the writer can create/truncate it
  3. Check for immutable flag (lsattr/chattr -i) and read-only mounts
  4. Verify the path's parent directory exists and is writable

Example fix

// before
_, err = public.WriteFile(signConfPath, signContent)
// after: diagnose write failure explicitly
if errors.Is(err, syscall.ENOSPC) {
    log.Printf("disk full; free space before retrying DKIM config write")
}
_, err = public.WriteFile(signConfPath, signContent)
Defensive patterns

Strategy: try-catch

Validate before calling

fi, err := os.Stat(filepath.Dir(signConfPath)); if err != nil || !fi.IsDir() { return fmt.Errorf("config dir missing") }
if err := syscall.Access(filepath.Dir(signConfPath), os.O_RDWR); err != nil { return fmt.Errorf("config dir not writable") }

Type guard

func isWritable(path string) bool {
    f, err := os.OpenFile(path, os.O_WRONLY|os.O_APPEND, 0o644)
    if err != nil { return false }
    f.Close()
    return true
}

Try / catch

if _, err := public.WriteFile(signConfPath, signContent); err != nil {
    log.Printf("DKIM sign config write failed at %s: %v — check disk space, perms, read-only mounts", signConfPath, err)
    return err
}

Prevention

When it happens

Trigger: public.WriteFile(signConfPath, signContent) returns an error — read-only filesystem, disk full (ENOSPC), permission denied on write/open for truncate, parent directory missing, or immutable file attribute.

Common situations: Disk full on the mail server volume, config volume mounted read-only after a container update, rspamd config owned by _rspamd while BillionMail writes as another user, chattr +i set by hardening scripts.

Understand the failure class

Background: "failed to write file", "Could not save figure", "Error saving remote file" — file write failed: causes and fixes across languages and libraries — this error's family across 38 libraries.

Related errors


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