feat: add email notification service with SMTP support
Some checks failed
Build and Deploy GooSeek / build-and-deploy (push) Failing after 8m22s

- Create pkg/email package (sender, templates, types)
- SMTP client with TLS, rate limiting, async sending
- HTML email templates with GooSeek branding
- Integrate welcome + password reset emails in auth-svc
- Add limit warning emails (80%/100%) in llm-svc middleware
- Add space invite endpoint with email notification in thread-svc
- Add GetUserEmail helper in JWT middleware
- Add SMTP config to .env, config.go, K8s configmap

Made-with: Cursor
This commit is contained in:
home
2026-03-03 02:50:17 +03:00
parent 7a40ff629e
commit 52134df4d1
12 changed files with 767 additions and 92 deletions

View File

@@ -3,14 +3,17 @@ package middleware
import (
"context"
"database/sql"
"log"
"github.com/gofiber/fiber/v2"
"github.com/gooseek/backend/internal/usage"
"github.com/gooseek/backend/pkg/email"
"github.com/gooseek/backend/pkg/metrics"
)
type LLMLimitsConfig struct {
UsageRepo *usage.Repository
UsageRepo *usage.Repository
EmailSender *email.Sender
}
func LLMLimits(config LLMLimitsConfig) fiber.Handler {
@@ -42,6 +45,8 @@ func LLMLimits(config LLMLimitsConfig) fiber.Handler {
}
metrics.RecordRateLimitHit("llm-svc", clientIP, reason)
sendLimitEmail(config, userID, tier, limits.LLMRequestsPerDay, limits.LLMRequestsPerDay)
return c.Status(429).JSON(fiber.Map{
"error": reason,
"tier": tier,
@@ -50,12 +55,68 @@ func LLMLimits(config LLMLimitsConfig) fiber.Handler {
"upgradeUrl": "/settings/billing",
})
}
checkLimitWarning(config, userID, tier)
}
return c.Next()
}
}
func checkLimitWarning(config LLMLimitsConfig, userID, tier string) {
if config.UsageRepo == nil || config.EmailSender == nil || !config.EmailSender.IsConfigured() {
return
}
go func() {
todayUsage, err := config.UsageRepo.GetTodayUsage(context.Background(), userID)
if err != nil || todayUsage == nil {
return
}
limits := usage.GetLimits(tier)
if limits.LLMRequestsPerDay == 0 {
return
}
percentage := (todayUsage.LLMRequests * 100) / limits.LLMRequestsPerDay
if percentage >= 80 && percentage < 100 {
userEmail := getUserEmail(config.UsageRepo, userID)
if userEmail != "" {
if err := config.EmailSender.SendLimitWarning(userEmail, "", todayUsage.LLMRequests, limits.LLMRequestsPerDay, tier); err != nil {
log.Printf("[email] Limit warning send error: %v", err)
}
}
}
}()
}
func sendLimitEmail(config LLMLimitsConfig, userID, tier string, usageCount, limitCount int) {
if config.EmailSender == nil || !config.EmailSender.IsConfigured() {
return
}
go func() {
userEmail := getUserEmail(config.UsageRepo, userID)
if userEmail != "" {
if err := config.EmailSender.SendLimitWarning(userEmail, "", usageCount, limitCount, tier); err != nil {
log.Printf("[email] Limit exceeded email error: %v", err)
}
}
}()
}
func getUserEmail(repo *usage.Repository, userID string) string {
if repo == nil {
return ""
}
emailAddr, err := repo.GetUserEmail(context.Background(), userID)
if err != nil {
return ""
}
return emailAddr
}
type UsageTracker struct {
repo *usage.Repository
}