feat: Go backend, enhanced search, new widgets, Docker deploy
Major changes: - Add Go backend (backend/) with microservices architecture - Enhanced master-agents-svc: reranker, content-classifier, stealth-crawler, proxy-manager, media-search, fastClassifier, language detection - New web-svc widgets: KnowledgeCard, ProductCard, ProfileCard, VideoCard, UnifiedCard, CardGallery, InlineImageGallery, SourcesPanel, RelatedQuestions - Improved discover-svc with discover-db integration - Docker deployment improvements (Caddyfile, vendor.sh, BUILD.md) - Library-svc: project_id schema migration - Remove deprecated finance-svc and travel-svc - Localization improvements across services Made-with: Cursor
This commit is contained in:
215
backend/internal/computer/connectors/email.go
Normal file
215
backend/internal/computer/connectors/email.go
Normal file
@@ -0,0 +1,215 @@
|
||||
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()
|
||||
}
|
||||
Reference in New Issue
Block a user