mirror of
https://github.com/warmbly/warmbly.git
synced 2026-09-05 16:02:48 +00:00
* feat: close the two halves of the delivery-signal loop that were still open, complaints and domain-auth refusals: a spam complaint arrives as mail long after the send succeeded, and nothing read those reports, so the strongest negative signal a sender gets never reached the complaint rate, the suppression list or the breaker; internal/pkg/arf parses RFC 5965 feedback reports worker-side alongside the existing DSN path, takes the LAST Message-ID because the reported mail's headers follow the report's own, and records only abuse-type reports so a not-spam report cannot be inverted into a complaint; separately a receiving server refusing mail because the SENDING DOMAIN failed its authentication (5.7.515, 5.7.26) was classified as SERVER_UNREACHABLE and retried forever, and it is now its own hard code that blames the domain rather than the address, leaves the recipient unsuppressed, and brings that domain's DNS re-check forward so the sweep confirms the verdict the send gate acts on * feat: make the complaint path actually reachable, and stop it trusting the report: selectTextParts only ever picked text/plain and text/html, so an RFC 5965 report's message/feedback-report and message/rfc822 parts never reached the worker and both Feedback-Type and the reported Message-ID were invisible, which would have left this feature inert and has been quietly weakening DSN parsing too; report parts are now selected as plain text; the complainer is taken from the RESOLVED SEND rather than the report body, because a report is unauthenticated mail anyone able to reach the mailbox could forge and honouring the address it names would let a forger suppress a contact the send never went to; Original-Mail-From is no longer read as the complainer since that address is the sender; and a domain-auth refusal now releases the reservation instead of spending one of the lead's attempts, because the recipient received nothing and the problem is the mailbox's domain, so another mailbox in the pool picks the lead up * feat: fix the same missing-parts gap in the Gmail adapter, and say plainly that Microsoft Graph cannot see reports at all: goog.extractBody took only text/plain and text/html exactly as the IMAP path did, so a feedback report synced from Gmail was as invisible as one synced over IMAP, and the tests that would have caught either called the parser directly rather than going through the adapter where it actually broke; the new tests exercise that seam, and Graph returns one rendered body with no parts so reports there are undetectable without a MIME fetch that is not built, which the docs now state rather than implying full coverage
73 lines
3.1 KiB
Go
73 lines
3.1 KiB
Go
package models
|
|
|
|
type WorkerEventType string
|
|
|
|
const (
|
|
WorkerEventTypeSendEmail WorkerEventType = "SEND_EMAIL"
|
|
WorkerEventTypeAddEmail WorkerEventType = "ADD_EMAIL"
|
|
WorkerEventTypeRemoveEmail WorkerEventType = "REMOVE_EMAIL"
|
|
WorkerEventTypeEmailValidation WorkerEventType = "EMAIL_VALIDATION"
|
|
WorkerEventTypeWarmupAction WorkerEventType = "WARMUP_ACTION"
|
|
)
|
|
|
|
type WorkerEvent struct {
|
|
Type WorkerEventType `json:"type" avro:"type"`
|
|
Body any `json:"body" avro:"body"`
|
|
}
|
|
|
|
type JobEventType string
|
|
|
|
const (
|
|
JobEventTypeNewEmail JobEventType = "NEW_EMAIL"
|
|
JobEventTypeInboundBounce JobEventType = "INBOUND_BOUNCE"
|
|
JobEventTypeInboundComplaint JobEventType = "INBOUND_COMPLAINT"
|
|
JobEventTypeRemoveEmail JobEventType = "REMOVE_EMAIL"
|
|
JobEventTypeFlagsAdd JobEventType = "FLAGS_ADD"
|
|
JobEventTypeFlagsRemove JobEventType = "FLAGS_REMOVE"
|
|
JobEventTypeEmailUpdate JobEventType = "UPDATE_EMAIL"
|
|
JobEventTypeMailboxUpdate JobEventType = "UPDATE_MAILBOX"
|
|
JobEventTypeMailboxDelete JobEventType = "DELETE_MAILBOX"
|
|
|
|
JobEventTypeTokenUpdate JobEventType = "TOKEN_UPDATE"
|
|
JobEventTypeHistoryIDUpdate JobEventType = "HISTORY_ID_UPDATE"
|
|
JobEventTypeGraphDeltaUpdate JobEventType = "GRAPH_DELTA_UPDATE"
|
|
// JobEventTypeSyncState relays a mailbox's backfill progress, fair-use
|
|
// throttle and last-synced time (JobEventSyncState).
|
|
JobEventTypeSyncState JobEventType = "SYNC_STATE"
|
|
|
|
// Task result events from worker
|
|
JobEventTypeEmailSent JobEventType = "EMAIL_SENT"
|
|
JobEventTypeEmailFailed JobEventType = "EMAIL_FAILED"
|
|
|
|
// Error-specific events for worker -> jobsService
|
|
JobEventTypeEmailAuthError JobEventType = "EMAIL_AUTH_ERROR" // Needs re-auth
|
|
JobEventTypeEmailDisabled JobEventType = "EMAIL_DISABLED" // Account disabled
|
|
JobEventTypeEmailRateLimited JobEventType = "EMAIL_RATE_LIMITED" // Rate limit hit
|
|
JobEventTypeEmailServerError JobEventType = "EMAIL_SERVER_ERROR" // Temporary server error
|
|
|
|
// Per-worker health telemetry. Emitted every 30s by every worker;
|
|
// consumer writes it into worker_health_samples for the capacity view.
|
|
JobEventTypeWorkerHealth JobEventType = "WORKER_HEALTH"
|
|
)
|
|
|
|
type JobEvent struct {
|
|
Type JobEventType `json:"type" avro:"type"`
|
|
Body any `json:"body" avro:"body"`
|
|
}
|
|
|
|
// EmailErrorEvent represents an email error event sent from worker to jobsService
|
|
type EmailErrorEvent struct {
|
|
TaskID string `json:"task_id" avro:"task_id"`
|
|
EmailAccountID string `json:"email_account_id" avro:"email_account_id"`
|
|
UserID string `json:"user_id" avro:"user_id"`
|
|
ErrorCode string `json:"error_code" avro:"error_code"`
|
|
ErrorType string `json:"error_type" avro:"error_type"`
|
|
ResolveMethod string `json:"resolve_method" avro:"resolve_method"`
|
|
Message string `json:"message" avro:"message"`
|
|
UserVisible bool `json:"user_visible" avro:"user_visible"`
|
|
UserTitle string `json:"user_title,omitempty" avro:"user_title"`
|
|
UserMessage string `json:"user_message,omitempty" avro:"user_message"`
|
|
ActionRequired string `json:"action_required,omitempty" avro:"action_required"`
|
|
Timestamp int64 `json:"timestamp" avro:"timestamp"`
|
|
}
|