mirror of
https://github.com/warmbly/warmbly.git
synced 2026-09-06 16:01:28 +00:00
299 lines
10 KiB
Go
299 lines
10 KiB
Go
package aitools
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
|
|
"github.com/warmbly/warmbly/internal/models"
|
|
"github.com/warmbly/warmbly/internal/pkg/generation"
|
|
)
|
|
|
|
// Mailbox (email-account) management tools. Reads run as the user over their
|
|
// mailboxes; writes require PermManageEmails. Connecting a mailbox (OAuth /
|
|
// SMTP+IMAP onboarding) is deliberately NOT exposed: it is an interactive
|
|
// credential flow with no API-permission gate, so it stays a human action.
|
|
func (d Deps) registerMailboxTools(r *Registry) {
|
|
if d.Emails == nil {
|
|
return
|
|
}
|
|
|
|
r.Register(Tool{
|
|
Name: "get_mailbox",
|
|
Description: "Get one sender mailbox's full configuration (status, sending limits, warmup settings, signature).",
|
|
InputSchema: objectSchema(map[string]any{
|
|
"email_account_id": strProp("The mailbox UUID (from list_mailboxes)."),
|
|
}, "email_account_id"),
|
|
Risk: generation.RiskRead,
|
|
RequiredOrgPerm: models.PermViewCampaigns,
|
|
RequiredAPIPerm: models.APIPermReadEmails,
|
|
Handler: d.getMailbox,
|
|
})
|
|
|
|
r.Register(Tool{
|
|
Name: "update_mailbox",
|
|
Description: "Update a mailbox's settings: display name, reply-to, cold-send cap (campaign_limit), minimum gap between sends, status, and warmup parameters. Only provided fields change.",
|
|
InputSchema: objectSchema(map[string]any{
|
|
"email_account_id": strProp("The mailbox UUID."),
|
|
"name": strProp("Display name."),
|
|
"reply_to": strProp("Reply-to address."),
|
|
"status": enumProp("Mailbox status.", "active", "inactive"),
|
|
"campaign_limit": intProp("Max cold-campaign emails per day for this mailbox, 0 to 5000. Default 50; 30-50/day is the safe cold-outreach band."),
|
|
"min_wait_time": intProp("Minimum seconds between sends."),
|
|
"warmup": boolProp("Enable or disable warmup."),
|
|
"warmup_base": intProp("Warmup starting emails/day."),
|
|
"warmup_max": intProp("Warmup ceiling emails/day."),
|
|
"warmup_increase": intProp("Warmup daily ramp increment."),
|
|
"warmup_reply_rate": intProp("Warmup reply rate percent."),
|
|
"warmup_days": intProp("Warmup active days bitmask."),
|
|
}, "email_account_id"),
|
|
Risk: generation.RiskWrite,
|
|
RequiredOrgPerm: models.PermManageEmails,
|
|
RequiredAPIPerm: models.APIPermWriteEmails,
|
|
Handler: d.updateMailbox,
|
|
})
|
|
|
|
r.Register(Tool{
|
|
Name: "set_mailbox_warmup",
|
|
Description: "Start, pause, resume, or stop warmup on a mailbox. start/resume preserve ramp progress; stop disables warmup.",
|
|
InputSchema: objectSchema(map[string]any{
|
|
"email_account_id": strProp("The mailbox UUID."),
|
|
"action": enumProp("Warmup lifecycle action.", "start", "pause", "resume", "stop"),
|
|
}, "email_account_id", "action"),
|
|
Risk: generation.RiskWrite,
|
|
RequiredOrgPerm: models.PermManageEmails,
|
|
RequiredAPIPerm: models.APIPermWriteEmails,
|
|
Handler: d.setMailboxWarmup,
|
|
})
|
|
|
|
r.Register(Tool{
|
|
Name: "set_mailbox_tracking_domain",
|
|
Description: "Set (and verify) a mailbox's custom tracking domain via its CNAME.",
|
|
InputSchema: objectSchema(map[string]any{
|
|
"email_account_id": strProp("The mailbox UUID."),
|
|
"domain": strProp("The custom tracking domain to use."),
|
|
}, "email_account_id", "domain"),
|
|
Risk: generation.RiskWrite,
|
|
RequiredOrgPerm: models.PermManageEmails,
|
|
RequiredAPIPerm: models.APIPermWriteEmails,
|
|
Handler: d.setMailboxTrackingDomain,
|
|
})
|
|
|
|
r.Register(Tool{
|
|
Name: "get_warmup_ban_status",
|
|
Description: "Read a mailbox's warmup pool standing (healthy, watched, throttled, quarantined, or blocked) and any ban reason.",
|
|
InputSchema: objectSchema(map[string]any{
|
|
"email_account_id": strProp("The mailbox UUID."),
|
|
}, "email_account_id"),
|
|
Risk: generation.RiskRead,
|
|
RequiredOrgPerm: models.PermViewCampaigns,
|
|
RequiredAPIPerm: models.APIPermReadEmails,
|
|
Handler: d.getWarmupBanStatus,
|
|
})
|
|
|
|
r.Register(Tool{
|
|
Name: "submit_warmup_appeal",
|
|
Description: "Appeal a mailbox's warmup pool ban with a reason.",
|
|
InputSchema: objectSchema(map[string]any{
|
|
"email_account_id": strProp("The mailbox UUID."),
|
|
"reason": strProp("Why the ban should be lifted."),
|
|
}, "email_account_id", "reason"),
|
|
Risk: generation.RiskWrite,
|
|
RequiredOrgPerm: models.PermManageEmails,
|
|
RequiredAPIPerm: models.APIPermWriteEmails,
|
|
Handler: d.submitWarmupAppeal,
|
|
})
|
|
|
|
r.Register(Tool{
|
|
Name: "disconnect_mailbox",
|
|
Description: "Disconnect (remove) a mailbox from the workspace. Destructive; stops all sending from it and requires user approval.",
|
|
InputSchema: objectSchema(map[string]any{
|
|
"email_account_id": strProp("The mailbox UUID."),
|
|
}, "email_account_id"),
|
|
Risk: generation.RiskWrite,
|
|
RequiredOrgPerm: models.PermManageEmails,
|
|
RequiredAPIPerm: models.APIPermWriteEmails,
|
|
Handler: d.disconnectMailbox,
|
|
})
|
|
}
|
|
|
|
func (d Deps) getMailbox(ctx context.Context, inv Invocation, args json.RawMessage) (string, error) {
|
|
in, err := decodeArgs[struct {
|
|
EmailAccountID string `json:"email_account_id"`
|
|
}](args)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
if _, err := parseUUIDArg(in.EmailAccountID); err != nil {
|
|
return "", err
|
|
}
|
|
mb, xerr := d.Emails.Get(ctx, inv.UserID.String(), in.EmailAccountID)
|
|
if xerr != nil {
|
|
return "", fromErrx(xerr)
|
|
}
|
|
return jsonResult(mb)
|
|
}
|
|
|
|
func (d Deps) updateMailbox(ctx context.Context, inv Invocation, args json.RawMessage) (string, error) {
|
|
in, err := decodeArgs[struct {
|
|
EmailAccountID string `json:"email_account_id"`
|
|
Name *string `json:"name"`
|
|
ReplyTo *string `json:"reply_to"`
|
|
Status *string `json:"status"`
|
|
CampaignLimit *int `json:"campaign_limit"`
|
|
MinWaitTime *int `json:"min_wait_time"`
|
|
Warmup *bool `json:"warmup"`
|
|
WarmupBase *int `json:"warmup_base"`
|
|
WarmupMax *int `json:"warmup_max"`
|
|
WarmupIncrease *int `json:"warmup_increase"`
|
|
WarmupReplyRate *int `json:"warmup_reply_rate"`
|
|
WarmupDays *int `json:"warmup_days"`
|
|
}](args)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
aid, err := parseUUIDArg(in.EmailAccountID)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
upd := &models.UpdateEmail{
|
|
Name: in.Name,
|
|
ReplyTo: in.ReplyTo,
|
|
Status: in.Status,
|
|
CampaignLimit: in.CampaignLimit,
|
|
MinWaitTime: in.MinWaitTime,
|
|
Warmup: in.Warmup,
|
|
WarmupBase: in.WarmupBase,
|
|
WarmupMax: in.WarmupMax,
|
|
WarmupIncrease: in.WarmupIncrease,
|
|
WarmupReplyRate: in.WarmupReplyRate,
|
|
WarmupDays: in.WarmupDays,
|
|
}
|
|
mb, xerr := d.Emails.Update(ctx, inv.UserID.String(), in.EmailAccountID, upd)
|
|
if xerr != nil {
|
|
return "", fromErrx(xerr)
|
|
}
|
|
d.logAudit(ctx, inv, models.AuditActionUpdate, models.AuditEntityEmailAccount, &aid, nil)
|
|
return jsonResult(mb)
|
|
}
|
|
|
|
func (d Deps) setMailboxWarmup(ctx context.Context, inv Invocation, args json.RawMessage) (string, error) {
|
|
in, err := decodeArgs[struct {
|
|
EmailAccountID string `json:"email_account_id"`
|
|
Action string `json:"action"`
|
|
}](args)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
aid, err := parseUUIDArg(in.EmailAccountID)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
var action models.AuditAction
|
|
switch in.Action {
|
|
case "start":
|
|
action = models.AuditActionStart
|
|
case "pause":
|
|
action = models.AuditActionPause
|
|
case "resume":
|
|
action = models.AuditActionResume
|
|
case "stop":
|
|
action = models.AuditActionStop
|
|
default:
|
|
return "", ErrInvalidArgs
|
|
}
|
|
mb, xerr := d.Emails.SetWarmupLifecycle(ctx, inv.UserID.String(), in.EmailAccountID, in.Action)
|
|
if xerr != nil {
|
|
return "", fromErrx(xerr)
|
|
}
|
|
d.logAudit(ctx, inv, action, models.AuditEntityEmailAccount, &aid, map[string]string{"warmup": in.Action})
|
|
return jsonResult(mb)
|
|
}
|
|
|
|
func (d Deps) setMailboxTrackingDomain(ctx context.Context, inv Invocation, args json.RawMessage) (string, error) {
|
|
in, err := decodeArgs[struct {
|
|
EmailAccountID string `json:"email_account_id"`
|
|
Domain string `json:"domain"`
|
|
}](args)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
aid, err := parseUUIDArg(in.EmailAccountID)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
if in.Domain == "" {
|
|
return "", ErrInvalidArgs
|
|
}
|
|
status, xerr := d.Emails.UpdateTrackingDomain(ctx, inv.OrgID.String(), in.EmailAccountID, in.Domain)
|
|
if xerr != nil {
|
|
return "", fromErrx(xerr)
|
|
}
|
|
d.logAudit(ctx, inv, models.AuditActionUpdate, models.AuditEntityEmailAccount, &aid, nil)
|
|
return jsonResult(status)
|
|
}
|
|
|
|
func (d Deps) getWarmupBanStatus(ctx context.Context, inv Invocation, args json.RawMessage) (string, error) {
|
|
if d.Warmup == nil {
|
|
return "", ErrToolNotFound
|
|
}
|
|
in, err := decodeArgs[struct {
|
|
EmailAccountID string `json:"email_account_id"`
|
|
}](args)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
aid, err := parseUUIDArg(in.EmailAccountID)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
status, xerr := d.Warmup.GetBanStatus(ctx, inv.UserID, aid)
|
|
if xerr != nil {
|
|
return "", fromErrx(xerr)
|
|
}
|
|
return jsonResult(status)
|
|
}
|
|
|
|
func (d Deps) submitWarmupAppeal(ctx context.Context, inv Invocation, args json.RawMessage) (string, error) {
|
|
if d.Warmup == nil {
|
|
return "", ErrToolNotFound
|
|
}
|
|
in, err := decodeArgs[struct {
|
|
EmailAccountID string `json:"email_account_id"`
|
|
Reason string `json:"reason"`
|
|
}](args)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
aid, err := parseUUIDArg(in.EmailAccountID)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
if in.Reason == "" {
|
|
return "", ErrInvalidArgs
|
|
}
|
|
appealID, xerr := d.Warmup.SubmitAppeal(ctx, inv.UserID, aid, in.Reason)
|
|
if xerr != nil {
|
|
return "", fromErrx(xerr)
|
|
}
|
|
d.logAudit(ctx, inv, models.AuditActionUpdate, models.AuditEntityEmailAccount, &aid, nil)
|
|
return jsonResult(map[string]any{"ok": true, "appeal_id": appealID.String()})
|
|
}
|
|
|
|
func (d Deps) disconnectMailbox(ctx context.Context, inv Invocation, args json.RawMessage) (string, error) {
|
|
in, err := decodeArgs[struct {
|
|
EmailAccountID string `json:"email_account_id"`
|
|
}](args)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
aid, err := parseUUIDArg(in.EmailAccountID)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
if xerr := d.Emails.Delete(ctx, inv.UserID.String(), in.EmailAccountID); xerr != nil {
|
|
return "", fromErrx(xerr)
|
|
}
|
|
d.logAudit(ctx, inv, models.AuditActionDisconnect, models.AuditEntityEmailAccount, &aid, nil)
|
|
return jsonResult(map[string]any{"ok": true, "email_account_id": in.EmailAccountID})
|
|
}
|