Files
boc/backend/email/resend.go
T

221 lines
5.9 KiB
Go
Raw Normal View History

package email
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"time"
)
const resendAPIURL = "https://api.resend.com/emails"
// Client handles email sending via Resend
type Client struct {
apiKey string
fromEmail string
fromName string
httpClient *http.Client
}
// NewClient creates a new Resend email client
func NewClient(apiKey, fromEmail, fromName string) *Client {
return &Client{
apiKey: apiKey,
fromEmail: fromEmail,
fromName: fromName,
httpClient: &http.Client{
Timeout: 30 * time.Second,
},
}
}
// Email represents an email to be sent
type Email struct {
To []string `json:"to"`
From string `json:"from"`
Subject string `json:"subject"`
HTML string `json:"html,omitempty"`
Text string `json:"text,omitempty"`
Attachments []Attachment `json:"attachments,omitempty"`
Headers map[string]string `json:"headers,omitempty"`
}
// Attachment represents an email attachment
type Attachment struct {
Filename string `json:"filename"`
Content []byte `json:"content"`
}
// SendEmail sends an email via Resend
func (c *Client) SendEmail(to []string, subject, htmlBody, textBody string) error {
from := c.fromEmail
if c.fromName != "" {
from = fmt.Sprintf("%s <%s>", c.fromName, c.fromEmail)
}
email := Email{
To: to,
From: from,
Subject: subject,
HTML: htmlBody,
Text: textBody,
}
return c.send(email)
}
// SendEmailWithAttachment sends an email with attachments
func (c *Client) SendEmailWithAttachment(to []string, subject, htmlBody, textBody string, attachments []Attachment) error {
from := c.fromEmail
if c.fromName != "" {
from = fmt.Sprintf("%s <%s>", c.fromName, c.fromEmail)
}
email := Email{
To: to,
From: from,
Subject: subject,
HTML: htmlBody,
Text: textBody,
Attachments: attachments,
}
return c.send(email)
}
// SendInvoice sends an invoice email with PDF attachment
func (c *Client) SendInvoice(to []string, invoiceNumber string, pdfData []byte, htmlBody string) error {
if htmlBody == "" {
htmlBody = fmt.Sprintf(`
<h2>Faktura %s</h2>
<p>Bifogat finner du din faktura.</p>
<p>Vid frågor, kontakta oss.</p>
`, invoiceNumber)
}
attachments := []Attachment{
{
Filename: fmt.Sprintf("faktura-%s.pdf", invoiceNumber),
Content: pdfData,
},
}
return c.SendEmailWithAttachment(
to,
fmt.Sprintf("Faktura %s", invoiceNumber),
htmlBody,
fmt.Sprintf("Faktura %s bifogad.", invoiceNumber),
attachments,
)
}
// SendQuote sends a quote email with PDF attachment
func (c *Client) SendQuote(to []string, quoteNumber string, pdfData []byte, htmlBody string) error {
if htmlBody == "" {
htmlBody = fmt.Sprintf(`
<h2>Offert %s</h2>
<p>Bifogat finner du din offert.</p>
<p>Offerten är giltig i 30 dagar.</p>
`, quoteNumber)
}
attachments := []Attachment{
{
Filename: fmt.Sprintf("offert-%s.pdf", quoteNumber),
Content: pdfData,
},
}
return c.SendEmailWithAttachment(
to,
fmt.Sprintf("Offert %s", quoteNumber),
htmlBody,
fmt.Sprintf("Offert %s bifogad.", quoteNumber),
attachments,
)
}
// SendWelcome sends a welcome email to a new customer
func (c *Client) SendWelcome(to []string, customerName string) error {
htmlBody := fmt.Sprintf(`
<h2>Välkommen %s!</h2>
<p>Tack för att du valde oss. Vi ser fram emot ett gott samarbete.</p>
<p>Logga in på din dashboard för att se dina uppgifter och hantera dina ärenden.</p>
`, customerName)
return c.SendEmail(
to,
"Välkommen!",
htmlBody,
fmt.Sprintf("Välkommen %s! Tack för att du valde oss.", customerName),
)
}
// SendPaymentReminder sends a payment reminder
func (c *Client) SendPaymentReminder(to []string, invoiceNumber string, amount float64, currency string, dueDate time.Time) error {
htmlBody := fmt.Sprintf(`
<h2>Påminnelse: Faktura %s</h2>
<p>Detta är en påminnelse om att faktura %s på <strong>%.2f %s</strong> förfaller %s.</p>
<p>Vänligen betala i tid för att undvika påminnelseavgifter.</p>
`, invoiceNumber, invoiceNumber, amount, currency, dueDate.Format("2006-01-02"))
return c.SendEmail(
to,
fmt.Sprintf("Påminnelse: Faktura %s", invoiceNumber),
htmlBody,
fmt.Sprintf("Påminnelse: Faktura %s på %.2f %s förfaller %s.", invoiceNumber, amount, currency, dueDate.Format("2006-01-02")),
)
}
// SendPasswordReset sends a password reset email
func (c *Client) SendPasswordReset(to []string, resetToken string, resetURL string) error {
htmlBody := fmt.Sprintf(`
<h2>Återställ ditt lösenord</h2>
<p>Du har begärt att återställa ditt lösenord.</p>
<p><a href="%s" style="background:#C96A3A;color:white;padding:12px 24px;text-decoration:none;border-radius:6px;">Återställ lösenord</a></p>
<p>Om du inte begärt detta, ignorera detta meddelande.</p>
`, resetURL+"?token="+resetToken)
return c.SendEmail(
to,
"Återställ ditt lösenord",
htmlBody,
"Klicka på länken för att återställa ditt lösenord: "+resetURL+"?token="+resetToken,
)
}
func (c *Client) send(email Email) error {
payload, err := json.Marshal(email)
if err != nil {
return fmt.Errorf("marshal email: %w", err)
}
req, err := http.NewRequest("POST", resendAPIURL, bytes.NewReader(payload))
if err != nil {
return fmt.Errorf("create request: %w", err)
}
req.Header.Set("Authorization", "Bearer "+c.apiKey)
req.Header.Set("Content-Type", "application/json")
resp, err := c.httpClient.Do(req)
if err != nil {
return fmt.Errorf("send request: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusCreated {
var errResp struct {
Error string `json:"error"`
Status int `json:"status"`
}
if err := json.NewDecoder(resp.Body).Decode(&errResp); err != nil {
return fmt.Errorf("resend API error (status %d)", resp.StatusCode)
}
return fmt.Errorf("resend API error: %s (status %d)", errResp.Error, resp.StatusCode)
}
return nil
}