Billionmail/BillionMail · error
failed to read HTML template: %w
Error message
failed to read HTML template: %w
What it means
readTemplateFiles loads the default subscribe-page HTML template from core/template/<baseFilename>.html; if os.ReadFile fails (missing file, wrong working directory, permissions) the error is wrapped with %w so the underlying os error (e.g. 'no such file or directory') is preserved.
Source
Thrown at core/internal/controller/subscribe_list/subscribe_list.go:109
// Update contact status: confirm (0,1), subscribe = 1
func updateContactStatus(email string, groupId int, status int) error {
_, err := g.DB().Model("bm_contacts").
Data(g.Map{"status": status, "active": 1}).
Where("email", email).
Where("group_id", groupId).
Update()
return err
}
func readTemplateFiles(baseFilename string) (htmlContent string, txtContent string, err error) {
htmlPath := filepath.Join(public.AbsPath("../core/template"), baseFilename+".html")
txtPath := filepath.Join(public.AbsPath("../core/template"), baseFilename+".txt")
// Read HTML file
htmlBytes, err := os.ReadFile(htmlPath)
if err != nil {
return "", "", fmt.Errorf("failed to read HTML template: %w", err)
}
// Read TXT file (allowed to not exist)
txtBytes, err := os.ReadFile(txtPath)
if err != nil {
if !os.IsNotExist(err) {
g.Log().Debugf(context.Background(),
"failed to read TXT template: %v, path: %s", err, txtPath)
}
txtBytes = []byte("") // Return empty content if file does not exist
}
return string(htmlBytes), string(txtBytes), nil
}
func GetDefaultTemplate(emailType int) (html string, txt string) {
var (
defaultHtml stringView on GitHub (pinned to fc36c76c05)
Solutions
- Verify core/template/<baseFilename>.html exists on the deployed host and is readable by the process user.
- Redeploy including the template/ directory (or fix the Docker image/COPY step).
- Log/inspect public.AbsPath("../core/template") output and fix path resolution if the binary runs from an unexpected cwd.
- Restore correct file permissions if the file exists but is unreadable.
Example fix
// before
htmlPath := filepath.Join(public.AbsPath("../core/template"), baseFilename+".html")
// after
base := public.AbsPath("../core/template")
if _, err := os.Stat(base); err != nil {
return "", "", fmt.Errorf("template dir missing at %s", base)
}
htmlPath := filepath.Join(base, baseFilename+".html") Defensive patterns
Strategy: fallback
Validate before calling
const htmlPath = path.join(templateDir, baseFilename + '.html')
if (!fs.existsSync(htmlPath)) {
throw new Error(`Deployed package missing template: ${htmlPath}`)
} Try / catch
html, txt, err := readTemplateFiles(baseFilename)
if err != nil {
log.Printf("template load failed: %v", err) // wrapped os error shows real path/problem
return defaultEmbeddedTemplate // fallback to embedded template asset
} Prevention
- Ship the template/ directory in the Docker image/deployment artifact
- Use go:embed for templates so they cannot be missing at runtime
- Log public.AbsPath resolution when the process cwd may vary
- Add a startup health check that stats the template directory
When it happens
Trigger: GetDefaultTemplate called while the HTML template file is absent from core/template, the template directory was not deployed (Docker image/volume missing), or AbsPath resolves to the wrong location because the binary's working directory changed.
Common situations: Deploying only the binary without the template/ directory; custom baseFilename that doesn't exist; container volume mounted over core/template; running tests from a directory where '../core/template' doesn't resolve.
Related errors
- unable to determine domain for noreply email
- failed to create email sender: %w
- failed to send confirmation email: %w
- No log files found
- no log files found
AI-assisted analysis of Billionmail/BillionMail@fc36c76c05 (2026-09-05).
Data as JSON: /api/errors/46672e22130f898f.
Report an issue: GitHub.