Billionmail/BillionMail · error

failed to hash postfix config

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.

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.

Appendix: 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)