package mailer import ( "context" "fmt" "os" "path/filepath" "regexp" "strings" "time" ) var sinkFileSafe = regexp.MustCompile(`[^a-zA-Z0-9@._-]+`) type SinkMailer struct { dir string } func NewSinkMailer(dir string) (*SinkMailer, error) { if err := os.MkdirAll(dir, 0o755); err != nil { return nil, fmt.Errorf("create sink dir: %w", err) } return &SinkMailer{dir: dir}, nil } func (m *SinkMailer) Send(ctx context.Context, to, subject, htmlBody, textBody string) error { if ctx != nil { if err := ctx.Err(); err != nil { return err } } ts := time.Now().UTC().Format("20060102T150405.000000000Z") safeTo := sanitizeRecipient(to) base := fmt.Sprintf("%s__email__%s", ts, safeTo) emlPath := filepath.Join(m.dir, base+".eml") textPath := filepath.Join(m.dir, base+".txt") htmlPath := filepath.Join(m.dir, base+".html") emlContent := fmt.Sprintf("Subject: %s\nTo: %s\nDate: %s\n\nTEXT:\n%s\n\nHTML:\n%s\n", subject, to, time.Now().UTC().Format(time.RFC3339), textBody, htmlBody) if err := os.WriteFile(emlPath, []byte(emlContent), 0o644); err != nil { return fmt.Errorf("write sink eml: %w", err) } if err := os.WriteFile(textPath, []byte(textBody), 0o644); err != nil { return fmt.Errorf("write sink text: %w", err) } if err := os.WriteFile(htmlPath, []byte(htmlBody), 0o644); err != nil { return fmt.Errorf("write sink html: %w", err) } return nil } func sanitizeRecipient(to string) string { clean := strings.TrimSpace(to) if clean == "" { return "unknown" } return sinkFileSafe.ReplaceAllString(clean, "_") }