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(`

Faktura %s

Bifogat finner du din faktura.

Vid frågor, kontakta oss.

`, 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(`

Offert %s

Bifogat finner du din offert.

Offerten är giltig i 30 dagar.

`, 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(`

Välkommen %s!

Tack för att du valde oss. Vi ser fram emot ett gott samarbete.

Logga in på din dashboard för att se dina uppgifter och hantera dina ärenden.

`, 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(`

Påminnelse: Faktura %s

Detta är en påminnelse om att faktura %s på %.2f %s förfaller %s.

Vänligen betala i tid för att undvika påminnelseavgifter.

`, 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(`

Återställ ditt lösenord

Du har begärt att återställa ditt lösenord.

Återställ lösenord

Om du inte begärt detta, ignorera detta meddelande.

`, 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 }