{"record":{"id":"525439ebff6fa6eb","repo":"Billionmail/BillionMail","slug":"failed-to-import-recipients-batch-d-d-w","errorCode":null,"errorMessage":"failed to import recipients (batch %d/%d): %w","messagePattern":"failed to import recipients \\(batch (.+?)/(.+?)\\): %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"core/internal/service/batch_mail/batch_mail.go","lineNumber":246,"sourceCode":"\t\t// Prepare data for current batch\n\t\tvalues := make([]g.Map, len(currentBatch))\n\t\tfor j, contact := range currentBatch {\n\t\t\tvalues[j] = g.Map{\n\t\t\t\t\"task_id\":     taskId,\n\t\t\t\t\"recipient\":   contact.Email,\n\t\t\t\t\"is_sent\":     0,\n\t\t\t\t\"sent_time\":   0,\n\t\t\t\t\"message_id\":  \"\",\n\t\t\t\t\"create_time\": now,\n\t\t\t}\n\t\t}\n\n\t\t// Insert current batch\n\t\tresult, err := g.DB().Model(\"recipient_info\").InsertIgnore(values)\n\t\tif err != nil {\n\t\t\tg.Log().Error(ctx, \"Failed to import recipient batch %d/%d for task %d: %v\",\n\t\t\t\ti+1, totalBatches, taskId, err)\n\t\t\treturn fmt.Errorf(\"failed to import recipients (batch %d/%d): %w\", i+1, totalBatches, err)\n\t\t}\n\n\t\t// Count affected rows\n\t\taffected, err := result.RowsAffected()\n\t\tif err != nil {\n\t\t\tg.Log().Debugf(ctx, \"Could not get affected rows for batch %d/%d: %v\", i+1, totalBatches, err)\n\t\t} else {\n\t\t\ttotalImported += int(affected)\n\t\t}\n\t}\n\n\tg.Log().Info(ctx, \"Task %d: Total %d recipients imported successfully\", taskId, totalImported)\n\treturn nil\n}\n\n// ImportRecipientsTx\nfunc ImportRecipientsTx(ctx context.Context, tx gdb.TX, taskId int, contacts []*entity.Contact) error {\n\tif len(contacts) == 0 {","sourceCodeStart":228,"sourceCodeEnd":264,"githubUrl":"https://github.com/Billionmail/BillionMail/blob/fc36c76c050c3775c5e899faf7403cf0262d2744/core/internal/service/batch_mail/batch_mail.go#L228-L264","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","solutions":["Read the logged underlying DB error for the failing batch number","Validate/normalize CSV rows (email format, field lengths, required columns) before importing","Ensure the recipient_info schema matches the value maps (run migrations)","Make imports resumable/idempotent (skip already-inserted batches via InsertIgnore semantics) and retry from the failed batch"],"exampleFix":"// before: abort entire import on first batch failure\nif err != nil {\n    return fmt.Errorf(\"failed to import recipients (batch %d/%d): %w\", i+1, totalBatches, err)\n}\n// after: validate rows before insert and report batch context\nfor _, v := range values {\n    if !isValidEmail(v[\"email\"].(string)) {\n        return fmt.Errorf(\"invalid email in batch %d/%d: %s\", i+1, totalBatches, v[\"email\"])\n    }\n}\nif _, err := g.DB().Model(\"recipient_info\").InsertIgnore(values); err != nil {\n    return fmt.Errorf(\"failed to import recipients (batch %d/%d): %w\", i+1, totalBatches, err)\n}","handlingStrategy":"validation","validationCode":"for _, r := range recipients {\n    if r.Email == \"\" || len(r.Email) > 254 || !strings.Contains(r.Email, \"@\") {\n        return fmt.Errorf(\"invalid recipient email: %q\", r.Email)\n    }\n}","typeGuard":null,"tryCatchPattern":"if err := ImportRecipients(ctx, taskId, recipients); err != nil {\n    log.Printf(\"import aborted: %v (earlier batches already committed)\", err)\n    return err\n}","preventionTips":["Validate CSV rows (email format, lengths, required fields) before import","Run schema checks/migrations so recipient_info matches inserted value maps","Insert in a transaction (ImportRecipientsTx) to avoid partial imports","Log the batch index with the underlying driver error for quick diagnosis"],"tags":["database","insert","batch"],"backgroundTag":"batch-insert-failed","analyzedSha":"fc36c76c050c3775c5e899faf7403cf0262d2744","analyzedAt":"2026-09-05T21:28:54.019Z","contentChangedAt":"2026-09-05T21:28:54.019Z","schemaVersion":2},"datasetVersion":"2026-09-12T22:17:10.623Z"}