Files
Memoh/internal/email/types.go
T
BBQ cc5f00355f feat: add email service with multi-adapter support (#146)
* feat: add email service with multi-adapter support

Implement a full-stack email service with global provider management,
per-bot bindings with granular read/write permissions, outbox audit
storage, and MCP tool integration for direct mailbox access.

Backend:
- Email providers: CRUD with dynamic config schema (generic SMTP/IMAP, Mailgun)
- Generic adapter: go-mail (SMTP) + go-imap/v2 (IMAP IDLE real-time push via
  UnilateralDataHandler + UID-based tracking + periodic check fallback)
- Mailgun adapter: mailgun-go/v5 with dual inbound mode (webhook + poll)
- Bot email bindings: per-bot provider binding with independent r/w permissions
- Outbox: outbound email audit log with status tracking
- Trigger: inbound emails push notification to bot_inbox (from/subject only,
  LLM reads full content on demand via MCP tools)
- MailboxReader interface: on-demand IMAP queries for listing/reading emails
- MCP tools: email_accounts, email_send, email_list (paginated mailbox),
  email_read (by UID) — all with multi-binding and provider_id selection
- Webhook: /email/mailgun/webhook/:config_id (JWT-skipped, signature-verified)
- DB migration: 0019_add_email (email_providers, bot_email_bindings, email_outbox)

Frontend:
- Email Providers page: /email-providers with MasterDetailSidebarLayout
- Dynamic config form rendered from ordered provider meta schema with i18n keys
- Bot detail: Email tab with bindings management + outbox audit table
- Sidebar navigation entry
- Full i18n support (en + zh)
- Auto-generated SDK from Swagger

Closes #17

* feat(email): trigger bot conversation immediately on inbound email

Instead of only storing an inbox item and waiting for the next chat,
the email trigger now proactively invokes the conversation resolver
so the bot processes new emails right away — aligned with the
schedule/heartbeat trigger pattern.

* fix: lint

---------

Co-authored-by: Acbox <acbox0328@gmail.com>
2026-02-28 21:03:59 +08:00

141 lines
4.7 KiB
Go

package email
import "time"
type ProviderName string
// FieldSchema describes a single configuration field for dynamic form generation.
type FieldSchema struct {
Key string `json:"key"`
Type string `json:"type"`
Title string `json:"title,omitempty"`
Description string `json:"description,omitempty"`
Required bool `json:"required,omitempty"`
Enum []string `json:"enum,omitempty"`
Example any `json:"example,omitempty"`
Order int `json:"order"`
}
type ConfigSchema struct {
Fields []FieldSchema `json:"fields"`
}
type ProviderMeta struct {
Provider string `json:"provider"`
DisplayName string `json:"display_name"`
ConfigSchema ConfigSchema `json:"config_schema"`
}
// ---- Provider CRUD DTOs ----
type CreateProviderRequest struct {
Name string `json:"name"`
Provider ProviderName `json:"provider"`
Config map[string]any `json:"config,omitempty"`
}
type UpdateProviderRequest struct {
Name *string `json:"name,omitempty"`
Provider *ProviderName `json:"provider,omitempty"`
Config map[string]any `json:"config,omitempty"`
}
type ProviderResponse struct {
ID string `json:"id"`
Name string `json:"name"`
Provider string `json:"provider"`
Config map[string]any `json:"config,omitempty"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
// ---- Binding DTOs ----
type CreateBindingRequest struct {
EmailProviderID string `json:"email_provider_id"`
EmailAddress string `json:"email_address"`
CanRead *bool `json:"can_read,omitempty"`
CanWrite *bool `json:"can_write,omitempty"`
CanDelete *bool `json:"can_delete,omitempty"`
Config map[string]any `json:"config,omitempty"`
}
type UpdateBindingRequest struct {
EmailAddress *string `json:"email_address,omitempty"`
CanRead *bool `json:"can_read,omitempty"`
CanWrite *bool `json:"can_write,omitempty"`
CanDelete *bool `json:"can_delete,omitempty"`
Config map[string]any `json:"config,omitempty"`
}
type BindingResponse struct {
ID string `json:"id"`
BotID string `json:"bot_id"`
EmailProviderID string `json:"email_provider_id"`
EmailAddress string `json:"email_address"`
CanRead bool `json:"can_read"`
CanWrite bool `json:"can_write"`
CanDelete bool `json:"can_delete"`
Config map[string]any `json:"config,omitempty"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
// ---- Email message types ----
type OutboundEmail struct {
To []string `json:"to"`
Subject string `json:"subject"`
Body string `json:"body"`
HTML bool `json:"html,omitempty"`
}
type InboundEmail struct {
MessageID string `json:"message_id"`
From string `json:"from"`
To []string `json:"to"`
Subject string `json:"subject"`
BodyText string `json:"body_text"`
BodyHTML string `json:"body_html"`
Attachments []any `json:"attachments,omitempty"`
Headers map[string]any `json:"headers,omitempty"`
ReceivedAt time.Time `json:"received_at"`
}
// ---- Inbox / Outbox response DTOs ----
type InboxItemResponse struct {
ID string `json:"id"`
ProviderID string `json:"provider_id"`
BotID string `json:"bot_id,omitempty"`
MessageID string `json:"message_id"`
From string `json:"from"`
To []string `json:"to"`
Subject string `json:"subject"`
BodyText string `json:"body_text,omitempty"`
BodyHTML string `json:"body_html,omitempty"`
Attachments []any `json:"attachments,omitempty"`
Headers any `json:"headers,omitempty"`
ReceivedAt time.Time `json:"received_at"`
Status string `json:"status"`
CreatedAt time.Time `json:"created_at"`
}
type OutboxItemResponse struct {
ID string `json:"id"`
ProviderID string `json:"provider_id"`
BotID string `json:"bot_id"`
MessageID string `json:"message_id,omitempty"`
From string `json:"from"`
To []string `json:"to"`
Subject string `json:"subject"`
BodyText string `json:"body_text,omitempty"`
BodyHTML string `json:"body_html,omitempty"`
Attachments []any `json:"attachments,omitempty"`
Status string `json:"status"`
Error string `json:"error,omitempty"`
SentAt time.Time `json:"sent_at,omitempty"`
CreatedAt time.Time `json:"created_at"`
}