sipeed/picoclaw · error

deltachat: email %q is missing a chatmail server. Use %q or

Error message

deltachat: email %q is missing a chatmail server. Use %q or one of:
%s

What it means

settings.email is exactly `@` (or `@ ` — an at-sign with only whitespace after trimming), so the chatmail-server marker form `@server` has no server part. The config parser strips the leading @, trims, and finds an empty domain; it tells you to use the default relay or one from the printed list.

Source

Thrown at pkg/channels/deltachat/deltachat.go:174

	ctx    context.Context
	cancel context.CancelFunc
}

func parseDeltaChatEmailSetting(value string) (string, bool, error) {
	email := strings.TrimSpace(value)
	if email == "" {
		return "", false, fmt.Errorf(
			"deltachat: email is required.\nNext step: choose one of the chatmail servers below and set channel_list.deltachat.settings.email to %q, or use the same @server form with another chatmail relay. Run `picoclaw g` again; PicoClaw will create the account, print the generated full email address, and stop so you can save that address in the config.\nAvailable chatmail servers:\n%s",
			"@"+defaultChatmailRelays[0].Domain,
			formatChatmailRelayList(),
		)
	}
	if !strings.HasPrefix(email, "@") {
		return email, false, nil
	}
	domain := strings.ToLower(strings.TrimSpace(strings.TrimPrefix(email, "@")))
	if domain == "" {
		return "", false, fmt.Errorf("deltachat: email %q is missing a chatmail server. Use %q or one of:\n%s",
			email, "@"+defaultChatmailRelays[0].Domain, formatChatmailRelayList())
	}
	if strings.Contains(domain, "@") || strings.ContainsAny(domain, "/\\ \t\r\n") {
		return "", false, fmt.Errorf("deltachat: invalid chatmail server marker %q; use settings.email like %q",
			email, "@"+defaultChatmailRelays[0].Domain)
	}
	return domain, true, nil
}

func formatChatmailRelayList() string {
	var b strings.Builder
	for _, relay := range defaultChatmailRelays {
		fmt.Fprintf(&b, "  @%-34s %s\n", relay.Domain, relay.Location)
	}
	return strings.TrimRight(b.String(), "\n")
}

func buildChatmailAccountQR(domain string) string {

View on GitHub (pinned to 49183d7e8d)

Solutions

  1. Set email to `@` plus a concrete chatmail domain, e.g. `@nine.testrun.org`.
  2. If templating, default the server variable (e.g. SERVER=${SERVER:-nine.testrun.org}) before rendering the config.
  3. Or provide a full email address instead of the @server form.

Example fix

# before
settings:
  email: "@"

# after
settings:
  email: "@nine.testrun.org"
Defensive patterns

Strategy: validation

Validate before calling

func validEmailSetting(v string) bool {
    e := strings.TrimSpace(v)
    if e == "" { return false }
    if strings.HasPrefix(e, "@") {
        d := strings.TrimSpace(strings.TrimPrefix(e, "@"))
        return d != ""
    }
    return true
}

Type guard

func validEmailSetting(v string) bool {
    e := strings.TrimSpace(v)
    if strings.HasPrefix(e, "@") {
        return strings.TrimSpace(strings.TrimPrefix(e, "@")) != ""
    }
    return e != ""
}

Try / catch

ch, err := deltachat.NewDeltaChatChannel(bc, cfg, bus)
if err != nil && strings.Contains(err.Error(), "missing a chatmail server") {
    return fmt.Errorf("finish the @server form, e.g. @nine.testrun.org")
}

Prevention

When it happens

Trigger: Setting channel_list.deltachat.settings.email to "@" intending to pick a server later; YAML value `"@ "` with trailing space; a templating step that substitutes an empty server variable into an `@${SERVER}` template.

Common situations: Config generated from a template where the server variable was empty; user misunderstanding `@server` syntax and writing just `@` as 'let the system choose'.

Related errors


AI-assisted analysis of sipeed/picoclaw@49183d7e8d (2026-08-15). Data as JSON: /api/errors/4a9f1868440b1795. Report an issue: GitHub.