happyDomain/internal/notification/email_sender.go
Pierre-Olivier Mercier 78e8deb71c notification: add channel senders for email, webhook, and UnifiedPush
Implement ChannelSender interface with three backends:
- EmailSender: reuses existing Mailer, sends Markdown-formatted alerts
- WebhookSender: HTTP POST with JSON payload and optional HMAC signature
- UnifiedPushSender: HTTP POST following the UnifiedPush protocol

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 04:48:49 +07:00

80 lines
2.5 KiB
Go

// This file is part of the happyDomain (R) project.
// Copyright (c) 2020-2026 happyDomain
// Authors: Pierre-Olivier Mercier, et al.
//
// This program is offered under a commercial and under the AGPL license.
// For commercial licensing, contact us at <contact@happydomain.org>.
//
// For AGPL licensing:
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package notification
import (
"fmt"
"net/mail"
"strings"
"git.happydns.org/happyDomain/model"
)
// EmailSender sends notifications via email using the existing Mailer.
type EmailSender struct {
mailer happydns.Mailer
}
// NewEmailSender creates a new EmailSender.
func NewEmailSender(mailer happydns.Mailer) *EmailSender {
return &EmailSender{mailer: mailer}
}
func (s *EmailSender) Send(channel *happydns.NotificationChannel, payload *NotificationPayload) error {
addr := channel.Config.EmailAddress
if addr == "" && payload.User != nil {
addr = payload.User.Email
}
if addr == "" {
return fmt.Errorf("no email address configured for channel %s", channel.Id)
}
to := &mail.Address{Address: addr}
if payload.User != nil {
to.Name = payload.User.Email
}
subject := fmt.Sprintf("[happyDomain] %s: %s", payload.DomainName, payload.NewStatus)
var body strings.Builder
fmt.Fprintf(&body, "## Status Change: %s -> %s\n\n", payload.OldStatus, payload.NewStatus)
fmt.Fprintf(&body, "**Domain:** %s\n\n", payload.DomainName)
fmt.Fprintf(&body, "**Checker:** %s\n\n", payload.CheckerID)
if len(payload.States) > 0 {
body.WriteString("### Rule Results\n\n")
for _, state := range payload.States {
fmt.Fprintf(&body, "- **%s** (%s): %s\n", state.Code, state.Status, state.Message)
}
body.WriteString("\n")
}
if payload.Annotation != "" {
fmt.Fprintf(&body, "**Note:** %s\n\n", payload.Annotation)
}
if payload.BaseURL != "" {
fmt.Fprintf(&body, "[View in happyDomain](%s)\n", payload.BaseURL)
}
return s.mailer.SendMail(to, subject, body.String())
}