mirror of
https://github.com/warmbly/warmbly.git
synced 2026-09-06 16:01:28 +00:00
145 lines
6.8 KiB
Go
145 lines
6.8 KiB
Go
package models
|
|
|
|
import (
|
|
"time"
|
|
|
|
"github.com/google/uuid"
|
|
)
|
|
|
|
type WarmupToken struct {
|
|
Token uuid.UUID `json:"token"`
|
|
TaskID uuid.UUID `json:"task_id"`
|
|
SenderAccountID uuid.UUID `json:"sender_account_id"`
|
|
RecipientAccountID uuid.UUID `json:"recipient_account_id"`
|
|
ConversationTheme string `json:"conversation_theme"`
|
|
// ContentSource records which content cohort produced this send
|
|
// ("static" or "ai") so the A/B harness can compare spam-placement
|
|
// rate by cohort. ConversationID points at the cached warmup_conversations
|
|
// row when the body came from the AI bank (nil for static content).
|
|
ContentSource string `json:"content_source"`
|
|
ConversationID *uuid.UUID `json:"conversation_id,omitempty"`
|
|
// ConversationTurn is zero for the opening and increments for each reply.
|
|
ConversationTurn int `json:"conversation_turn"`
|
|
// Subject is what was actually sent, and SentMessageID is the Message-ID
|
|
// the provider put on the wire (which is not the one we minted on Graph).
|
|
// Both exist so the recipient can still resolve this token when the verify
|
|
// header did not survive delivery.
|
|
Subject string `json:"subject"`
|
|
SentMessageID string `json:"sent_message_id"`
|
|
CreatedAt time.Time `json:"created_at"`
|
|
ConsumedAt *time.Time `json:"consumed_at,omitempty"`
|
|
ExpiresAt time.Time `json:"expires_at"`
|
|
}
|
|
|
|
// WarmupEmailAction represents actions to perform on a detected warmup email.
|
|
//
|
|
// For Gmail accounts the worker uses GmailID to issue Users.Messages.Modify
|
|
// requests. For IMAP-backed accounts (Outlook + custom SMTP/IMAP) the worker
|
|
// needs UID + the source mailbox's UIDValidity to locate the message; the
|
|
// mailbox name is then resolved against the worker's cached folder list.
|
|
type WarmupEmailAction struct {
|
|
UserID uuid.UUID `json:"user_id"`
|
|
EmailID uuid.UUID `json:"email_id"`
|
|
GmailID string `json:"gmail_id"`
|
|
UID uint32 `json:"uid"`
|
|
MailboxUIDValidity uint32 `json:"mailbox_uid_validity"`
|
|
// RFCMessageID is the immutable RFC 5322 Message-ID. Graph provider ids
|
|
// change when a message is moved (copy+delete), so the worker re-resolves
|
|
// the live Graph id from this stable key at action time.
|
|
RFCMessageID string `json:"rfc_message_id,omitempty"`
|
|
Actions []string `json:"actions"` // "move_to_warmbly", "mark_read", "remove_from_spam", "mark_important"
|
|
|
|
// DelaySeconds is retained for wire compatibility but is now always 0: the
|
|
// recipient-side "dwell" is owned by the consumer's durable schedule
|
|
// (warmup_pending_engagements + the engagement poller), which publishes the
|
|
// immediate leg (folder + spam-rescue) now and the delayed leg (read /
|
|
// important / star) when due. The worker runs whatever it receives
|
|
// immediately. This survives a worker restart, which the old in-process
|
|
// timer did not.
|
|
DelaySeconds int `json:"delay_seconds,omitempty"`
|
|
}
|
|
|
|
type WarmupHealthState string
|
|
|
|
const (
|
|
WarmupHealthHealthy WarmupHealthState = "healthy"
|
|
WarmupHealthWatch WarmupHealthState = "watch"
|
|
WarmupHealthThrottled WarmupHealthState = "throttled"
|
|
WarmupHealthQuarantined WarmupHealthState = "quarantined"
|
|
WarmupHealthBlocked WarmupHealthState = "blocked"
|
|
)
|
|
|
|
type WarmupParticipantHealth struct {
|
|
PoolID uuid.UUID `json:"pool_id"`
|
|
PoolType string `json:"pool_type"`
|
|
EmailAccountID uuid.UUID `json:"email_account_id"`
|
|
JoinedAt time.Time `json:"joined_at"`
|
|
BlockedAt *time.Time `json:"blocked_at,omitempty"`
|
|
BlockedUntil *time.Time `json:"blocked_until,omitempty"`
|
|
BlockedReason *string `json:"blocked_reason,omitempty"`
|
|
SpamScore int `json:"spam_score"`
|
|
HealthState WarmupHealthState `json:"health_state"`
|
|
LastHealthScore float64 `json:"last_health_score"`
|
|
LastHealthReason *string `json:"last_health_reason,omitempty"`
|
|
LastHealthEvaluatedAt *time.Time `json:"last_health_evaluated_at,omitempty"`
|
|
// Health signals older than this are not counted against the mailbox: its
|
|
// join time for a new participant, the #195 fix deploy for one that predates
|
|
// it. Keeps the first evaluation from judging a mailbox on a period nothing
|
|
// was evaluating.
|
|
HealthSignalsFrom time.Time `json:"health_signals_from"`
|
|
}
|
|
|
|
// WarmupBanStatus is the user-facing view of a mailbox's warmup standing,
|
|
// returned by GetBanStatus so the dashboard can show why warmup is blocked and
|
|
// whether the user can appeal.
|
|
type WarmupBanStatus struct {
|
|
EmailAccountID uuid.UUID `json:"email_account_id"`
|
|
Blocked bool `json:"blocked"`
|
|
HealthState string `json:"health_state"`
|
|
Reason string `json:"reason,omitempty"`
|
|
BlockedAt *time.Time `json:"blocked_at,omitempty"`
|
|
BlockedUntil *time.Time `json:"blocked_until,omitempty"`
|
|
CanAppeal bool `json:"can_appeal"`
|
|
PendingAppeal bool `json:"pending_appeal"`
|
|
}
|
|
|
|
type WarmupPoolHealthSummary struct {
|
|
TotalParticipants int `json:"total_participants"`
|
|
ByState map[string]int `json:"by_state"`
|
|
AvgSpamScore float64 `json:"avg_spam_score"`
|
|
AvgSpamPlacement float64 `json:"avg_spam_placement_rate"`
|
|
// Keyed by who runs the recipient's mail, the vocabulary routing reads.
|
|
SpamPlacementByProvider map[string]int `json:"spam_placement_by_provider"`
|
|
BlockedCount int `json:"blocked_count"`
|
|
AtRiskCount int `json:"at_risk_count"`
|
|
}
|
|
|
|
type WarmupHealthMetrics struct {
|
|
SentLast7d int `json:"sent_last_7d"`
|
|
|
|
// SpamReportsLast7d is the combined warmup-pool spam signal (placement +
|
|
// user complaints). Retained for callers that want a single number.
|
|
SpamReportsLast7d int `json:"spam_reports_last_7d"`
|
|
|
|
// SpamPlacementsLast7d counts warmup messages that landed in the
|
|
// recipient's Junk/Spam folder on delivery. SpamPlacementRate is the
|
|
// ratio against SentLast7d.
|
|
SpamPlacementsLast7d int `json:"spam_placements_last_7d"`
|
|
SpamPlacementRate float64 `json:"spam_placement_rate"`
|
|
|
|
// UserComplaintsLast7d counts warmup messages the recipient explicitly
|
|
// flagged as spam. WarmupComplaintRate is the ratio against SentLast7d.
|
|
// This is distinct from external-recipient complaints captured in
|
|
// deliverability_events (ComplaintsLast30d / ComplaintRate below).
|
|
UserComplaintsLast7d int `json:"user_complaints_last_7d"`
|
|
WarmupComplaintRate float64 `json:"warmup_complaint_rate"`
|
|
|
|
InvalidAttemptsLast24 int `json:"invalid_attempts_last_24h"`
|
|
SpamScore int `json:"spam_score"`
|
|
ComplaintsLast30d int `json:"complaints_last_30d"`
|
|
DeliveredLast30d int `json:"delivered_last_30d"`
|
|
ComplaintRate float64 `json:"complaint_rate"`
|
|
BouncesLast30d int `json:"bounces_last_30d"`
|
|
BounceRate float64 `json:"bounce_rate"`
|
|
}
|