Billionmail/BillionMail · error

read dovecot.conf failed: %w

Error message

read dovecot.conf failed: %w

What it means

Raised in ensureDovecotConf when ioutil.ReadFile cannot load <confRoot>/dovecot.conf during quota plugin initialization. The Dovecot main config is missing at the expected path or unreadable, so the function cannot patch in the quota plugin directives; the error is wrapped with %w preserving the os error chain.

Source

Thrown at core/internal/service/mail_boxes/init_quota_plugin.go:87

	// 6. mark & reload dovecot (best-effort)
	if err := gfile.PutContents(markPath, fmt.Sprintf("First sync completed at %s", time.Now().Format("2006-01-02 15:04:05"))); err != nil {
		g.Log().Warningf(ctx, "Failed to create the quota marker file: %v", err)
	}

	if err := reloadDovecot(ctx); err != nil {
		g.Log().Warning(ctx, "reload dovecot failed", err)
	}

	g.Log().Info(ctx, "Quota plugin initialization completed")
	return nil
}

func ensureDovecotConf(confRoot string) error {
	path := filepath.Join(confRoot, "dovecot.conf")
	data, err := ioutil.ReadFile(path)
	if err != nil {
		return fmt.Errorf("read dovecot.conf failed: %w", err)
	}
	content := string(data)

	// Search for the "mail_plugins" line
	mailPluginsRe := regexp.MustCompile(`(?m)^\s*mail_plugins\s*=.*$`)
	if mailPluginsRe.MatchString(content) {
		// If it exists but there is no quota, add a quota.
		newContent := mailPluginsRe.ReplaceAllStringFunc(content, func(line string) string {
			if strings.Contains(line, "quota") {
				return line
			}
			return line + " quota"
		})
		content = newContent
	} else {
		// Insert before the first !include
		includeIdx := strings.Index(content, "!include")
		if includeIdx > -1 {

View on GitHub (pinned to fc36c76c05)

Solutions

  1. Confirm dovecot.conf exists at confRoot (ls the directory from error 315's path)
  2. Fix file permissions so the process user can read it
  3. Restore the stock dovecot.conf from the repo conf/dovecot directory
  4. Check the wrapped error (%w) for the exact OS reason

Example fix

// before
data, err := ioutil.ReadFile(path)
if err != nil {
	return fmt.Errorf("read dovecot.conf failed: %w", err)
}
// after
data, err := ioutil.ReadFile(path)
if err != nil {
	return fmt.Errorf("read dovecot.conf at %s failed: %w", path, err)
}
Defensive patterns

Strategy: validation

Validate before calling

path := filepath.Join(confRoot, "dovecot.conf")
if f, err := os.Open(path); err != nil {
	return fmt.Errorf("dovecot.conf unreadable at %s: %v", path, err)
} else {
	f.Close()
}

Try / catch

if err := mail_boxes.InitQuotaPluginAndUpdateUsedSpace(ctx); err != nil && strings.Contains(err.Error(), "read dovecot.conf failed") {
	log.Printf("restore or chmod dovecot.conf: %v", errors.Unwrap(err))
}

Prevention

When it happens

Trigger: ioutil.ReadFile(dovecot.conf) fails: file deleted or renamed, path permission denied, confRoot points to the wrong directory (often the cause of 315 being bypassed because the dir exists but the file doesn't).

Common situations: dovecot.conf renamed to dovecot.conf.custom by an admin; partial extraction of config files; running as a user without read permission on the conf dir; symlinked conf broken after migration.

Related errors


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