Billionmail/BillionMail · error

failed to import recipients (batch %d/%d): %w

Error message

failed to import recipients (batch %d/%d): %w

What it means

ImportRecipients inserts recipient rows into recipient_info in chunks using InsertIgnore. When any batch insert fails at the database level, the error is logged and wrapped as "failed to import recipients (batch %d/%d): %w", aborting the whole import. Because earlier batches were already committed, the import is left partially complete.

Source

Thrown at core/internal/service/batch_mail/batch_mail.go:246

		// Prepare data for current batch
		values := make([]g.Map, len(currentBatch))
		for j, contact := range currentBatch {
			values[j] = g.Map{
				"task_id":     taskId,
				"recipient":   contact.Email,
				"is_sent":     0,
				"sent_time":   0,
				"message_id":  "",
				"create_time": now,
			}
		}

		// Insert current batch
		result, err := g.DB().Model("recipient_info").InsertIgnore(values)
		if err != nil {
			g.Log().Error(ctx, "Failed to import recipient batch %d/%d for task %d: %v",
				i+1, totalBatches, taskId, err)
			return fmt.Errorf("failed to import recipients (batch %d/%d): %w", i+1, totalBatches, err)
		}

		// Count affected rows
		affected, err := result.RowsAffected()
		if err != nil {
			g.Log().Debugf(ctx, "Could not get affected rows for batch %d/%d: %v", i+1, totalBatches, err)
		} else {
			totalImported += int(affected)
		}
	}

	g.Log().Info(ctx, "Task %d: Total %d recipients imported successfully", taskId, totalImported)
	return nil
}

// ImportRecipientsTx
func ImportRecipientsTx(ctx context.Context, tx gdb.TX, taskId int, contacts []*entity.Contact) error {
	if len(contacts) == 0 {

View on GitHub (pinned to fc36c76c05)

Solutions

  1. Read the logged underlying DB error for the failing batch number
  2. Validate/normalize CSV rows (email format, field lengths, required columns) before importing
  3. Ensure the recipient_info schema matches the value maps (run migrations)
  4. Make imports resumable/idempotent (skip already-inserted batches via InsertIgnore semantics) and retry from the failed batch

Example fix

// before: abort entire import on first batch failure
if err != nil {
    return fmt.Errorf("failed to import recipients (batch %d/%d): %w", i+1, totalBatches, err)
}
// after: validate rows before insert and report batch context
for _, v := range values {
    if !isValidEmail(v["email"].(string)) {
        return fmt.Errorf("invalid email in batch %d/%d: %s", i+1, totalBatches, v["email"])
    }
}
if _, err := g.DB().Model("recipient_info").InsertIgnore(values); err != nil {
    return fmt.Errorf("failed to import recipients (batch %d/%d): %w", i+1, totalBatches, err)
}
Defensive patterns

Strategy: validation

Validate before calling

for _, r := range recipients {
    if r.Email == "" || len(r.Email) > 254 || !strings.Contains(r.Email, "@") {
        return fmt.Errorf("invalid recipient email: %q", r.Email)
    }
}

Try / catch

if err := ImportRecipients(ctx, taskId, recipients); err != nil {
    log.Printf("import aborted: %v (earlier batches already committed)", err)
    return err
}

Prevention

When it happens

Trigger: Any batch's INSERT into recipient_info errors: schema mismatch between value maps and table columns, invalid/overlong field values (bad email, oversized custom fields), DB connection loss mid-import, or constraint violations not suppressed by InsertIgnore (e.g. NOT NULL without default).

Common situations: Importing a CSV with columns not matching recipient_info schema; multi-byte or malformed emails; Postgres restarted during a large import; duplicates are fine (InsertIgnore) but NOT NULL/length violations are not.

Related errors


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