Billionmail/BillionMail · error

failed to check unsubscribe: %w

Error message

failed to check unsubscribe: %w

What it means

rebuildPostfixServiceNetworks found a 'services' map in the parsed compose config but no 'postfix-billionmail' key (or it is not a map). It is the typed-parsed counterpart of the text-scanner error of the same message.

Source

Thrown at core/internal/controller/batch_mail/batch_mail_v1_api_mail_send.go:212

			"name":        apiGroupName,
			"description": fmt.Sprintf(public.LangCtx(ctx, "API %d automatically created contact group"), apiId),
			"create_time": now,
			"update_time": now,
		})
		if err != nil {
			return contact, err
		}
		groupId, _ := groupResult.LastInsertId()
		group.Id = int(groupId)
	} else {

		count, err := g.DB().Model("bm_contacts").
			Where("email", email).
			Where("group_id", group.Id).
			Where("active", 0).
			Count()
		if err != nil {
			return contact, fmt.Errorf("failed to check unsubscribe: %w", err)
		}
		if count > 0 {
			return contact, fmt.Errorf("the recipient has unsubscribed from the current group and cannot send")
		}
	}

	contactData := g.Map{
		"email":       email,
		"group_id":    group.Id,
		"active":      1,
		"status":      1,
		"create_time": now,
	}
	contactResult, err := g.DB().Model("bm_contacts").
		Data(contactData).
		OnConflict("email,group_id").
		Save()
	if err != nil {

View on GitHub (pinned to fc36c76c05)

Solutions

  1. Ensure 'services' contains an entry keyed exactly 'postfix-billionmail' before rebuilding
  2. If the service is declared in an override file, load/merge it into the config map passed to the function
  3. Rename the service back to 'postfix-billionmail' or adapt the code's service key
  4. Verify the parsed config: log config["services"] keys to confirm what the parser saw

Example fix

// before
services:
  mail: {image: billionmail/postfix}
// after
services:
  postfix-billionmail: {image: billionmail/postfix}
Defensive patterns

Strategy: validation

Validate before calling

services, _ := cfg["services"].(map[string]interface{})
if _, ok := services["postfix-billionmail"].(map[string]interface{}); !ok {
	return fmt.Errorf("postfix-billionmail service missing from parsed config")
}

Type guard

func getPostfixService(cfg map[string]interface{}) (map[string]interface{}, bool) {
	svc, _ := cfg["services"].(map[string]interface{})
	p, ok := svc["postfix-billionmail"].(map[string]interface{})
	return p, ok
}

Try / catch

if err := m.rebuildPostfixServiceNetworks(cfg, configs); err != nil {
	if strings.Contains(err.Error(), "postfix-billionmail service not found") {
		// merge override files or restore the stock service block
	}
	return err
}

Prevention

When it happens

Trigger: Rebuild is invoked against a compose config where services exists but does not include a mapping named 'postfix-billionmail' — renamed service, wrong file, or service defined only in an override file not merged into the parsed config.

Common situations: Customized compose files with renamed mail service; parsing only the base file when the service is added in docker-compose.override.yml; typos in the service name.

Related errors


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