diff --git a/mox-/config.go b/mox-/config.go
index 34c033d..f4090f7 100644
--- a/mox-/config.go
+++ b/mox-/config.go
@@ -663,6 +663,10 @@ func PrepareStaticConfig(ctx context.Context, log mlog.Log, configFile string, c
 		}
 	}
 	for name, acme := range c.ACME {
+		addAcmeErrorf := func(format string, args ...any) {
+			addErrorf("acme provider %s: %s", name, fmt.Sprintf(format, args...))
+		}
+
 		var eabKeyID string
 		var eabKey []byte
 		if acme.ExternalAccountBinding != nil {
@@ -670,12 +674,12 @@ func PrepareStaticConfig(ctx context.Context, log mlog.Log, configFile string, c
 			p := configDirPath(configFile, acme.ExternalAccountBinding.KeyFile)
 			buf, err := os.ReadFile(p)
 			if err != nil {
-				addErrorf("reading external account binding key for acme provider %q: %s", name, err)
+				addAcmeErrorf("reading external account binding key: %s", err)
 			} else {
 				dec := make([]byte, base64.RawURLEncoding.DecodedLen(len(buf)))
 				n, err := base64.RawURLEncoding.Decode(dec, buf)
 				if err != nil {
-					addErrorf("parsing external account binding key as base64 for acme provider %q: %s", name, err)
+					addAcmeErrorf("parsing external account binding key as base64: %s", err)
 				} else {
 					eabKey = dec[:n]
 				}
@@ -690,7 +694,7 @@ func PrepareStaticConfig(ctx context.Context, log mlog.Log, configFile string, c
 		os.MkdirAll(acmeDir, 0770)
 		manager, err := autotls.Load(name, acmeDir, acme.ContactEmail, acme.DirectoryURL, eabKeyID, eabKey, makeGetPrivateKey(name), Shutdown.Done())
 		if err != nil {
-			addErrorf("loading ACME identity for %q: %s", name, err)
+			addAcmeErrorf("loading ACME identity: %s", err)
 		}
 		acme.Manager = manager
 
@@ -704,20 +708,24 @@ func PrepareStaticConfig(ctx context.Context, log mlog.Log, configFile string, c
 
 	var haveUnspecifiedSMTPListener bool
 	for name, l := range c.Listeners {
+		addListenerErrorf := func(format string, args ...any) {
+			addErrorf("listener %s: %s", name, fmt.Sprintf(format, args...))
+		}
+
 		if l.Hostname != "" {
 			d, err := dns.ParseDomain(l.Hostname)
 			if err != nil {
-				addErrorf("bad listener hostname %q: %s", l.Hostname, err)
+				addListenerErrorf("parsing hostname %q: %s", l.Hostname, err)
 			}
 			l.HostnameDomain = d
 		}
 		if l.TLS != nil {
 			if l.TLS.ACME != "" && len(l.TLS.KeyCerts) != 0 {
-				addErrorf("listener %q: cannot have ACME and static key/certificates", name)
+				addListenerErrorf("cannot have ACME and static key/certificates")
 			} else if l.TLS.ACME != "" {
 				acme, ok := c.ACME[l.TLS.ACME]
 				if !ok {
-					addErrorf("listener %q: unknown ACME provider %q", name, l.TLS.ACME)
+					addListenerErrorf("unknown ACME provider %q", l.TLS.ACME)
 				}
 
 				// If only checking or with missing ACME definition, we don't have an acme manager,
@@ -745,17 +753,17 @@ func PrepareStaticConfig(ctx context.Context, log mlog.Log, configFile string, c
 			} else if len(l.TLS.KeyCerts) != 0 {
 				if doLoadTLSKeyCerts {
 					if err := loadTLSKeyCerts(configFile, "listener "+name, l.TLS); err != nil {
-						addErrorf("%w", err)
+						addListenerErrorf("%w", err)
 					}
 				}
 			} else {
-				addErrorf("listener %q: cannot have TLS config without ACME and without static keys/certificates", name)
+				addListenerErrorf("cannot have TLS config without ACME and without static keys/certificates")
 			}
 			for _, privKeyFile := range l.TLS.HostPrivateKeyFiles {
 				keyPath := configDirPath(configFile, privKeyFile)
 				privKey, err := loadPrivateKeyFile(keyPath)
 				if err != nil {
-					addErrorf("listener %q: parsing host private key for DANE and ACME certificates: %v", name, err)
+					addListenerErrorf("parsing host private key for DANE and ACME certificates: %v", err)
 					continue
 				}
 				switch k := privKey.(type) {
@@ -797,7 +805,7 @@ func PrepareStaticConfig(ctx context.Context, log mlog.Log, configFile string, c
 				}
 				v, ok := versions[l.TLS.MinVersion]
 				if !ok {
-					addErrorf("listener %q: unknown TLS mininum version %q", name, l.TLS.MinVersion)
+					addListenerErrorf("unknown TLS mininum version %q", l.TLS.MinVersion)
 				}
 				minVersion = v
 			}
@@ -827,11 +835,11 @@ func PrepareStaticConfig(ctx context.Context, log mlog.Log, configFile string, c
 			needtls("MTASTSHTTPS", l.MTASTSHTTPS.Enabled && !l.MTASTSHTTPS.NonTLS)
 			needtls("WebserverHTTPS", l.WebserverHTTPS.Enabled)
 			if len(needsTLS) > 0 {
-				addErrorf("listener %q does not specify tls config, but requires tls for %s", name, strings.Join(needsTLS, ", "))
+				addListenerErrorf("no tls config specified, but requires tls for %s", strings.Join(needsTLS, ", "))
 			}
 		}
 		if l.AutoconfigHTTPS.Enabled && l.MTASTSHTTPS.Enabled && l.AutoconfigHTTPS.Port == l.MTASTSHTTPS.Port && l.AutoconfigHTTPS.NonTLS != l.MTASTSHTTPS.NonTLS {
-			addErrorf("listener %q tries to enable autoconfig and mta-sts enabled on same port but with both http and https", name)
+			addListenerErrorf("autoconfig and mta-sts enabled on same port but with both http and https")
 		}
 		if l.SMTP.Enabled {
 			if len(l.IPs) == 0 {
@@ -840,7 +848,7 @@ func PrepareStaticConfig(ctx context.Context, log mlog.Log, configFile string, c
 			for _, ipstr := range l.IPs {
 				ip := net.ParseIP(ipstr)
 				if ip == nil {
-					addErrorf("listener %q has invalid IP %q", name, ipstr)
+					addListenerErrorf("invalid IP %q", ipstr)
 					continue
 				}
 				if ip.IsUnspecified() {
@@ -859,25 +867,25 @@ func PrepareStaticConfig(ctx context.Context, log mlog.Log, configFile string, c
 		for _, s := range l.SMTP.DNSBLs {
 			d, err := dns.ParseDomain(s)
 			if err != nil {
-				addErrorf("listener %q has invalid DNSBL zone %q", name, s)
+				addListenerErrorf("parsing DNSBL zone %q: %s", s, err)
 				continue
 			}
 			l.SMTP.DNSBLZones = append(l.SMTP.DNSBLZones, d)
 		}
 		if l.IPsNATed && len(l.NATIPs) > 0 {
-			addErrorf("listener %q has both IPsNATed and NATIPs (remove deprecated IPsNATed)", name)
+			addListenerErrorf("both IPsNATed and NATIPs configued (remove deprecated IPsNATed)")
 		}
 		for _, ipstr := range l.NATIPs {
 			ip := net.ParseIP(ipstr)
 			if ip == nil {
-				addErrorf("listener %q has invalid ip %q", name, ipstr)
+				addListenerErrorf("invalid ip %q", ipstr)
 			} else if ip.IsUnspecified() || ip.IsLoopback() {
-				addErrorf("listener %q has NAT ip that is the unspecified or loopback address %s", name, ipstr)
+				addListenerErrorf("NAT ip that is the unspecified or loopback address %s", ipstr)
 			}
 		}
 		checkPath := func(kind string, enabled bool, path string) {
 			if enabled && path != "" && !strings.HasPrefix(path, "/") {
-				addErrorf("listener %q has %s with path %q that must start with a slash", name, kind, path)
+				addListenerErrorf("%s with path %q that must start with a slash", kind, path)
 			}
 		}
 		checkPath("AccountHTTP", l.AccountHTTP.Enabled, l.AccountHTTP.Path)
@@ -919,17 +927,21 @@ func PrepareStaticConfig(ctx context.Context, log mlog.Log, configFile string, c
 	}
 
 	checkTransportSMTP := func(name string, isTLS bool, t *config.TransportSMTP) {
+		addTransportErrorf := func(format string, args ...any) {
+			addErrorf("transport %s: %s", name, fmt.Sprintf(format, args...))
+		}
+
 		var err error
 		t.DNSHost, err = dns.ParseDomain(t.Host)
 		if err != nil {
-			addErrorf("transport %s: bad host %s: %v", name, t.Host, err)
+			addTransportErrorf("bad host %s: %v", t.Host, err)
 		}
 
 		if isTLS && t.STARTTLSInsecureSkipVerify {
-			addErrorf("transport %s: cannot have STARTTLSInsecureSkipVerify with immediate TLS")
+			addTransportErrorf("cannot have STARTTLSInsecureSkipVerify with immediate TLS")
 		}
 		if isTLS && t.NoSTARTTLS {
-			addErrorf("transport %s: cannot have NoSTARTTLS with immediate TLS")
+			addTransportErrorf("cannot have NoSTARTTLS with immediate TLS")
 		}
 
 		if t.Auth == nil {
@@ -938,7 +950,7 @@ func PrepareStaticConfig(ctx context.Context, log mlog.Log, configFile string, c
 		seen := map[string]bool{}
 		for _, m := range t.Auth.Mechanisms {
 			if seen[m] {
-				addErrorf("transport %s: duplicate authentication mechanism %s", name, m)
+				addTransportErrorf("duplicate authentication mechanism %s", m)
 			}
 			seen[m] = true
 			switch m {
@@ -949,7 +961,7 @@ func PrepareStaticConfig(ctx context.Context, log mlog.Log, configFile string, c
 			case "CRAM-MD5":
 			case "PLAIN":
 			default:
-				addErrorf("transport %s: unknown authentication mechanism %s", name, m)
+				addTransportErrorf("unknown authentication mechanism %s", m)
 			}
 		}
 
@@ -960,27 +972,35 @@ func PrepareStaticConfig(ctx context.Context, log mlog.Log, configFile string, c
 	}
 
 	checkTransportSocks := func(name string, t *config.TransportSocks) {
+		addTransportErrorf := func(format string, args ...any) {
+			addErrorf("transport %s: %s", name, fmt.Sprintf(format, args...))
+		}
+
 		_, _, err := net.SplitHostPort(t.Address)
 		if err != nil {
-			addErrorf("transport %s: bad address %s: %v", name, t.Address, err)
+			addTransportErrorf("bad address %s: %v", t.Address, err)
 		}
 		for _, ipstr := range t.RemoteIPs {
 			ip := net.ParseIP(ipstr)
 			if ip == nil {
-				addErrorf("transport %s: bad ip %s", name, ipstr)
+				addTransportErrorf("bad ip %s", ipstr)
 			} else {
 				t.IPs = append(t.IPs, ip)
 			}
 		}
 		t.Hostname, err = dns.ParseDomain(t.RemoteHostname)
 		if err != nil {
-			addErrorf("transport %s: bad hostname %s: %v", name, t.RemoteHostname, err)
+			addTransportErrorf("bad hostname %s: %v", t.RemoteHostname, err)
 		}
 	}
 
 	checkTransportDirect := func(name string, t *config.TransportDirect) {
+		addTransportErrorf := func(format string, args ...any) {
+			addErrorf("transport %s: %s", name, fmt.Sprintf(format, args...))
+		}
+
 		if t.DisableIPv4 && t.DisableIPv6 {
-			addErrorf("transport %s: both IPv4 and IPv6 are disabled, enable at least one", name)
+			addTransportErrorf("both IPv4 and IPv6 are disabled, enable at least one")
 		}
 		t.IPFamily = "ip"
 		if t.DisableIPv4 {
@@ -992,6 +1012,10 @@ func PrepareStaticConfig(ctx context.Context, log mlog.Log, configFile string, c
 	}
 
 	for name, t := range c.Transports {
+		addTransportErrorf := func(format string, args ...any) {
+			addErrorf("transport %s: %s", name, fmt.Sprintf(format, args...))
+		}
+
 		n := 0
 		if t.Submissions != nil {
 			n++
@@ -1014,7 +1038,7 @@ func PrepareStaticConfig(ctx context.Context, log mlog.Log, configFile string, c
 			checkTransportDirect(name, t.Direct)
 		}
 		if n > 1 {
-			addErrorf("transport %s: cannot have multiple methods in a transport", name)
+			addTransportErrorf("cannot have multiple methods in a transport")
 		}
 	}
 
@@ -1075,11 +1099,10 @@ func prepareDynamicConfig(ctx context.Context, log mlog.Log, dynamicPath string,
 	}
 
 	// Check that mailbox is in unicode NFC normalized form.
-	checkMailboxNormf := func(mailbox string, format string, args ...any) {
+	checkMailboxNormf := func(mailbox string, what string, errorf func(format string, args ...any)) {
 		s := norm.NFC.String(mailbox)
 		if mailbox != s {
-			msg := fmt.Sprintf(format, args...)
-			addErrorf("%s: mailbox %q is not in NFC normalized form, should be %q", msg, mailbox, s)
+			errorf("%s: mailbox %q is not in NFC normalized form, should be %q", what, mailbox, s)
 		}
 	}
 
@@ -1087,7 +1110,7 @@ func prepareDynamicConfig(ctx context.Context, log mlog.Log, dynamicPath string,
 	if _, ok := c.Accounts[static.Postmaster.Account]; !ok {
 		addErrorf("postmaster account %q does not exist", static.Postmaster.Account)
 	}
-	checkMailboxNormf(static.Postmaster.Mailbox, "postmaster mailbox")
+	checkMailboxNormf(static.Postmaster.Mailbox, "postmaster mailbox", addErrorf)
 
 	accDests = map[string]AccountDestination{}
 	aliases = map[string]config.Alias{}
@@ -1097,7 +1120,7 @@ func prepareDynamicConfig(ctx context.Context, log mlog.Log, dynamicPath string,
 		if _, ok := c.Accounts[static.HostTLSRPT.Account]; !ok {
 			addErrorf("host tlsrpt account %q does not exist", static.HostTLSRPT.Account)
 		}
-		checkMailboxNormf(static.HostTLSRPT.Mailbox, "host tlsrpt mailbox")
+		checkMailboxNormf(static.HostTLSRPT.Mailbox, "host tlsrpt mailbox", addErrorf)
 
 		// Localpart has been parsed already.
 
@@ -1157,11 +1180,15 @@ func prepareDynamicConfig(ctx context.Context, log mlog.Log, dynamicPath string,
 	// Validate domains.
 	c.ClientSettingDomains = map[dns.Domain]struct{}{}
 	for d, domain := range c.Domains {
+		addDomainErrorf := func(format string, args ...any) {
+			addErrorf(fmt.Sprintf("domain %v: %s", d, fmt.Sprintf(format, args...)))
+		}
+
 		dnsdomain, err := dns.ParseDomain(d)
 		if err != nil {
-			addErrorf("bad domain %q: %s", d, err)
+			addDomainErrorf("parsing domain: %s", err)
 		} else if dnsdomain.Name() != d {
-			addErrorf("domain %s must be specified in unicode form, %s", d, dnsdomain.Name())
+			addDomainErrorf("must be specified in unicode form, %s", dnsdomain.Name())
 		}
 
 		domain.Domain = dnsdomain
@@ -1169,7 +1196,7 @@ func prepareDynamicConfig(ctx context.Context, log mlog.Log, dynamicPath string,
 		if domain.ClientSettingsDomain != "" {
 			csd, err := dns.ParseDomain(domain.ClientSettingsDomain)
 			if err != nil {
-				addErrorf("bad client settings domain %q: %s", domain.ClientSettingsDomain, err)
+				addDomainErrorf("bad client settings domain %q: %s", domain.ClientSettingsDomain, err)
 			}
 			domain.ClientSettingsDNSDomain = csd
 			c.ClientSettingDomains[csd] = struct{}{}
@@ -1177,22 +1204,26 @@ func prepareDynamicConfig(ctx context.Context, log mlog.Log, dynamicPath string,
 
 		for _, sign := range domain.DKIM.Sign {
 			if _, ok := domain.DKIM.Selectors[sign]; !ok {
-				addErrorf("selector %s for signing is missing in domain %s", sign, d)
+				addDomainErrorf("unknown selector %s for signing", sign)
 			}
 		}
 		for name, sel := range domain.DKIM.Selectors {
+			addSelectorErrorf := func(format string, args ...any) {
+				addDomainErrorf("selector %s: %s", name, fmt.Sprintf(format, args...))
+			}
+
 			seld, err := dns.ParseDomain(name)
 			if err != nil {
-				addErrorf("bad selector %q: %s", name, err)
+				addSelectorErrorf("parsing selector: %s", err)
 			} else if seld.Name() != name {
-				addErrorf("selector %q must be specified in unicode form, %q", name, seld.Name())
+				addSelectorErrorf("must be specified in unicode form, %q", seld.Name())
 			}
 			sel.Domain = seld
 
 			if sel.Expiration != "" {
 				exp, err := time.ParseDuration(sel.Expiration)
 				if err != nil {
-					addErrorf("selector %q has invalid expiration %q: %v", name, sel.Expiration, err)
+					addSelectorErrorf("invalid expiration %q: %v", sel.Expiration, err)
 				} else {
 					sel.ExpirationSeconds = int(exp / time.Second)
 				}
@@ -1206,22 +1237,22 @@ func prepareDynamicConfig(ctx context.Context, log mlog.Log, dynamicPath string,
 				log.Error("using sha1 with DKIM is deprecated as not secure enough, switch to sha256")
 			case "sha256":
 			default:
-				addErrorf("unsupported hash %q for selector %q in domain %s", sel.HashEffective, name, d)
+				addSelectorErrorf("unsupported hash %q", sel.HashEffective)
 			}
 
 			pemBuf, err := os.ReadFile(configDirPath(dynamicPath, sel.PrivateKeyFile))
 			if err != nil {
-				addErrorf("reading private key for selector %s in domain %s: %s", name, d, err)
+				addSelectorErrorf("reading private key: %s", err)
 				continue
 			}
 			p, _ := pem.Decode(pemBuf)
 			if p == nil {
-				addErrorf("private key for selector %s in domain %s has no PEM block", name, d)
+				addSelectorErrorf("private key has no PEM block")
 				continue
 			}
 			key, err := x509.ParsePKCS8PrivateKey(p.Bytes)
 			if err != nil {
-				addErrorf("parsing private key for selector %s in domain %s: %s", name, d, err)
+				addSelectorErrorf("parsing private key: %s", err)
 				continue
 			}
 			switch k := key.(type) {
@@ -1229,18 +1260,18 @@ func prepareDynamicConfig(ctx context.Context, log mlog.Log, dynamicPath string,
 				if k.N.BitLen() < 1024 {
 					// ../rfc/6376:757
 					// Let's help user do the right thing.
-					addErrorf("rsa keys should be >= 1024 bits")
+					addSelectorErrorf("rsa keys should be >= 1024 bits, is %d bits", k.N.BitLen())
 				}
 				sel.Key = k
 				sel.Algorithm = fmt.Sprintf("rsa-%d", k.N.BitLen())
 			case ed25519.PrivateKey:
 				if sel.HashEffective != "sha256" {
-					addErrorf("hash algorithm %q is not supported with ed25519, only sha256 is", sel.HashEffective)
+					addSelectorErrorf("hash algorithm %q is not supported with ed25519, only sha256 is", sel.HashEffective)
 				}
 				sel.Key = k
 				sel.Algorithm = "ed25519"
 			default:
-				addErrorf("private key type %T not yet supported, at selector %s in domain %s", key, name, d)
+				addSelectorErrorf("private key type %T not yet supported", key)
 			}
 
 			if len(sel.Headers) == 0 {
@@ -1261,7 +1292,7 @@ func prepareDynamicConfig(ctx context.Context, log mlog.Log, dynamicPath string,
 					}
 				}
 				if !from {
-					addErrorf("From-field must always be DKIM-signed")
+					addSelectorErrorf("From-field must always be DKIM-signed")
 				}
 				sel.HeadersEffective = sel.Headers
 			}
@@ -1271,16 +1302,16 @@ func prepareDynamicConfig(ctx context.Context, log mlog.Log, dynamicPath string,
 
 		if domain.MTASTS != nil {
 			if !haveSTSListener {
-				addErrorf("MTA-STS enabled for domain %q, but there is no listener for MTASTS", d)
+				addDomainErrorf("MTA-STS enabled, but there is no listener for MTASTS", d)
 			}
 			sts := domain.MTASTS
 			if sts.PolicyID == "" {
-				addErrorf("invalid empty MTA-STS PolicyID")
+				addDomainErrorf("invalid empty MTA-STS PolicyID")
 			}
 			switch sts.Mode {
 			case mtasts.ModeNone, mtasts.ModeTesting, mtasts.ModeEnforce:
 			default:
-				addErrorf("invalid mtasts mode %q", sts.Mode)
+				addDomainErrorf("invalid mtasts mode %q", sts.Mode)
 			}
 		}
 
@@ -1294,35 +1325,39 @@ func prepareDynamicConfig(ctx context.Context, log mlog.Log, dynamicPath string,
 
 	// Validate email addresses.
 	for accName, acc := range c.Accounts {
+		addAccountErrorf := func(format string, args ...any) {
+			addErrorf("account %q: %s", accName, fmt.Sprintf(format, args...))
+		}
+
 		var err error
 		acc.DNSDomain, err = dns.ParseDomain(acc.Domain)
 		if err != nil {
-			addErrorf("parsing domain %s for account %q: %s", acc.Domain, accName, err)
+			addAccountErrorf("parsing domain %s: %s", acc.Domain, err)
 		}
 
 		if strings.EqualFold(acc.RejectsMailbox, "Inbox") {
-			addErrorf("account %q: cannot set RejectsMailbox to inbox, messages will be removed automatically from the rejects mailbox", accName)
+			addAccountErrorf("cannot set RejectsMailbox to inbox, messages will be removed automatically from the rejects mailbox")
 		}
-		checkMailboxNormf(acc.RejectsMailbox, "account %q", accName)
+		checkMailboxNormf(acc.RejectsMailbox, "rejects mailbox", addErrorf)
 
 		if acc.AutomaticJunkFlags.JunkMailboxRegexp != "" {
 			r, err := regexp.Compile(acc.AutomaticJunkFlags.JunkMailboxRegexp)
 			if err != nil {
-				addErrorf("invalid JunkMailboxRegexp regular expression: %v", err)
+				addAccountErrorf("invalid JunkMailboxRegexp regular expression: %v", err)
 			}
 			acc.JunkMailbox = r
 		}
 		if acc.AutomaticJunkFlags.NeutralMailboxRegexp != "" {
 			r, err := regexp.Compile(acc.AutomaticJunkFlags.NeutralMailboxRegexp)
 			if err != nil {
-				addErrorf("invalid NeutralMailboxRegexp regular expression: %v", err)
+				addAccountErrorf("invalid NeutralMailboxRegexp regular expression: %v", err)
 			}
 			acc.NeutralMailbox = r
 		}
 		if acc.AutomaticJunkFlags.NotJunkMailboxRegexp != "" {
 			r, err := regexp.Compile(acc.AutomaticJunkFlags.NotJunkMailboxRegexp)
 			if err != nil {
-				addErrorf("invalid NotJunkMailboxRegexp regular expression: %v", err)
+				addAccountErrorf("invalid NotJunkMailboxRegexp regular expression: %v", err)
 			}
 			acc.NotJunkMailbox = r
 		}
@@ -1330,16 +1365,16 @@ func prepareDynamicConfig(ctx context.Context, log mlog.Log, dynamicPath string,
 		if acc.JunkFilter != nil {
 			params := acc.JunkFilter.Params
 			if params.MaxPower < 0 || params.MaxPower > 0.5 {
-				addErrorf("junk filter MaxPower must be >= 0 and < 0.5")
+				addAccountErrorf("junk filter MaxPower must be >= 0 and < 0.5")
 			}
 			if params.TopWords < 0 {
-				addErrorf("junk filter TopWords must be >= 0")
+				addAccountErrorf("junk filter TopWords must be >= 0")
 			}
 			if params.IgnoreWords < 0 || params.IgnoreWords > 0.5 {
-				addErrorf("junk filter IgnoreWords must be >= 0 and < 0.5")
+				addAccountErrorf("junk filter IgnoreWords must be >= 0 and < 0.5")
 			}
 			if params.RareWords < 0 {
-				addErrorf("junk filter RareWords must be >= 0")
+				addAccountErrorf("junk filter RareWords must be >= 0")
 			}
 		}
 
@@ -1347,14 +1382,14 @@ func prepareDynamicConfig(ctx context.Context, log mlog.Log, dynamicPath string,
 		for i, s := range acc.FromIDLoginAddresses {
 			a, err := smtp.ParseAddress(s)
 			if err != nil {
-				addErrorf("invalid fromid login address %q in account %q: %v", s, accName, err)
+				addAccountErrorf("invalid fromid login address %q: %v", s, err)
 			}
 			// We check later on if address belongs to account.
 			dom, ok := c.Domains[a.Domain.Name()]
 			if !ok {
-				addErrorf("unknown domain in fromid login address %q for account %q", s, accName)
+				addAccountErrorf("unknown domain in fromid login address %q", s)
 			} else if dom.LocalpartCatchallSeparator == "" {
-				addErrorf("localpart catchall separator not configured for domain for fromid login address %q for account %q", s, accName)
+				addAccountErrorf("localpart catchall separator not configured for domain for fromid login address %q", s)
 			}
 			acc.ParsedFromIDLoginAddresses[i] = a
 		}
@@ -1370,14 +1405,14 @@ func prepareDynamicConfig(ctx context.Context, log mlog.Log, dynamicPath string,
 				err = errors.New("scheme must be http or https")
 			}
 			if err != nil {
-				addErrorf("parsing outgoing hook url %q in account %q: %v", acc.OutgoingWebhook.URL, accName, err)
+				addAccountErrorf("parsing outgoing hook url %q: %v", acc.OutgoingWebhook.URL, err)
 			}
 
 			// note: outgoing hook events are in ../queue/hooks.go, ../mox-/config.go, ../queue.go and ../webapi/gendoc.sh. keep in sync.
 			outgoingHookEvents := []string{"delivered", "suppressed", "delayed", "failed", "relayed", "expanded", "canceled", "unrecognized"}
 			for _, e := range acc.OutgoingWebhook.Events {
 				if !slices.Contains(outgoingHookEvents, e) {
-					addErrorf("unknown outgoing hook event %q", e)
+					addAccountErrorf("unknown outgoing hook event %q", e)
 				}
 			}
 		}
@@ -1387,7 +1422,7 @@ func prepareDynamicConfig(ctx context.Context, log mlog.Log, dynamicPath string,
 				err = errors.New("scheme must be http or https")
 			}
 			if err != nil {
-				addErrorf("parsing incoming hook url %q in account %q: %v", acc.IncomingWebhook.URL, accName, err)
+				addAccountErrorf("parsing incoming hook url %q: %v", acc.IncomingWebhook.URL, err)
 			}
 		}
 
@@ -1395,10 +1430,18 @@ func prepareDynamicConfig(ctx context.Context, log mlog.Log, dynamicPath string,
 		replaceLocalparts := map[string]string{}
 
 		for addrName, dest := range acc.Destinations {
-			checkMailboxNormf(dest.Mailbox, "account %q, destination %q", accName, addrName)
+			addDestErrorf := func(format string, args ...any) {
+				addAccountErrorf("destination %q: %s", addrName, fmt.Sprintf(format, args...))
+			}
+
+			checkMailboxNormf(dest.Mailbox, "destination mailbox", addDestErrorf)
 
 			for i, rs := range dest.Rulesets {
-				checkMailboxNormf(rs.Mailbox, "account %q, destination %q, ruleset %d", accName, addrName, i+1)
+				addRulesetErrorf := func(format string, args ...any) {
+					addDestErrorf("ruleset %d: %s", i+1, fmt.Sprintf(format, args...))
+				}
+
+				checkMailboxNormf(rs.Mailbox, "ruleset mailbox", addRulesetErrorf)
 
 				n := 0
 
@@ -1406,7 +1449,7 @@ func prepareDynamicConfig(ctx context.Context, log mlog.Log, dynamicPath string,
 					n++
 					r, err := regexp.Compile(rs.SMTPMailFromRegexp)
 					if err != nil {
-						addErrorf("invalid SMTPMailFrom regular expression: %v", err)
+						addRulesetErrorf("invalid SMTPMailFrom regular expression: %v", err)
 					}
 					c.Accounts[accName].Destinations[addrName].Rulesets[i].SMTPMailFromRegexpCompiled = r
 				}
@@ -1414,7 +1457,7 @@ func prepareDynamicConfig(ctx context.Context, log mlog.Log, dynamicPath string,
 					n++
 					r, err := regexp.Compile(rs.MsgFromRegexp)
 					if err != nil {
-						addErrorf("invalid MsgFrom regular expression: %v", err)
+						addRulesetErrorf("invalid MsgFrom regular expression: %v", err)
 					}
 					c.Accounts[accName].Destinations[addrName].Rulesets[i].MsgFromRegexpCompiled = r
 				}
@@ -1422,7 +1465,7 @@ func prepareDynamicConfig(ctx context.Context, log mlog.Log, dynamicPath string,
 					n++
 					d, err := dns.ParseDomain(rs.VerifiedDomain)
 					if err != nil {
-						addErrorf("invalid VerifiedDomain: %v", err)
+						addRulesetErrorf("invalid VerifiedDomain: %v", err)
 					}
 					c.Accounts[accName].Destinations[addrName].Rulesets[i].VerifiedDNSDomain = d
 				}
@@ -1431,46 +1474,46 @@ func prepareDynamicConfig(ctx context.Context, log mlog.Log, dynamicPath string,
 				for k, v := range rs.HeadersRegexp {
 					n++
 					if strings.ToLower(k) != k {
-						addErrorf("header field %q must only have lower case characters", k)
+						addRulesetErrorf("header field %q must only have lower case characters", k)
 					}
 					if strings.ToLower(v) != v {
-						addErrorf("header value %q must only have lower case characters", v)
+						addRulesetErrorf("header value %q must only have lower case characters", v)
 					}
 					rk, err := regexp.Compile(k)
 					if err != nil {
-						addErrorf("invalid rule header regexp %q: %v", k, err)
+						addRulesetErrorf("invalid rule header regexp %q: %v", k, err)
 					}
 					rv, err := regexp.Compile(v)
 					if err != nil {
-						addErrorf("invalid rule header regexp %q: %v", v, err)
+						addRulesetErrorf("invalid rule header regexp %q: %v", v, err)
 					}
 					hdr = append(hdr, [...]*regexp.Regexp{rk, rv})
 				}
 				c.Accounts[accName].Destinations[addrName].Rulesets[i].HeadersRegexpCompiled = hdr
 
 				if n == 0 {
-					addErrorf("ruleset must have at least one rule")
+					addRulesetErrorf("ruleset must have at least one rule")
 				}
 
 				if rs.IsForward && rs.ListAllowDomain != "" {
-					addErrorf("ruleset cannot have both IsForward and ListAllowDomain")
+					addRulesetErrorf("ruleset cannot have both IsForward and ListAllowDomain")
 				}
 				if rs.IsForward {
 					if rs.SMTPMailFromRegexp == "" || rs.VerifiedDomain == "" {
-						addErrorf("ruleset with IsForward must have both SMTPMailFromRegexp and VerifiedDomain too")
+						addRulesetErrorf("ruleset with IsForward must have both SMTPMailFromRegexp and VerifiedDomain too")
 					}
 				}
 				if rs.ListAllowDomain != "" {
 					d, err := dns.ParseDomain(rs.ListAllowDomain)
 					if err != nil {
-						addErrorf("invalid ListAllowDomain %q: %v", rs.ListAllowDomain, err)
+						addRulesetErrorf("invalid ListAllowDomain %q: %v", rs.ListAllowDomain, err)
 					}
 					c.Accounts[accName].Destinations[addrName].Rulesets[i].ListAllowDNSDomain = d
 				}
 
-				checkMailboxNormf(rs.AcceptRejectsToMailbox, "account %q, destination %q, ruleset %d, rejects mailbox", accName, addrName, i+1)
+				checkMailboxNormf(rs.AcceptRejectsToMailbox, "rejects mailbox", addRulesetErrorf)
 				if strings.EqualFold(rs.AcceptRejectsToMailbox, "inbox") {
-					addErrorf("account %q, destination %q, ruleset %d: AcceptRejectsToMailbox cannot be set to Inbox", accName, addrName, i+1)
+					addRulesetErrorf("AcceptRejectsToMailbox cannot be set to Inbox")
 				}
 			}
 
@@ -1478,16 +1521,16 @@ func prepareDynamicConfig(ctx context.Context, log mlog.Log, dynamicPath string,
 			if strings.HasPrefix(addrName, "@") {
 				d, err := dns.ParseDomain(addrName[1:])
 				if err != nil {
-					addErrorf("parsing domain %q in account %q", addrName[1:], accName)
+					addDestErrorf("parsing domain %q", addrName[1:])
 					continue
 				} else if _, ok := c.Domains[d.Name()]; !ok {
-					addErrorf("unknown domain for address %q in account %q", addrName, accName)
+					addDestErrorf("unknown domain for address")
 					continue
 				}
 				domainHasAddress[d.Name()] = true
 				addrFull := "@" + d.Name()
 				if _, ok := accDests[addrFull]; ok {
-					addErrorf("duplicate canonicalized catchall destination address %s", addrFull)
+					addDestErrorf("duplicate canonicalized catchall destination address %s", addrFull)
 				}
 				accDests[addrFull] = AccountDestination{true, "", accName, dest}
 				continue
@@ -1498,20 +1541,20 @@ func prepareDynamicConfig(ctx context.Context, log mlog.Log, dynamicPath string,
 			if localpart, err := smtp.ParseLocalpart(addrName); err != nil && errors.Is(err, smtp.ErrBadLocalpart) {
 				address, err = smtp.ParseAddress(addrName)
 				if err != nil {
-					addErrorf("invalid email address %q in account %q", addrName, accName)
+					addDestErrorf("invalid email address")
 					continue
 				} else if _, ok := c.Domains[address.Domain.Name()]; !ok {
-					addErrorf("unknown domain for address %q in account %q", addrName, accName)
+					addDestErrorf("unknown domain for address")
 					continue
 				}
 			} else {
 				if err != nil {
-					addErrorf("invalid localpart %q in account %q", addrName, accName)
+					addDestErrorf("invalid localpart %q", addrName)
 					continue
 				}
 				address = smtp.NewAddress(localpart, acc.DNSDomain)
 				if _, ok := c.Domains[acc.DNSDomain.Name()]; !ok {
-					addErrorf("unknown domain %s for account %q", acc.DNSDomain.Name(), accName)
+					addDestErrorf("unknown domain %s", acc.DNSDomain.Name())
 					continue
 				}
 				replaceLocalparts[addrName] = address.Pack(true)
@@ -1522,13 +1565,13 @@ func prepareDynamicConfig(ctx context.Context, log mlog.Log, dynamicPath string,
 			domainHasAddress[address.Domain.Name()] = true
 			lp := CanonicalLocalpart(address.Localpart, dc)
 			if dc.LocalpartCatchallSeparator != "" && strings.Contains(string(address.Localpart), dc.LocalpartCatchallSeparator) {
-				addErrorf("localpart of address %s includes domain catchall separator %s", address, dc.LocalpartCatchallSeparator)
+				addDestErrorf("localpart of address %s includes domain catchall separator %s", address, dc.LocalpartCatchallSeparator)
 			} else {
 				address.Localpart = lp
 			}
 			addrFull := address.Pack(true)
 			if _, ok := accDests[addrFull]; ok {
-				addErrorf("duplicate canonicalized destination address %s", addrFull)
+				addDestErrorf("duplicate canonicalized destination address %s", addrFull)
 			}
 			accDests[addrFull] = AccountDestination{false, origLP, accName, dest}
 		}
@@ -1536,7 +1579,7 @@ func prepareDynamicConfig(ctx context.Context, log mlog.Log, dynamicPath string,
 		for lp, addr := range replaceLocalparts {
 			dest, ok := acc.Destinations[lp]
 			if !ok {
-				addErrorf("could not find localpart %q to replace with address in destinations", lp)
+				addAccountErrorf("could not find localpart %q to replace with address in destinations", lp)
 			} else {
 				log.Warn(`deprecation warning: support for account destination addresses specified as just localpart ("username") instead of full email address will be removed in the future; update domains.conf, for each Account, for each Destination, ensure each key is an email address by appending "@" and the default domain for the account`,
 					slog.Any("localpart", lp),
@@ -1557,7 +1600,7 @@ func prepareDynamicConfig(ctx context.Context, log mlog.Log, dynamicPath string,
 			dc := c.Domains[a.Domain.Name()]
 			a.Localpart = CanonicalLocalpart(a.Localpart, dc)
 			if _, ok := accDests[a.Pack(true)]; !ok {
-				addErrorf("fromid login address %q for account %q does not match its destination addresses", acc.FromIDLoginAddresses[i], accName)
+				addAccountErrorf("fromid login address %q does not match its destination addresses", acc.FromIDLoginAddresses[i])
 			}
 		}
 
@@ -1566,28 +1609,32 @@ func prepareDynamicConfig(ctx context.Context, log mlog.Log, dynamicPath string,
 
 	// Set DMARC destinations.
 	for d, domain := range c.Domains {
+		addDomainErrorf := func(format string, args ...any) {
+			addErrorf("domain %s: %s", d, fmt.Sprintf(format, args...))
+		}
+
 		dmarc := domain.DMARC
 		if dmarc == nil {
 			continue
 		}
 		if _, ok := c.Accounts[dmarc.Account]; !ok {
-			addErrorf("DMARC account %q does not exist", dmarc.Account)
+			addDomainErrorf("DMARC account %q does not exist", dmarc.Account)
 		}
 		lp, err := smtp.ParseLocalpart(dmarc.Localpart)
 		if err != nil {
-			addErrorf("invalid DMARC localpart %q: %s", dmarc.Localpart, err)
+			addDomainErrorf("invalid DMARC localpart %q: %s", dmarc.Localpart, err)
 		}
 		if lp.IsInternational() {
 			// ../rfc/8616:234
-			addErrorf("DMARC localpart %q is an internationalized address, only conventional ascii-only address possible for interopability", lp)
+			addDomainErrorf("DMARC localpart %q is an internationalized address, only conventional ascii-only address possible for interopability", lp)
 		}
 		addrdom := domain.Domain
 		if dmarc.Domain != "" {
 			addrdom, err = dns.ParseDomain(dmarc.Domain)
 			if err != nil {
-				addErrorf("DMARC domain %q: %s", dmarc.Domain, err)
+				addDomainErrorf("DMARC domain %q: %s", dmarc.Domain, err)
 			} else if _, ok := c.Domains[addrdom.Name()]; !ok {
-				addErrorf("unknown domain %q for DMARC address in domain %q", addrdom, d)
+				addDomainErrorf("unknown domain %q for DMARC address", addrdom)
 			}
 		}
 		if addrdom == domain.Domain {
@@ -1602,35 +1649,39 @@ func prepareDynamicConfig(ctx context.Context, log mlog.Log, dynamicPath string,
 			Mailbox:      dmarc.Mailbox,
 			DMARCReports: true,
 		}
-		checkMailboxNormf(dmarc.Mailbox, "DMARC mailbox for account %q", dmarc.Account)
+		checkMailboxNormf(dmarc.Mailbox, "DMARC mailbox for account", addDomainErrorf)
 		accDests[addrFull] = AccountDestination{false, lp, dmarc.Account, dest}
 	}
 
 	// Set TLSRPT destinations.
 	for d, domain := range c.Domains {
+		addDomainErrorf := func(format string, args ...any) {
+			addErrorf("domain %s: %s", d, fmt.Sprintf(format, args...))
+		}
+
 		tlsrpt := domain.TLSRPT
 		if tlsrpt == nil {
 			continue
 		}
 		if _, ok := c.Accounts[tlsrpt.Account]; !ok {
-			addErrorf("TLSRPT account %q does not exist", tlsrpt.Account)
+			addDomainErrorf("TLSRPT account %q does not exist", tlsrpt.Account)
 		}
 		lp, err := smtp.ParseLocalpart(tlsrpt.Localpart)
 		if err != nil {
-			addErrorf("invalid TLSRPT localpart %q: %s", tlsrpt.Localpart, err)
+			addDomainErrorf("invalid TLSRPT localpart %q: %s", tlsrpt.Localpart, err)
 		}
 		if lp.IsInternational() {
 			// Does not appear documented in ../rfc/8460, but similar to DMARC it makes sense
 			// to keep this ascii-only addresses.
-			addErrorf("TLSRPT localpart %q is an internationalized address, only conventional ascii-only address allowed for interopability", lp)
+			addDomainErrorf("TLSRPT localpart %q is an internationalized address, only conventional ascii-only address allowed for interopability", lp)
 		}
 		addrdom := domain.Domain
 		if tlsrpt.Domain != "" {
 			addrdom, err = dns.ParseDomain(tlsrpt.Domain)
 			if err != nil {
-				addErrorf("TLSRPT domain %q: %s", tlsrpt.Domain, err)
+				addDomainErrorf("TLSRPT domain %q: %s", tlsrpt.Domain, err)
 			} else if _, ok := c.Domains[addrdom.Name()]; !ok {
-				addErrorf("unknown domain %q for TLSRPT address in domain %q", tlsrpt.Domain, d)
+				addDomainErrorf("unknown domain %q for TLSRPT address", tlsrpt.Domain)
 			}
 		}
 		if addrdom == domain.Domain {
@@ -1645,7 +1696,7 @@ func prepareDynamicConfig(ctx context.Context, log mlog.Log, dynamicPath string,
 			Mailbox:          tlsrpt.Mailbox,
 			DomainTLSReports: true,
 		}
-		checkMailboxNormf(tlsrpt.Mailbox, "TLSRPT mailbox for account %q", tlsrpt.Account)
+		checkMailboxNormf(tlsrpt.Mailbox, "TLSRPT mailbox", addDomainErrorf)
 		accDests[addrFull] = AccountDestination{false, lp, tlsrpt.Account, dest}
 	}
 
@@ -1659,15 +1710,19 @@ func prepareDynamicConfig(ctx context.Context, log mlog.Log, dynamicPath string,
 	// Aliases, per domain. Also add references to accounts.
 	for d, domain := range c.Domains {
 		for lpstr, a := range domain.Aliases {
+			addAliasErrorf := func(format string, args ...any) {
+				addErrorf("domain %s: alias %s: %s", d, lpstr, fmt.Sprintf(format, args...))
+			}
+
 			var err error
 			a.LocalpartStr = lpstr
 			var clp smtp.Localpart
 			lp, err := smtp.ParseLocalpart(lpstr)
 			if err != nil {
-				addErrorf("domain %q: parsing localpart %q for alias: %v", d, lpstr, err)
+				addAliasErrorf("parsing alias: %v", err)
 				continue
 			} else if domain.LocalpartCatchallSeparator != "" && strings.Contains(string(lp), domain.LocalpartCatchallSeparator) {
-				addErrorf("domain %q: alias %q contains localpart catchall separator", d, a.LocalpartStr)
+				addAliasErrorf("alias contains localpart catchall separator")
 				continue
 			} else {
 				clp = CanonicalLocalpart(lp, domain)
@@ -1675,16 +1730,16 @@ func prepareDynamicConfig(ctx context.Context, log mlog.Log, dynamicPath string,
 
 			addr := smtp.NewAddress(clp, domain.Domain).Pack(true)
 			if _, ok := aliases[addr]; ok {
-				addErrorf("domain %q: duplicate alias address %q", d, addr)
+				addAliasErrorf("duplicate alias address %q", addr)
 				continue
 			}
 			if _, ok := accDests[addr]; ok {
-				addErrorf("domain %q: alias %q already present as regular address", d, addr)
+				addAliasErrorf("alias %q already present as regular address", addr)
 				continue
 			}
 			if len(a.Addresses) == 0 {
 				// Not currently possible, Addresses isn't optional.
-				addErrorf("domain %q: alias %q needs at least one destination address", d, addr)
+				addAliasErrorf("alias %q needs at least one destination address", addr)
 				continue
 			}
 			a.ParsedAddresses = make([]config.AliasAddress, 0, len(a.Addresses))
@@ -1692,17 +1747,17 @@ func prepareDynamicConfig(ctx context.Context, log mlog.Log, dynamicPath string,
 			for _, destAddr := range a.Addresses {
 				da, err := smtp.ParseAddress(destAddr)
 				if err != nil {
-					addErrorf("domain %q: parsing destination address %q in alias %q: %v", d, destAddr, addr, err)
+					addAliasErrorf("parsing destination address %q: %v", destAddr, err)
 					continue
 				}
 				dastr := da.Pack(true)
 				accDest, ok := accDests[dastr]
 				if !ok {
-					addErrorf("domain %q: alias %q references non-existent address %q", d, addr, destAddr)
+					addAliasErrorf("references non-existent address %q", destAddr)
 					continue
 				}
 				if seen[dastr] {
-					addErrorf("domain %q: alias %q has duplicate address %q", d, addr, destAddr)
+					addAliasErrorf("duplicate address %q", destAddr)
 					continue
 				}
 				seen[dastr] = true
@@ -1743,19 +1798,23 @@ func prepareDynamicConfig(ctx context.Context, log mlog.Log, dynamicPath string,
 
 	c.WebDNSDomainRedirects = map[dns.Domain]dns.Domain{}
 	for from, to := range c.WebDomainRedirects {
+		addRedirectErrorf := func(format string, args ...any) {
+			addErrorf("web redirect %s to %s: %s", from, to, fmt.Sprintf(format, args...))
+		}
+
 		fromdom, err := dns.ParseDomain(from)
 		if err != nil {
-			addErrorf("parsing domain for redirect %s: %v", from, err)
+			addRedirectErrorf("parsing domain for redirect %s: %v", from, err)
 		}
 		todom, err := dns.ParseDomain(to)
 		if err != nil {
-			addErrorf("parsing domain for redirect %s: %v", to, err)
+			addRedirectErrorf("parsing domain for redirect %s: %v", to, err)
 		} else if fromdom == todom {
-			addErrorf("will not redirect domain %s to itself", todom)
+			addRedirectErrorf("will not redirect domain %s to itself", todom)
 		}
 		var zerodom dns.Domain
 		if _, ok := c.WebDNSDomainRedirects[fromdom]; ok && fromdom != zerodom {
-			addErrorf("duplicate redirect domain %s", from)
+			addRedirectErrorf("duplicate redirect domain %s", from)
 		}
 		c.WebDNSDomainRedirects[fromdom] = todom
 	}
@@ -1763,6 +1822,10 @@ func prepareDynamicConfig(ctx context.Context, log mlog.Log, dynamicPath string,
 	for i := range c.WebHandlers {
 		wh := &c.WebHandlers[i]
 
+		addHandlerErrorf := func(format string, args ...any) {
+			addErrorf("webhandler %s %s: %s", wh.Domain, wh.PathRegexp, fmt.Sprintf(format, args...))
+		}
+
 		if wh.LogName == "" {
 			wh.Name = fmt.Sprintf("%d", i)
 		} else {
@@ -1771,16 +1834,16 @@ func prepareDynamicConfig(ctx context.Context, log mlog.Log, dynamicPath string,
 
 		dom, err := dns.ParseDomain(wh.Domain)
 		if err != nil {
-			addErrorf("webhandler %s %s: parsing domain: %v", wh.Domain, wh.PathRegexp, err)
+			addHandlerErrorf("parsing domain: %v", err)
 		}
 		wh.DNSDomain = dom
 
 		if !strings.HasPrefix(wh.PathRegexp, "^") {
-			addErrorf("webhandler %s %s: path regexp must start with a ^", wh.Domain, wh.PathRegexp)
+			addHandlerErrorf("path regexp must start with a ^")
 		}
 		re, err := regexp.Compile(wh.PathRegexp)
 		if err != nil {
-			addErrorf("webhandler %s %s: compiling regexp: %v", wh.Domain, wh.PathRegexp, err)
+			addHandlerErrorf("compiling regexp: %v", err)
 		}
 		wh.Path = re
 
@@ -1789,13 +1852,13 @@ func prepareDynamicConfig(ctx context.Context, log mlog.Log, dynamicPath string,
 			n++
 			ws := wh.WebStatic
 			if ws.StripPrefix != "" && !strings.HasPrefix(ws.StripPrefix, "/") {
-				addErrorf("webstatic %s %s: prefix to strip %s must start with a slash", wh.Domain, wh.PathRegexp, ws.StripPrefix)
+				addHandlerErrorf("static: prefix to strip %s must start with a slash", ws.StripPrefix)
 			}
 			for k := range ws.ResponseHeaders {
 				xk := k
 				k := strings.TrimSpace(xk)
 				if k != xk || k == "" {
-					addErrorf("webstatic %s %s: bad header %q", wh.Domain, wh.PathRegexp, xk)
+					addHandlerErrorf("static: bad header %q", xk)
 				}
 			}
 		}
@@ -1805,29 +1868,29 @@ func prepareDynamicConfig(ctx context.Context, log mlog.Log, dynamicPath string,
 			if wr.BaseURL != "" {
 				u, err := url.Parse(wr.BaseURL)
 				if err != nil {
-					addErrorf("webredirect %s %s: parsing redirect url %s: %v", wh.Domain, wh.PathRegexp, wr.BaseURL, err)
+					addHandlerErrorf("redirect: parsing redirect url %s: %v", wr.BaseURL, err)
 				}
 				switch u.Path {
 				case "", "/":
 					u.Path = "/"
 				default:
-					addErrorf("webredirect %s %s: BaseURL must have empty path", wh.Domain, wh.PathRegexp, wr.BaseURL)
+					addHandlerErrorf("redirect: BaseURL must have empty path", wr.BaseURL)
 				}
 				wr.URL = u
 			}
 			if wr.OrigPathRegexp != "" && wr.ReplacePath != "" {
 				re, err := regexp.Compile(wr.OrigPathRegexp)
 				if err != nil {
-					addErrorf("webredirect %s %s: compiling regexp %s: %v", wh.Domain, wh.PathRegexp, wr.OrigPathRegexp, err)
+					addHandlerErrorf("compiling regexp %s: %v", wr.OrigPathRegexp, err)
 				}
 				wr.OrigPath = re
 			} else if wr.OrigPathRegexp != "" || wr.ReplacePath != "" {
-				addErrorf("webredirect %s %s: must have either both OrigPathRegexp and ReplacePath, or neither", wh.Domain, wh.PathRegexp)
+				addHandlerErrorf("redirect: must have either both OrigPathRegexp and ReplacePath, or neither")
 			} else if wr.BaseURL == "" {
-				addErrorf("webredirect %s %s: must at least one of BaseURL and OrigPathRegexp+ReplacePath", wh.Domain, wh.PathRegexp)
+				addHandlerErrorf("must at least one of BaseURL and OrigPathRegexp+ReplacePath")
 			}
 			if wr.StatusCode != 0 && (wr.StatusCode < 300 || wr.StatusCode >= 400) {
-				addErrorf("webredirect %s %s: invalid redirect status code %d", wh.Domain, wh.PathRegexp, wr.StatusCode)
+				addHandlerErrorf("redirect: invalid redirect status code %d", wr.StatusCode)
 			}
 		}
 		if wh.WebForward != nil {
@@ -1835,7 +1898,7 @@ func prepareDynamicConfig(ctx context.Context, log mlog.Log, dynamicPath string,
 			wf := wh.WebForward
 			u, err := url.Parse(wf.URL)
 			if err != nil {
-				addErrorf("webforward %s %s: parsing url %s: %v", wh.Domain, wh.PathRegexp, wf.URL, err)
+				addHandlerErrorf("forward: parsing url %s: %v", wf.URL, err)
 			}
 			wf.TargetURL = u
 
@@ -1843,7 +1906,7 @@ func prepareDynamicConfig(ctx context.Context, log mlog.Log, dynamicPath string,
 				xk := k
 				k := strings.TrimSpace(xk)
 				if k != xk || k == "" {
-					addErrorf("webforward %s %s: bad header %q", wh.Domain, wh.PathRegexp, xk)
+					addHandlerErrorf("forrward: bad header %q", xk)
 				}
 			}
 		}
@@ -1851,7 +1914,7 @@ func prepareDynamicConfig(ctx context.Context, log mlog.Log, dynamicPath string,
 			n++
 			wi := wh.WebInternal
 			if !strings.HasPrefix(wi.BasePath, "/") || !strings.HasSuffix(wi.BasePath, "/") {
-				addErrorf("webinternal %s %s: base path %q must start and end with /", wh.Domain, wh.PathRegexp, wi.BasePath)
+				addHandlerErrorf("internal service: base path %q must start and end with /", wi.BasePath)
 			}
 			// todo: we could make maxMsgSize and accountPath configurable
 			const isForwarded = false
@@ -1866,12 +1929,12 @@ func prepareDynamicConfig(ctx context.Context, log mlog.Log, dynamicPath string,
 			case "webapi":
 				wi.Handler = NewWebapiHandler(config.DefaultMaxMsgSize, wi.BasePath, isForwarded)
 			default:
-				addErrorf("webinternal %s %s: unknown service %q", wh.Domain, wh.PathRegexp, wi.Service)
+				addHandlerErrorf("internal service: unknown service %q", wi.Service)
 			}
 			wi.Handler = SafeHeaders(http.StripPrefix(wi.BasePath[:len(wi.BasePath)-1], wi.Handler))
 		}
 		if n != 1 {
-			addErrorf("webhandler %s %s: must have exactly one handler, not %d", wh.Domain, wh.PathRegexp, n)
+			addHandlerErrorf("must have exactly one handler, not %d", n)
 		}
 	}
 
@@ -1879,11 +1942,11 @@ func prepareDynamicConfig(ctx context.Context, log mlog.Log, dynamicPath string,
 	for _, s := range c.MonitorDNSBLs {
 		d, err := dns.ParseDomain(s)
 		if err != nil {
-			addErrorf("invalid monitor dnsbl zone %s: %v", s, err)
+			addErrorf("dnsbl %s: parsing dnsbl zone: %v", s, err)
 			continue
 		}
 		if slices.Contains(c.MonitorDNSBLZones, d) {
-			addErrorf("duplicate zone %s in monitor dnsbl zones", d)
+			addErrorf("dnsbl %s: duplicate zone", s)
 			continue
 		}
 		c.MonitorDNSBLZones = append(c.MonitorDNSBLZones, d)