Billionmail/BillionMail · error

retention_days must be a number

Error message

retention_days must be a number

What it means

RETENTION_DAYS must parse as an integer via strconv.Atoi; validateConfigValue rejects it otherwise. This value controls how long maillog/statistics data is retained, and a non-numeric value would break downstream numeric comparisons and cleanup jobs.

Source

Thrown at core/internal/controller/settings/settings.go:239

		if !public.IsValidCIDR(value) {
			return fmt.Errorf("IPv4 network format is incorrect, please use CIDR format (e.g. 192.168.1.0/24)")
		}

	case "TZ", "timezone":
		// Timezone: check if it is a valid timezone
		if !public.IsValidTimezone(value) {
			return fmt.Errorf("invalid timezone")
		}

	case "FAIL2BAN_INIT", "fail2ban":
		// fail2ban: only allowed y/n or 1/0
		if value != "y" && value != "n" && value != "1" && value != "0" {
			return fmt.Errorf("fail2ban value can only be y/n or 1/0")
		}
	case "RETENTION_DAYS", "retention_days":
		// retention_days: must be a number
		if _, err := strconv.Atoi(value); err != nil {
			return fmt.Errorf("retention_days must be a number")
		}
	}

	// General character check: not allowed dangerous characters
	if public.ContainsDangerousChars(value) {
		return fmt.Errorf("configuration value contains illegal characters")
	}

	return nil
}

View on GitHub (pinned to fc36c76c05)

Solutions

  1. Send a plain integer string, e.g. '30'
  2. Strip units and separators before the call ('30 days'→'30')
  3. Use whole days only — decimals are rejected by Atoi
  4. Ensure the field is not empty when the form is submitted

Example fix

// before
RETENTION_DAYS = 30d
// after
RETENTION_DAYS = 30
Defensive patterns

Strategy: validation

Validate before calling

const n = Number(retentionDays);
if (!Number.isInteger(n) || n <= 0) throw new Error('retention_days must be a positive integer');

Type guard

function isWholeDays(v: unknown): v is number {
  return typeof v === 'number' && Number.isInteger(v) && v > 0;
}

Try / catch

try {
  await api.setSystemConfigKey('RETENTION_DAYS', String(days));
} catch (e) {
  if (String(e.message).includes('retention_days')) {
    notify('Enter retention as a plain number of days, e.g. 30');
  } else throw e;
}

Prevention

When it happens

Trigger: Setting RETENTION_DAYS to a non-numeric string such as '30d', 'thirty', '', '30 days', or a value with a decimal point like '30.5'.

Common situations: Users append units ('30d', '1 month') or paste values with whitespace; frontends send formatted numbers with thousands separators ('1,000').

Related errors


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