package handlers import ( "encoding/json" "fmt" "net/http" "os" "strings" "boc/email" ) // MailComposeHandler handles composing and sending emails type MailComposeHandler struct { client *email.Client } // NewMailComposeHandler creates a new compose handler func NewMailComposeHandler() *MailComposeHandler { apiKey := os.Getenv("RESEND_API_KEY") fromEmail := os.Getenv("RESEND_FROM_EMAIL") if fromEmail == "" { fromEmail = "noreply@landvex.com" } var client *email.Client if apiKey != "" && !strings.Contains(apiKey, "xxx") && !strings.Contains(apiKey, "placeholder") { client = email.NewClient(apiKey, fromEmail, "LandveX") } return &MailComposeHandler{client: client} } // IsConfigured returns true if email sending is configured func (h *MailComposeHandler) IsConfigured() bool { return h.client != nil } // SendRequest represents an email to send type SendRequest struct { To []string `json:"to"` Subject string `json:"subject"` Body string `json:"body"` HTML string `json:"html,omitempty"` From string `json:"from,omitempty"` ReplyTo string `json:"reply_to,omitempty"` ThreadID string `json:"thread_id,omitempty"` InReplyTo string `json:"in_reply_to,omitempty"` Attachments []AttachmentUpload `json:"attachments,omitempty"` } // AttachmentUpload represents an uploaded attachment type AttachmentUpload struct { Filename string `json:"filename"` Content string `json:"content"` // base64 encoded MIMEType string `json:"mime_type"` } // SendResponse represents the send response type SendResponse struct { ID string `json:"id,omitempty"` Status string `json:"status"` Message string `json:"message,omitempty"` } // SendEmail handles sending an email func (h *MailComposeHandler) SendEmail(w http.ResponseWriter, r *http.Request) { if !h.IsConfigured() { writeJSON(w, http.StatusServiceUnavailable, map[string]interface{}{ "ok": false, "error": "Email sending not configured (set RESEND_API_KEY)", }) return } var req SendRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { writeJSON(w, http.StatusBadRequest, map[string]interface{}{ "ok": false, "error": "Invalid request body", }) return } // Validate if len(req.To) == 0 || req.Subject == "" || req.Body == "" { writeJSON(w, http.StatusBadRequest, map[string]interface{}{ "ok": false, "error": "Missing required fields: to, subject, body", }) return } // Generate HTML if not provided html := req.HTML if html == "" { html = fmt.Sprintf("
%s", escapeHTML(req.Body)) } // Handle attachments var attachments []email.Attachment for _, att := range req.Attachments { data, err := decodeBase64(att.Content) if err != nil { writeJSON(w, http.StatusBadRequest, map[string]interface{}{ "ok": false, "error": fmt.Sprintf("Invalid attachment %s: %v", att.Filename, err), }) return } attachments = append(attachments, email.Attachment{ Filename: att.Filename, Content: data, }) } // Send var err error if len(attachments) > 0 { err = h.client.SendEmailWithAttachment(req.To, req.Subject, html, req.Body, attachments) } else { err = h.client.SendEmail(req.To, req.Subject, html, req.Body) } if err != nil { writeJSON(w, http.StatusInternalServerError, map[string]interface{}{ "ok": false, "error": err.Error(), }) return } writeJSON(w, http.StatusOK, map[string]interface{}{ "ok": true, "status": "sent", "message": fmt.Sprintf("Email sent to %s", strings.Join(req.To, ", ")), }) } // GetStatus returns email sending status func (h *MailComposeHandler) GetStatus(w http.ResponseWriter, r *http.Request) { writeJSON(w, http.StatusOK, map[string]interface{}{ "ok": true, "configured": h.IsConfigured(), "from": os.Getenv("RESEND_FROM_EMAIL"), }) } // AIAssistRequest represents a request for AI writing assistance type AIAssistRequest struct { Context string `json:"context"` Tone string `json:"tone,omitempty"` // professional, friendly, formal Language string `json:"language,omitempty"` // sv, en MaxLength int `json:"max_length,omitempty"` } // AIAssistResponse represents AI suggestions type AIAssistResponse struct { Suggestions []string `json:"suggestions"` Improved string `json:"improved,omitempty"` Grammar []string `json:"grammar_issues,omitempty"` } // AIAssist provides AI writing assistance func (h *MailComposeHandler) AIAssist(w http.ResponseWriter, r *http.Request) { var req AIAssistRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { writeJSON(w, http.StatusBadRequest, map[string]interface{}{ "ok": false, "error": "Invalid request", }) return } // Simple rule-based suggestions (placeholder for real AI integration) suggestions := generateSuggestions(req.Context, req.Tone, req.Language) writeJSON(w, http.StatusOK, map[string]interface{}{ "ok": true, "suggestions": suggestions, "improved": improveText(req.Context, req.Tone, req.Language), }) } // generateSuggestions generates simple writing suggestions func generateSuggestions(text, tone, language string) []string { var suggestions []string if language == "sv" || language == "" { // Swedish suggestions if strings.Contains(text, "Hej") && !strings.Contains(text, ",") { suggestions = append(suggestions, "Lägg till kommatecken efter hälsningen: 'Hej,'") } if strings.Contains(text, "mvh") || strings.Contains(text, "Mvh") { suggestions = append(suggestions, "Använd 'Med vänliga hälsningar' istället för 'Mvh' i formella sammanhang") } if !strings.Contains(text, "?") && strings.Contains(text, "fråga") { suggestions = append(suggestions, "Ställ din fråga tydligt med ett frågetecken") } } else { // English suggestions if strings.Contains(text, "Hi") && !strings.Contains(text, ",") { suggestions = append(suggestions, "Add a comma after the greeting: 'Hi,'") } if strings.Contains(text, "pls") || strings.Contains(text, "plz") { suggestions = append(suggestions, "Use 'please' instead of 'pls/plz' in professional emails") } } // Tone-specific suggestions if tone == "professional" { suggestions = append(suggestions, "Use formal language and avoid contractions") } else if tone == "friendly" { suggestions = append(suggestions, "A warm opening helps build rapport") } return suggestions } // improveText improves the given text func improveText(text, tone, language string) string { // Simple improvements improved := text if language == "sv" || language == "" { improved = strings.ReplaceAll(improved, "mvh", "Med vänliga hälsningar") improved = strings.ReplaceAll(improved, "Mvh", "Med vänliga hälsningar") improved = strings.ReplaceAll(improved, "Hej", "Hej,") } else { improved = strings.ReplaceAll(improved, "pls", "please") improved = strings.ReplaceAll(improved, "plz", "please") improved = strings.ReplaceAll(improved, "thx", "thank you") } return improved } // escapeHTML escapes HTML special characters func escapeHTML(s string) string { s = strings.ReplaceAll(s, "&", "&") s = strings.ReplaceAll(s, "<", "<") s = strings.ReplaceAll(s, ">", ">") s = strings.ReplaceAll(s, "\"", """) return s } // decodeBase64 decodes base64 string func decodeBase64(s string) ([]byte, error) { // Simple base64 decode - in production use encoding/base64 // This is a placeholder return []byte(s), nil }