package email import ( "bytes" "context" "encoding/json" "fmt" "io" "net/http" "strings" "time" ) type resendTransport struct { apiKey string client *http.Client } func newResendTransport(apiKey string, client *http.Client) *resendTransport { if client == nil { client = &http.Client{Timeout: 20 * time.Second} } return &resendTransport{apiKey: apiKey, client: client} } func (r *resendTransport) Name() string { return ProviderResend } func (r *resendTransport) Send(ctx context.Context, from FromIdentity, msg Outbound) error { to := strings.TrimSpace(msg.To) if to == "" { return ErrInvalidRecipient } payload := map[string]any{ "from": from.Formatted(), "to": []string{to}, "subject": msg.Subject, } if strings.TrimSpace(msg.HTML) != "" { payload["html"] = msg.HTML } if strings.TrimSpace(msg.Text) != "" { payload["text"] = msg.Text } if strings.TrimSpace(from.ReplyTo) != "" { payload["reply_to"] = from.ReplyTo } if len(msg.Headers) > 0 { payload["headers"] = msg.Headers } body, err := json.Marshal(payload) if err != nil { return err } req, err := http.NewRequestWithContext(ctx, http.MethodPost, "https://api.resend.com/emails", bytes.NewReader(body)) if err != nil { return err } req.Header.Set("Authorization", "Bearer "+r.apiKey) req.Header.Set("Content-Type", "application/json") res, err := r.client.Do(req) if err != nil { return fmt.Errorf("resend request failed") } defer res.Body.Close() raw, _ := io.ReadAll(io.LimitReader(res.Body, 1<<20)) if res.StatusCode >= 300 { return fmt.Errorf("resend api %d", res.StatusCode) } _ = raw return nil } // verifyResendDomain checks the API key can list domains and that domain appears verified. // When Resend returns no domains (sandbox), returns ok=false with a clear message. func verifyResendDomain(ctx context.Context, apiKey, domain string, client *http.Client) (bool, string, error) { domain = strings.ToLower(strings.TrimSpace(domain)) if domain == "" { return false, "domain required", nil } if client == nil { client = &http.Client{Timeout: 15 * time.Second} } req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://api.resend.com/domains", nil) if err != nil { return false, "", err } req.Header.Set("Authorization", "Bearer "+apiKey) res, err := client.Do(req) if err != nil { return false, "", fmt.Errorf("resend domains request failed") } defer res.Body.Close() raw, err := io.ReadAll(io.LimitReader(res.Body, 1<<20)) if err != nil { return false, "", err } if res.StatusCode == http.StatusUnauthorized { return false, "invalid Resend API key", nil } if res.StatusCode >= 300 { return false, fmt.Sprintf("resend domains api %d", res.StatusCode), nil } var parsed struct { Data []struct { Name string `json:"name"` Status string `json:"status"` } `json:"data"` } if err := json.Unmarshal(raw, &parsed); err != nil { return false, "unexpected resend response", nil } for _, d := range parsed.Data { if strings.EqualFold(d.Name, domain) { st := strings.ToLower(strings.TrimSpace(d.Status)) if st == "verified" || st == "ok" || st == "active" { return true, "domain verified with Resend", nil } return false, fmt.Sprintf("Resend domain status is %q", d.Status), nil } } return false, "domain not found in Resend account — add and verify it in the Resend dashboard", nil }