{"record":{"id":"c505abaf38a7e5ab","repo":"Billionmail/BillionMail","slug":"failed-to-load-api-templates-v","errorCode":null,"errorMessage":"failed to load API templates: %v","messagePattern":"failed to load API templates: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"core/internal/service/batch_mail/api_mail_send.go","lineNumber":372,"sourceCode":"\treturn nil\n}\n\nfunc preloadData(ctx context.Context, logs []ApiMailLog) (*CacheData, error) {\n\t// Collect all necessary IDs\n\tapiIds := make([]int, 0, len(logs))\n\trecipientEmails := make([]string, 0, len(logs))\n\tfor _, log := range logs {\n\t\tapiIds = append(apiIds, log.ApiId)\n\t\trecipientEmails = append(recipientEmails, log.Recipient)\n\t}\n\t// Batch query API templates\n\tvar apiTemplates []entity.ApiTemplates\n\terr := g.DB().Model(\"api_templates\").\n\t\tWhereIn(\"id\", apiIds).\n\t\tCtx(ctx).\n\t\tScan(&apiTemplates)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to load API templates: %v\", err)\n\t}\n\t// Collect template IDs\n\ttemplateIds := make([]int, 0, len(apiTemplates))\n\tfor _, t := range apiTemplates {\n\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\").","sourceCodeStart":354,"sourceCodeEnd":390,"githubUrl":"https://github.com/Billionmail/BillionMail/blob/fc36c76c050c3775c5e899faf7403cf0262d2744/core/internal/service/batch_mail/api_mail_send.go#L354-L390","documentation":"preloadData bulk-loads api_templates rows for the API IDs referenced by the queued mails. If the PostgreSQL query fails (connection issue, permission, bad schema, driver error), the underlying DB error is wrapped with fmt.Errorf as \"failed to load API templates: %v\". This is a database read failure, not a 'template missing' error — a missing ID simply yields an empty result set.","triggerScenarios":"ProcessApiMailQueue -> preloadData while the SELECT ... WHERE id IN (apiIds) on api_templates fails: DB down/restarting, connection pool exhausted, TLS/auth failure, table missing or column renamed by a migration, or read-only replica errors.","commonSituations":"Postgres container restarting during batch processing; app database user lacking SELECT on api_templates; schema drift after an upgrade; network partition between app and DB under load.","solutions":["Check PostgreSQL availability and app logs for the underlying wrapped DB error","Confirm the api_templates table exists with the expected schema (run pending migrations)","Verify DB credentials/permissions grant SELECT on api_templates","Retry the queue processing once the DB connection is healthy"],"exampleFix":"// before: hard fail whole batch on transient DB error\nreturn nil, fmt.Errorf(\"failed to load API templates: %v\", err)\n// after: retry transient failures with backoff\nvar apiTemplates []entity.ApiTemplates\nfor attempt := 0; attempt < 3; attempt++ {\n    err = g.DB().Model(\"api_templates\").WhereIn(\"id\", apiIds).Ctx(ctx).Scan(&apiTemplates)\n    if err == nil { break }\n    time.Sleep(time.Duration(attempt+1) * time.Second)\n}\nif err != nil {\n    return nil, fmt.Errorf(\"failed to load API templates: %v\", err)\n}","handlingStrategy":"retry","validationCode":"if len(apiIds) == 0 {\n    return nil, errors.New(\"no API template ids to preload\")\n}\nif err := g.DB().Ping(ctx); err != nil {\n    return nil, fmt.Errorf(\"db unavailable: %w\", err)\n}","typeGuard":null,"tryCatchPattern":"data, err := preloadData(ctx, apiIds)\nif err != nil {\n    var dbErr goatdb.Error\n    if errors.As(err, &dbErr) && isTransient(dbErr) {\n        // requeue and retry later\n    }\n    return err\n}","preventionTips":["Monitor Postgres health before batch processing starts","Keep migrations in sync so api_templates schema matches entities","Set sane connection pool limits and retry policy for queue workers"],"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-12T22:17:10.623Z"}