mail.go
⎇
Raw
1package web
2
3import (
4 "bytes"
5 "html/template"
6 "net/http"
7 "net/url"
8 "strings"
9
10 "forge.gpl-nas.de/physiobase/internal/mailer"
11 "forge.gpl-nas.de/physiobase/internal/store"
12)
13
14// E-Mail-Clients ignorieren externe CSS-Dateien → schlichte Inline-Stile.
15var mailHTML = template.Must(template.New("mail").Parse(`<!doctype html>
16<html lang="de"><head><meta charset="utf-8"><title>{{.Title}}</title></head>
17<body style="margin:0;padding:0;background:#f4f6f8;">
18<div style="max-width:640px;margin:0 auto;padding:24px;font-family:-apple-system,'Segoe UI',Roboto,Arial,sans-serif;font-size:16px;line-height:1.55;color:#1d2a33;background:#ffffff;">
19<h1 style="font-size:22px;margin:0 0 4px;color:#16a085;">{{.Title}}</h1>
20<p style="margin:0 0 18px;color:#5c6b75;font-size:14px;">{{.Date}}</p>
21<p>Guten Tag {{.Name}},</p>
22<p>hier ist Ihre Zusammenfassung zum Nachlesen.</p>
23<div>{{.Body}}</div>
24<p style="margin-top:24px;">Viele Grüße<br>{{.Practice}}</p>
25<p style="margin-top:24px;font-size:12px;color:#8a979f;">Diese Nachricht enthält persönliche Gesundheitsinformationen und ist nur für {{.Name}} bestimmt.</p>
26</div></body></html>`))
27
28func mailText(name, title, date, body, practice string) string {
29 var b strings.Builder
30 b.WriteString("Guten Tag " + name + ",\n\nhier ist Ihre Zusammenfassung zum Nachlesen.\n\n")
31 b.WriteString(title + " (" + date + ")\n" + strings.Repeat("=", len([]rune(title))) + "\n\n")
32 b.WriteString(body + "\n\nViele Grüße\n" + practice + "\n\n")
33 b.WriteString("Diese Nachricht enthält persönliche Gesundheitsinformationen und ist nur für " + name + " bestimmt.\n")
34 return b.String()
35}
36
37// mailBlockReason: warum (noch) nicht gesendet werden kann; leer = alles bereit.
38func (s *Server) mailBlockReason(p store.Patient) string {
39 switch {
40 case !s.cfg.SMTP.Enabled():
41 return "Der E-Mail-Versand ist nicht eingerichtet (PHYSIOBASE_SMTP_* in .env)."
42 case p.Email == "":
43 return "Für diesen Patienten ist keine E-Mail-Adresse hinterlegt."
44 case !mailer.ValidAddress(p.Email):
45 return "Die hinterlegte E-Mail-Adresse ist ungültig."
46 case !p.EmailConsent:
47 return "Es fehlt die Einwilligung in den Versand per E-Mail (Stammdaten des Patienten)."
48 }
49 return ""
50}
51
52func (s *Server) sendSummary(w http.ResponseWriter, r *http.Request) {
53 ctx := r.Context()
54 x, p, ok := s.loadSummary(w, r)
55 if !ok {
56 return
57 }
58 if reason := s.mailBlockReason(p); reason != "" {
59 redirect(w, r, "/zusammenfassung/%d?fehler=%s#senden", x.ID, url.QueryEscape(reason))
60 return
61 }
62 body := Printable(x.Body)
63 var html bytes.Buffer
64 if err := mailHTML.Execute(&html, map[string]any{
65 "Title": x.Title, "Date": deDate(x.Date), "Name": p.Name, "Practice": s.cfg.PracticeName,
66 "Body": funcs["md"].(func(string) template.HTML)(body),
67 }); err != nil {
68 s.fail(w, r, err)
69 return
70 }
71 msg := mailer.Message{
72 To: p.Email, Subject: x.Title, FromName: s.cfg.PracticeName,
73 Text: mailText(p.Name, x.Title, deDate(x.Date), body, s.cfg.PracticeName), HTML: html.String(),
74 }
75 err := mailer.Send(ctx, s.cfg.SMTP, msg)
76 entry := store.MailLog{Recipient: p.Email, Subject: x.Title, OK: err == nil}
77 if err != nil {
78 entry.Error = err.Error()
79 s.log.Error("e-mail fehlgeschlagen", "zusammenfassung", x.ID, "err", err)
80 } else {
81 s.log.Info("e-mail gesendet", "zusammenfassung", x.ID, "patient", p.ID)
82 }
83 if lerr := s.st.LogMail(ctx, x.ID, p.ID, entry); lerr != nil {
84 s.log.Error("versandprotokoll", "err", lerr)
85 }
86 if err != nil {
87 redirect(w, r, "/zusammenfassung/%d?fehler=%s#senden", x.ID, url.QueryEscape("Senden fehlgeschlagen: "+err.Error()))
88 return
89 }
90 redirect(w, r, "/zusammenfassung/%d?gesendet=1#senden", x.ID)
91}
92