package connectors import ( "context" "crypto/tls" "errors" "fmt" "net/smtp" "strings" ) type EmailConfig struct { SMTPHost string SMTPPort int Username string Password string FromAddress string FromName string UseTLS bool AllowHTML bool } type EmailConnector struct { cfg EmailConfig } func NewEmailConnector(cfg EmailConfig) *EmailConnector { return &EmailConnector{cfg: cfg} } func (e *EmailConnector) ID() string { return "email" } func (e *EmailConnector) Name() string { return "Email" } func (e *EmailConnector) Description() string { return "Send emails via SMTP" } func (e *EmailConnector) GetActions() []Action { return []Action{ { Name: "send", Description: "Send an email", Schema: map[string]interface{}{ "type": "object", "properties": map[string]interface{}{ "to": map[string]interface{}{"type": "string", "description": "Recipient email address"}, "subject": map[string]interface{}{"type": "string", "description": "Email subject"}, "body": map[string]interface{}{"type": "string", "description": "Email body"}, "html": map[string]interface{}{"type": "boolean", "description": "Whether body is HTML"}, "cc": map[string]interface{}{"type": "string", "description": "CC recipients (comma-separated)"}, "bcc": map[string]interface{}{"type": "string", "description": "BCC recipients (comma-separated)"}, }, }, Required: []string{"to", "subject", "body"}, }, } } func (e *EmailConnector) Validate(params map[string]interface{}) error { if _, ok := params["to"]; !ok { return errors.New("'to' is required") } if _, ok := params["subject"]; !ok { return errors.New("'subject' is required") } if _, ok := params["body"]; !ok { return errors.New("'body' is required") } return nil } func (e *EmailConnector) Execute(ctx context.Context, action string, params map[string]interface{}) (interface{}, error) { switch action { case "send": return e.send(ctx, params) default: return nil, errors.New("unknown action: " + action) } } func (e *EmailConnector) send(ctx context.Context, params map[string]interface{}) (interface{}, error) { to := params["to"].(string) subject := params["subject"].(string) body := params["body"].(string) isHTML := false if html, ok := params["html"].(bool); ok { isHTML = html && e.cfg.AllowHTML } var cc, bcc []string if ccStr, ok := params["cc"].(string); ok && ccStr != "" { cc = strings.Split(ccStr, ",") for i := range cc { cc[i] = strings.TrimSpace(cc[i]) } } if bccStr, ok := params["bcc"].(string); ok && bccStr != "" { bcc = strings.Split(bccStr, ",") for i := range bcc { bcc[i] = strings.TrimSpace(bcc[i]) } } from := e.cfg.FromAddress if e.cfg.FromName != "" { from = fmt.Sprintf("%s <%s>", e.cfg.FromName, e.cfg.FromAddress) } var msg strings.Builder msg.WriteString(fmt.Sprintf("From: %s\r\n", from)) msg.WriteString(fmt.Sprintf("To: %s\r\n", to)) if len(cc) > 0 { msg.WriteString(fmt.Sprintf("Cc: %s\r\n", strings.Join(cc, ", "))) } msg.WriteString(fmt.Sprintf("Subject: %s\r\n", subject)) msg.WriteString("MIME-Version: 1.0\r\n") if isHTML { msg.WriteString("Content-Type: text/html; charset=\"UTF-8\"\r\n") } else { msg.WriteString("Content-Type: text/plain; charset=\"UTF-8\"\r\n") } msg.WriteString("\r\n") msg.WriteString(body) recipients := []string{to} recipients = append(recipients, cc...) recipients = append(recipients, bcc...) addr := fmt.Sprintf("%s:%d", e.cfg.SMTPHost, e.cfg.SMTPPort) var auth smtp.Auth if e.cfg.Username != "" && e.cfg.Password != "" { auth = smtp.PlainAuth("", e.cfg.Username, e.cfg.Password, e.cfg.SMTPHost) } var err error if e.cfg.UseTLS { err = e.sendWithTLS(addr, auth, e.cfg.FromAddress, recipients, []byte(msg.String())) } else { err = smtp.SendMail(addr, auth, e.cfg.FromAddress, recipients, []byte(msg.String())) } if err != nil { return map[string]interface{}{ "success": false, "error": err.Error(), }, err } return map[string]interface{}{ "success": true, "to": to, "subject": subject, "recipients": len(recipients), }, nil } func (e *EmailConnector) sendWithTLS(addr string, auth smtp.Auth, from string, to []string, msg []byte) error { tlsConfig := &tls.Config{ ServerName: e.cfg.SMTPHost, } conn, err := tls.Dial("tcp", addr, tlsConfig) if err != nil { return err } defer conn.Close() client, err := smtp.NewClient(conn, e.cfg.SMTPHost) if err != nil { return err } defer client.Close() if auth != nil { if err := client.Auth(auth); err != nil { return err } } if err := client.Mail(from); err != nil { return err } for _, recipient := range to { if err := client.Rcpt(recipient); err != nil { return err } } w, err := client.Data() if err != nil { return err } _, err = w.Write(msg) if err != nil { return err } err = w.Close() if err != nil { return err } return client.Quit() }