Files
pulse-api/internal/service/email.go
2026-02-06 11:19:55 +00:00

134 lines
4.5 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package service
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
)
type EmailService struct {
apiKey string
fromEmail string
fromName string
baseURL string
}
type ResendRequest struct {
From string `json:"from"`
To []string `json:"to"`
Subject string `json:"subject"`
HTML string `json:"html"`
}
func NewEmailService(apiKey, fromEmail, fromName, baseURL string) *EmailService {
return &EmailService{
apiKey: apiKey,
fromEmail: fromEmail,
fromName: fromName,
baseURL: baseURL,
}
}
func (s *EmailService) SendVerificationEmail(toEmail, username, token string) error {
verifyURL := fmt.Sprintf("%s/verify-email?token=%s", s.baseURL, token)
html := fmt.Sprintf(`
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<style>
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; line-height: 1.6; color: #333; background: #FAF9F6; margin: 0; padding: 20px; }
.container { max-width: 500px; margin: 0 auto; padding: 40px; background: white; border-radius: 24px; }
h2 { color: #0f766e; margin-bottom: 8px; }
.button { display: inline-block; padding: 14px 28px; background: linear-gradient(135deg, #14b8a6, #0f766e); color: white; text-decoration: none; border-radius: 16px; font-weight: 600; }
.link { color: #0f766e; word-break: break-all; }
.footer { margin-top: 30px; font-size: 13px; color: #888; }
</style>
</head>
<body>
<div class="container">
<h2>Привет, %s! 👋</h2>
<p>Спасибо за регистрацию в Pulse! Подтверди свой email, нажав на кнопку ниже:</p>
<p style="margin: 30px 0;"><a href="%s" class="button">Подтвердить email</a></p>
<p>Или скопируй ссылку:<br><a href="%s" class="link">%s</a></p>
<p class="footer">Ссылка действительна 24 часа. Если ты не регистрировался — просто проигнорируй это письмо.</p>
</div>
</body>
</html>`, username, verifyURL, verifyURL, verifyURL)
return s.send(toEmail, "Подтверди свой email — Pulse", html)
}
func (s *EmailService) SendPasswordResetEmail(toEmail, username, token string) error {
resetURL := fmt.Sprintf("%s/reset-password?token=%s", s.baseURL, token)
html := fmt.Sprintf(`
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<style>
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; line-height: 1.6; color: #333; background: #FAF9F6; margin: 0; padding: 20px; }
.container { max-width: 500px; margin: 0 auto; padding: 40px; background: white; border-radius: 24px; }
h2 { color: #0f766e; margin-bottom: 8px; }
.button { display: inline-block; padding: 14px 28px; background: linear-gradient(135deg, #14b8a6, #0f766e); color: white; text-decoration: none; border-radius: 16px; font-weight: 600; }
.link { color: #0f766e; word-break: break-all; }
.footer { margin-top: 30px; font-size: 13px; color: #888; }
</style>
</head>
<body>
<div class="container">
<h2>Сброс пароля</h2>
<p>Привет, %s! Мы получили запрос на сброс пароля для твоего аккаунта в Pulse.</p>
<p style="margin: 30px 0;"><a href="%s" class="button">Сбросить пароль</a></p>
<p>Или скопируй ссылку:<br><a href="%s" class="link">%s</a></p>
<p class="footer">Ссылка действительна 1 час. Если ты не запрашивал сброс пароля — просто проигнорируй это письмо.</p>
</div>
</body>
</html>`, username, resetURL, resetURL, resetURL)
return s.send(toEmail, "Сброс пароля — Pulse", html)
}
func (s *EmailService) send(to, subject, html string) error {
if s.apiKey == "" {
fmt.Printf("[EMAIL] Would send to %s: %s\n", to, subject)
return nil
}
payload := ResendRequest{
From: fmt.Sprintf("%s <%s>", s.fromName, s.fromEmail),
To: []string{to},
Subject: subject,
HTML: html,
}
jsonData, err := json.Marshal(payload)
if err != nil {
return err
}
req, err := http.NewRequest("POST", "https://api.resend.com/emails", bytes.NewBuffer(jsonData))
if err != nil {
return err
}
req.Header.Set("Authorization", "Bearer "+s.apiKey)
req.Header.Set("Content-Type", "application/json")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode >= 400 {
return fmt.Errorf("resend API error: %d", resp.StatusCode)
}
return nil
}