| 205 | } |
| 206 | |
| 207 | func parseEmailSummary(message string) (EmailSummary, error) { |
| 208 | var summary EmailSummary |
| 209 | |
| 210 | // Decode quoted-printable message |
| 211 | reader := quotedprintable.NewReader(strings.NewReader(message)) |
| 212 | content, err := io.ReadAll(reader) |
| 213 | if err != nil { |
| 214 | return summary, xerrors.Errorf("decode email content: %w", err) |
| 215 | } |
| 216 | |
| 217 | contentStr := string(content) |
| 218 | scanner := bufio.NewScanner(strings.NewReader(contentStr)) |
| 219 | |
| 220 | // Extract Subject and Date from headers. |
| 221 | // Date is used to measure latency. |
| 222 | for scanner.Scan() { |
| 223 | line := scanner.Text() |
| 224 | if line == "" { |
| 225 | break |
| 226 | } |
| 227 | if prefix, found := strings.CutPrefix(line, "Subject: "); found { |
| 228 | summary.Subject = prefix |
| 229 | } else if prefix, found := strings.CutPrefix(line, "Date: "); found { |
| 230 | if parsedDate, err := time.Parse(time.RFC1123Z, prefix); err == nil { |
| 231 | summary.Date = parsedDate |
| 232 | } |
| 233 | } |
| 234 | } |
| 235 | |
| 236 | // Extract notification ID from decoded email content |
| 237 | // Notification ID is present in the email footer like this |
| 238 | // <p><a href="http://127.0.0.1:3000/settings/notifications?disabled=4e19c0ac-94e1-4532-9515-d1801aa283b2" style="color: #2563eb; text-decoration: none;">Stop receiving emails like this</a></p> |
| 239 | if matches := notificationTemplateIDRegex.FindStringSubmatch(contentStr); len(matches) > 1 { |
| 240 | summary.NotificationTemplateID, err = uuid.Parse(matches[1]) |
| 241 | if err != nil { |
| 242 | return summary, xerrors.Errorf("parse notification ID: %w", err) |
| 243 | } |
| 244 | } |
| 245 | |
| 246 | return summary, nil |
| 247 | } |