feat: send email notifications for missed messages

- New cron job in cluster
- Handle presence and other concerns
- Toggle in app to disable email notifications
- Handles other edge cases such as cooldown period
- Simple html email with simple message

Closes #117
This commit is contained in:
talksik
2026-04-09 14:15:34 -07:00
parent 6fe5af8d8d
commit 51c4c6c822
25 changed files with 725 additions and 29 deletions
+39 -8
View File
@@ -50,10 +50,11 @@ func NewHandler(authSvc auth.AuthService, humanSvc human.Service, networkSvc net
// Response DTOs
type Human struct {
Id string `json:"id"`
Email string `json:"email"`
EmailPrefix string `json:"email_prefix"`
CreatedAt time.Time `json:"created_at"`
Id string `json:"id"`
Email string `json:"email"`
EmailPrefix string `json:"email_prefix"`
EmailNotificationsEnabled bool `json:"email_notifications_enabled"`
CreatedAt time.Time `json:"created_at"`
}
type Network struct {
@@ -267,6 +268,35 @@ func (h *Handler) GetCurrentHuman(w http.ResponseWriter, r *http.Request) {
json.NewEncoder(w).Encode(dto)
}
type UpdateSettingsRequest struct {
EmailNotificationsEnabled *bool `json:"email_notifications_enabled"`
}
// UpdateSettings updates the authenticated human's settings
func (h *Handler) UpdateSettings(w http.ResponseWriter, r *http.Request) {
humanId, ok := middleware.HumanIdFromContext(r.Context())
if !ok {
http.Error(w, "unauthorized", http.StatusUnauthorized)
return
}
var req UpdateSettingsRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, "invalid request body", http.StatusBadRequest)
return
}
if req.EmailNotificationsEnabled != nil {
if err := h.humanSvc.UpdateEmailNotificationsEnabled(r.Context(), humanId, *req.EmailNotificationsEnabled); err != nil {
slog.Error("failed to update email notifications setting", "error", err, "humanId", humanId)
http.Error(w, "internal server error", http.StatusInternalServerError)
return
}
}
w.WriteHeader(http.StatusNoContent)
}
// ============================================================================
// Network Handlers
// ============================================================================
@@ -953,10 +983,11 @@ func waitlistEntryToDTO(e *waitlist.WaitlistEntry) WaitlistEntryResponse {
func humanToDTO(h *human.Human) Human {
return Human{
Id: h.ID,
Email: h.Email,
EmailPrefix: h.EmailPrefix,
CreatedAt: h.CreatedAt,
Id: h.ID,
Email: h.Email,
EmailPrefix: h.EmailPrefix,
EmailNotificationsEnabled: h.EmailNotificationsEnabled,
CreatedAt: h.CreatedAt,
}
}