{"record":{"id":"a43d268a981a4785","repo":"Billionmail/BillionMail","slug":"failed-to-load-contacts-v","errorCode":null,"errorMessage":"failed to load contacts: %v","messagePattern":"failed to load contacts: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"core/internal/service/batch_mail/api_mail_send.go","lineNumber":395,"sourceCode":"\t\ttemplateIds = append(templateIds, t.TemplateId)\n\t}\n\t// Batch query email templates\n\tvar emailTemplates []entity.EmailTemplate\n\terr = g.DB().Model(\"email_templates\").\n\t\tWhereIn(\"id\", templateIds).\n\t\tCtx(ctx).\n\t\tScan(&emailTemplates)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to load email templates: %v\", err)\n\t}\n\t// Batch query contacts\n\tvar contacts []entity.Contact\n\terr = g.DB().Model(\"bm_contacts\").\n\t\tWhereIn(\"email\", recipientEmails).\n\t\tCtx(ctx).\n\t\tScan(&contacts)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to load contacts: %v\", err)\n\t}\n\t// Build cache\n\tcache := &CacheData{\n\t\tApiTemplates:   make(map[int]entity.ApiTemplates),\n\t\tEmailTemplates: make(map[int]entity.EmailTemplate),\n\t\tContacts:       make(map[string]entity.Contact),\n\t}\n\n\tfor _, t := range apiTemplates {\n\t\tcache.ApiTemplates[t.Id] = t\n\t}\n\tfor _, t := range emailTemplates {\n\t\tcache.EmailTemplates[t.Id] = t\n\t}\n\tfor _, c := range contacts {\n\t\tcache.Contacts[c.Email] = c\n\t}\n","sourceCodeStart":377,"sourceCodeEnd":413,"githubUrl":"https://github.com/Billionmail/BillionMail/blob/fc36c76c050c3775c5e899faf7403cf0262d2744/core/internal/service/batch_mail/api_mail_send.go#L377-L413","documentation":"preloadData fetches bm_contacts rows for all recipient emails to build the in-memory contact cache. If the SELECT ... WHERE email IN (...) on bm_contacts fails, it wraps the driver error as \"failed to load contacts: %v\". Missing contacts are not an error here; only an actual DB read failure triggers this message.","triggerScenarios":"ProcessApiMailQueue -> preloadData when the bm_contacts query errors: DB outage, prepared statement failure with a very large recipient list (e.g. WHERE IN with thousands of emails exceeding parameter limits), timeout, or permission denial.","commonSituations":"Very large recipient batches producing oversized WHERE IN clauses; DB connection dropped between the template and contact queries; stale indexes/locks slowing the query past timeout.","solutions":["Check the wrapped underlying error and DB health","Chunk the recipient email list into smaller batches for the WHERE IN query","Verify SELECT permission on bm_contacts and that the table schema matches the entity","Add a timeout/lock diagnosis if the query times out on large lists"],"exampleFix":"// before: one huge WHERE IN\nerr = g.DB().Model(\"bm_contacts\").WhereIn(\"email\", recipientEmails).Ctx(ctx).Scan(&contacts)\n// after: chunked loads\nconst chunk = 500\nfor i := 0; i < len(recipientEmails); i += chunk {\n    end := i + chunk\n    if end > len(recipientEmails) { end = len(recipientEmails) }\n    var part []entity.Contact\n    if err = g.DB().Model(\"bm_contacts\").WhereIn(\"email\", recipientEmails[i:end]).Ctx(ctx).Scan(&part); err != nil {\n        return nil, fmt.Errorf(\"failed to load contacts: %v\", err)\n    }\n    contacts = append(contacts, part...)\n}","handlingStrategy":"validation","validationCode":"const maxIn = 1000\nif len(recipientEmails) > maxIn {\n    return nil, fmt.Errorf(\"recipient list too large for single query: %d\", len(recipientEmails))\n}","typeGuard":null,"tryCatchPattern":"cache, err := preloadData(ctx, apiIds)\nif err != nil && strings.Contains(err.Error(), \"failed to load contacts\") {\n    // chunk recipients and retry, or fail the batch gracefully\n    return err\n}","preventionTips":["Chunk WHERE IN clauses (a few hundred items per query)","Normalize/validate emails before querying bm_contacts","Ensure an index on bm_contacts.email for large lookups"],"tags":["database","postgresql","query"],"backgroundTag":"database-query-failed","analyzedSha":"fc36c76c050c3775c5e899faf7403cf0262d2744","analyzedAt":"2026-09-05T21:28:54.019Z","contentChangedAt":"2026-09-05T21:28:54.019Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}