package email import ( "context" "fmt" "log/slog" ) // ChatTriggerer triggers a proactive bot conversation (e.g. when a new email arrives). type ChatTriggerer interface { TriggerBotChat(ctx context.Context, botID, content string) error } // Trigger notifies bots when a new email arrives and immediately triggers // the bot's LLM to process it. type Trigger struct { logger *slog.Logger emailService *Service chatTriggerer ChatTriggerer } func NewTrigger(log *slog.Logger, emailService *Service, chatTriggerer ChatTriggerer) *Trigger { return &Trigger{ logger: log.With(slog.String("component", "email_trigger")), emailService: emailService, chatTriggerer: chatTriggerer, } } // HandleInbound triggers a conversation for each bound bot so it can process // the incoming email. func (t *Trigger) HandleInbound(ctx context.Context, providerID string, mail InboundEmail) error { t.logger.Info("new email arrived", slog.String("provider_id", providerID), slog.String("from", mail.From), slog.String("subject", mail.Subject)) bindings, err := t.emailService.ListReadableBindingsByProvider(ctx, providerID) if err != nil { t.logger.Error("failed to list readable bindings", slog.Any("error", err)) return err } for _, binding := range bindings { content := fmt.Sprintf("New email received at %s from %s — %s", binding.EmailAddress, mail.From, mail.Subject) t.logger.Info("bot notified of new email", slog.String("bot_id", binding.BotID), slog.String("from", mail.From)) if t.chatTriggerer != nil { go func(botID, text string) { if err := t.chatTriggerer.TriggerBotChat(ctx, botID, text); err != nil { t.logger.Error("failed to trigger bot chat for email", slog.String("bot_id", botID), slog.Any("error", err)) } }(binding.BotID, content) } } return nil }