Billionmail/BillionMail · error

required column 'email' not found

Error message

required column 'email' not found

What it means

parseCSVContent validates that the CSV header row contains an 'email' column (header names are lowercased and trimmed before the lookup). This is a precondition check: without an email column the importer cannot map rows to contacts. It is thrown with a plain fmt.Errorf, no wrapped cause.

Source

Thrown at core/internal/controller/contact/contact_v1_import_contacts.go:120

	reader.LazyQuotes = true
	reader.TrimLeadingSpace = true

	// Read headers
	headers, err := reader.Read()
	if err != nil {
		return nil, fmt.Errorf("failed to read CSV headers: %v", err)
	}

	// Find the index of the required columns
	columnIndexes := make(map[string]int)
	for i, header := range headers {
		header = strings.ToLower(strings.TrimSpace(header))
		columnIndexes[header] = i
	}

	// Check the required columns
	if _, ok := columnIndexes["email"]; !ok {
		return nil, fmt.Errorf("required column 'email' not found")
	}

	// Process data rows
	for {
		record, err := reader.Read()
		if err == io.EOF {
			break
		}
		if err != nil {
			g.Log().Debugf(ctx, "Error reading CSV record: %v", err)
			continue
		}

		email := strings.TrimSpace(record[columnIndexes["email"]])
		if email == "" {
			continue
		}

View on GitHub (pinned to fc36c76c05)

Solutions

  1. Rename the header cell to exactly 'email' (case and surrounding whitespace are tolerated, synonyms are not).
  2. Re-export the CSV ensuring the 'Export column headers' option is enabled.
  3. If the source uses 'Email Address', post-process the header row before upload or extend the matcher to accept common synonyms.
  4. Validate the CSV header locally before importing (script or spreadsheet check).

Example fix

// before
Name,Email Address,Company
John,john@x.com,Acme
// after
name,email,company
John,john@x.com,Acme
Defensive patterns

Strategy: validation

Validate before calling

r := csv.NewReader(f)
headers, err := r.Read()
if err != nil { return err }
norm := make([]string, len(headers))
for i, h := range headers {
	norm[i] = strings.ToLower(strings.TrimSpace(h))
}
if !slices.Contains(norm, "email") {
	return fmt.Errorf("CSV must contain an 'email' column; got: %v", norm)
}

Try / catch

contacts, err := importContacts(ctx, file)
if err != nil {
	if strings.Contains(err.Error(), "required column 'email'") {
		return fmt.Errorf("upload rejected: add an 'email' header column to your CSV")
	}
	return err
}

Prevention

When it happens

Trigger: The uploaded CSV's first row contains no cell that normalizes to 'email' — e.g. columns named 'Email Address' with extra characters that don't trim/lower to exactly 'email', 'e-mail', 'mail', or a header row that is actually data because the export skipped headers.

Common situations: Users import exports from other CRMs that label the column 'Email Address' or 'E-mail'; CSV saved without a header row; header row misspelled ('emails'); localized headers (e.g. 'correo').

Related errors


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