109 lines
3.6 KiB
Go
109 lines
3.6 KiB
Go
package email
|
|
|
|
// EmailService provides high-level operations for the Email channel.
|
|
// Reference: Chatwoot app/services/channel/email/process_mail_service.rb
|
|
// + app/services/channel/email/fetch_imap_email_service.rb
|
|
//
|
|
// This service coordinates:
|
|
// - IMAPListener (IMAP polling and message fetching)
|
|
// - SMTPSender (outbound email delivery)
|
|
// - Content processing (HTML/text conversion, sanitization)
|
|
// - Address mapping (email → contact identification)
|
|
// - Thread context (In-Reply-To, References for conversation threading)
|
|
//
|
|
// The service implements EmailServiceInterface defined in provider/email.go,
|
|
// which is the contract between EmailProvider and the email sub-package.
|
|
|
|
import (
|
|
"context"
|
|
"net/mail"
|
|
"strings"
|
|
|
|
channelpkg "github.com/gochat/gochat/internal/channel"
|
|
"github.com/gochat/gochat/internal/model"
|
|
)
|
|
|
|
// Service coordinates Email channel operations.
|
|
// This is the concrete implementation of EmailServiceInterface.
|
|
type Service struct {
|
|
imapListener *IMAPListener
|
|
smtpSender *SMTPSender
|
|
}
|
|
|
|
// NewService creates a new Email service with IMAP and SMTP components.
|
|
func NewService() *Service {
|
|
return &Service{
|
|
imapListener: NewIMAPListener(),
|
|
smtpSender: NewSMTPSender(),
|
|
}
|
|
}
|
|
|
|
// === EmailServiceInterface implementation ===
|
|
|
|
// ValidateIMAPConnection checks IMAP server connectivity and auth.
|
|
func (s *Service) ValidateIMAPConnection(config channelpkg.ChannelConfig) error {
|
|
return s.imapListener.ValidateConnection(config)
|
|
}
|
|
|
|
// ValidateSMTPConnection checks SMTP server connectivity and auth.
|
|
func (s *Service) ValidateSMTPConnection(config channelpkg.ChannelConfig) error {
|
|
return s.smtpSender.ValidateConnection(config)
|
|
}
|
|
|
|
// FetchIMAPMessages polls IMAP for new messages and converts them to IncomingMessage.
|
|
func (s *Service) FetchIMAPMessages(ctx context.Context, inbox *model.Inbox, config channelpkg.ChannelConfig) ([]*channelpkg.IncomingMessage, error) {
|
|
return s.imapListener.Fetch(ctx, inbox, config)
|
|
}
|
|
|
|
// SendViaSMTP sends an outgoing email and returns the message ID.
|
|
func (s *Service) SendViaSMTP(config channelpkg.ChannelConfig, from, to, subject, body string) (string, error) {
|
|
return s.smtpSender.Send(config, from, to, subject, body)
|
|
}
|
|
|
|
// === Address processing helpers (unique to service.go) ===
|
|
|
|
// extractEmailAddress extracts just the email address from a full address string.
|
|
// Handles formats like "John Doe <john@example.com>" or "john@example.com".
|
|
func extractEmailAddress(addr string) string {
|
|
if addr == "" {
|
|
return ""
|
|
}
|
|
// Try RFC 5322 parsing first
|
|
addresses, err := mail.ParseAddressList(addr)
|
|
if err == nil && len(addresses) > 0 {
|
|
return addresses[0].Address
|
|
}
|
|
// Fallback: extract content between < and >
|
|
start := strings.Index(addr, "<")
|
|
end := strings.Index(addr, ">")
|
|
if start >= 0 && end > start {
|
|
return strings.TrimSpace(addr[start+1:end])
|
|
}
|
|
// Plain email address
|
|
return strings.TrimSpace(addr)
|
|
}
|
|
|
|
// extractDisplayName extracts the display name from a full address string.
|
|
// Handles formats like "John Doe <john@example.com>" → "John Doe".
|
|
func extractDisplayName(addr string) string {
|
|
if addr == "" {
|
|
return ""
|
|
}
|
|
// Try RFC 5322 parsing first
|
|
addresses, err := mail.ParseAddressList(addr)
|
|
if err == nil && len(addresses) > 0 {
|
|
if addresses[0].Name != "" {
|
|
return addresses[0].Name
|
|
}
|
|
return "" // No display name, just email address
|
|
}
|
|
// Fallback: extract content before < if present
|
|
start := strings.Index(addr, "<")
|
|
if start >= 0 {
|
|
name := strings.TrimSpace(addr[:start])
|
|
// Remove surrounding quotes
|
|
name = strings.Trim(name, "\"")
|
|
return name
|
|
}
|
|
return "" // Just an email address, no display name
|
|
} |