Billionmail/BillionMail · error

failed to hash postfix config: %v

Error message

failed to hash postfix config: %v

What it means

After persisting the SNI map, updatePostfixSNIMap execs 'postmap /etc/postfix/conf/vmail_ssl.map' inside the Postfix container to rebuild the .db lookup file, wrapping failure with this message. Without rehashing, Postfix keeps serving the stale SNI map.

Source

Thrown at core/internal/service/mail_service/certificate.go:342

	for i, line := range lines {
		if strings.HasPrefix(line, domain) {
			lines[i] = fmt.Sprintf("%s %s %s", domain, keyPath, certPath)
			addNewLine = false
			break
		}
	}

	if addNewLine {
		lines = append(lines, fmt.Sprintf("%s %s %s", domain, keyPath, certPath))
	}

	if err := os.WriteFile(c.PostfixSNIPath, []byte(strings.Join(lines, "\n")), 0755); err != nil {
		return fmt.Errorf("failed to write postfix config: %v", err)
	}

	// Rehash configuration
	if _, err := c.dockerApiClient().ExecCommandByName(context.Background(), consts.SERVICES.Postfix, []string{"postmap", "/etc/postfix/conf/vmail_ssl.map"}, "root"); err != nil {
		return fmt.Errorf("failed to hash postfix config: %v", err)
	}

	return nil
}

// updateDovecotConfig updates Dovecot configuration with new certificate
func (c *Certificate) updateDovecotConfig(csrPem, keyPem string) error {
	dovecotConf := c.DovecotSslConf
	content, err := os.ReadFile(dovecotConf)
	if err != nil {
		return fmt.Errorf("failed to read dovecot config: %v", err)
	}

	// Write certificate and key to files
	certPath := public.AbsPath(filepath.Join(consts.SSL_PATH, "dovecot.crt"))
	keyPath := public.AbsPath(filepath.Join(consts.SSL_PATH, "dovecot.key"))

	if err := os.WriteFile(certPath, []byte(csrPem), 0755); err != nil {

View on GitHub (pinned to fc36c76c05)

Solutions

  1. Verify the Postfix container is running (docker ps) and restart it if stopped.
  2. Confirm consts.SERVICES.Postfix matches the actual container/service name.
  3. Check the map file exists at /etc/postfix/conf/vmail_ssl.map inside the container (volume mount correct).
  4. Inspect the wrapped exec error/output from ExecCommandByName for the postmap stderr.
  5. Run postmap manually inside the container as a workaround, then retry SetSNI.

Example fix

// before
if _, err := c.dockerApiClient().ExecCommandByName(context.Background(), consts.SERVICES.Postfix, []string{"postmap", "/etc/postfix/conf/vmail_ssl.map"}, "root"); err != nil {
    return fmt.Errorf("failed to hash postfix config: %v", err)
}
// after
if _, err := c.dockerApiClient().ExecCommandByName(context.Background(), consts.SERVICES.Postfix, []string{"postmap", "/etc/postfix/conf/vmail_ssl.map"}, "root"); err != nil {
    return fmt.Errorf("failed to hash postfix config (postmap in %s): %w", consts.SERVICES.Postfix, err)
}
Defensive patterns

Strategy: retry

Validate before calling

// before calling SetSNI, ensure the exec target is healthy:
if !dockerServiceRunning(consts.SERVICES.Postfix) {
    return fmt.Errorf("postfix container %s not running", consts.SERVICES.Postfix)
}
// and that the map is visible in-container:
// docker exec <postfix> test -f /etc/postfix/conf/vmail_ssl.map

Try / catch

err := svc.SetSNI(ctx, domain)
if err != nil && strings.Contains(err.Error(), "failed to hash postfix config") {
    log.Printf("postmap exec failed — is the postfix container up? %v", err)
    time.Sleep(5 * time.Second)
    err = svc.SetSNI(ctx, domain) // retry once container healthy
}

Prevention

When it happens

Trigger: The docker exec of postmap fails because the Postfix container is stopped/restarting, the service name in consts.SERVICES.Postfix doesn't match a running container, or the map file isn't visible at /etc/postfix/conf/vmail_ssl.map inside the container.

Common situations: Postfix container crashed or mid-restart during cert update; docker API unreachable from the app container; volume mount path mismatch so the map file is absent in the container; wrong service name after a compose rename.

Related errors


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