Files
warmbly/internal/models/mailbox_import.go
T

508 lines
21 KiB
Go

package models
import (
"time"
"github.com/google/uuid"
)
// MailboxImportField is what a column of an import file means.
type MailboxImportField string
const (
ImportFieldIgnore MailboxImportField = "ignore"
ImportFieldEmail MailboxImportField = "email"
ImportFieldName MailboxImportField = "name"
ImportFieldFirstName MailboxImportField = "first_name"
ImportFieldLastName MailboxImportField = "last_name"
ImportFieldPassword MailboxImportField = "password"
ImportFieldAppPassword MailboxImportField = "app_password"
ImportFieldSMTPPassword MailboxImportField = "smtp_password"
ImportFieldIMAPPassword MailboxImportField = "imap_password"
ImportFieldUsername MailboxImportField = "username"
ImportFieldSMTPUsername MailboxImportField = "smtp_username"
ImportFieldIMAPUsername MailboxImportField = "imap_username"
ImportFieldSMTPHost MailboxImportField = "smtp_host"
ImportFieldSMTPPort MailboxImportField = "smtp_port"
ImportFieldSMTPSecurity MailboxImportField = "smtp_security"
ImportFieldIMAPHost MailboxImportField = "imap_host"
ImportFieldIMAPPort MailboxImportField = "imap_port"
ImportFieldIMAPSecurity MailboxImportField = "imap_security"
ImportFieldDailyLimit MailboxImportField = "daily_limit"
ImportFieldMinWait MailboxImportField = "min_wait"
ImportFieldWarmup MailboxImportField = "warmup"
ImportFieldWarmupStart MailboxImportField = "warmup_start"
ImportFieldWarmupMax MailboxImportField = "warmup_max"
ImportFieldWarmupIncrease MailboxImportField = "warmup_increase"
ImportFieldWarmupReplyRate MailboxImportField = "warmup_reply_rate"
ImportFieldReplyTo MailboxImportField = "reply_to"
ImportFieldSignature MailboxImportField = "signature"
ImportFieldTags MailboxImportField = "tags"
ImportFieldTimezone MailboxImportField = "timezone"
)
// Row statuses of a mailbox import, mirroring the mailbox_import_rows CHECK.
const (
ImportRowQueued = "queued"
ImportRowRunning = "running"
ImportRowConnected = "connected"
ImportRowUpdated = "updated"
ImportRowSkipped = "skipped"
ImportRowFailed = "failed"
ImportRowNeedsSignin = "needs_signin"
ImportRowCancelled = "cancelled"
)
// Import statuses, mirroring the mailbox_imports CHECK.
const (
ImportRunning = "running"
ImportCompleted = "completed"
ImportCancelled = "cancelled"
)
// Preview-only row states: what would happen, before anything is tried.
const (
ImportPreviewReady = "ready"
ImportPreviewNeedsSignin = "needs_signin"
ImportPreviewInvalid = "invalid"
ImportPreviewExisting = "existing"
ImportPreviewDuplicate = "duplicate"
)
// MailboxImportSettings are applied to every mailbox an import connects or
// updates; a mapped column overrides them per row. Nil means "leave as is".
type MailboxImportSettings struct {
TagIDs []string `json:"tag_ids,omitempty"`
DailyLimit *int `json:"daily_limit,omitempty"`
MinWait *int `json:"min_wait,omitempty"`
Warmup *bool `json:"warmup,omitempty"`
WarmupStart *int `json:"warmup_start,omitempty"`
WarmupMax *int `json:"warmup_max,omitempty"`
WarmupIncrease *int `json:"warmup_increase,omitempty"`
WarmupReplyRate *int `json:"warmup_reply_rate,omitempty"`
ReplyTo *string `json:"reply_to,omitempty"`
Signature *string `json:"signature,omitempty"`
Timezone *string `json:"timezone,omitempty"`
}
// MailboxImportOptions are the non-file inputs of a preview or an import.
type MailboxImportOptions struct {
HasHeader *bool `json:"has_header"`
SharedPassword string `json:"shared_password"`
OnExisting string `json:"on_existing"`
SaveMapping *bool `json:"save_mapping"`
Settings MailboxImportSettings `json:"settings"`
// TrackingDomains picks a tracking host per sending domain, e.g. {"acme.io": "link.acme.io"}.
TrackingDomains map[string]string `json:"tracking_domains,omitempty"`
// Redirects sends a sending domain's root to a website, e.g. {"acme.io": "https://acme.com"}.
Redirects map[string]string `json:"redirects,omitempty"`
}
// MailboxImportMapping maps a column index (as a string, JSON object keys) to a field.
type MailboxImportMapping map[string]MailboxImportField
type MailboxImportVendor struct {
ID string `json:"id"`
Label string `json:"label"`
}
type MailboxImportColumn struct {
Index int `json:"index"`
Header string `json:"header"`
Samples []string `json:"samples"`
Secret bool `json:"secret"`
Field MailboxImportField `json:"field"`
Source string `json:"source"`
Confidence float64 `json:"confidence"`
}
type MailboxImportEndpoint struct {
Host string `json:"host"`
Port int `json:"port"`
Security string `json:"security"`
Username string `json:"username,omitempty"`
}
type MailboxImportPreviewRow struct {
Line int `json:"line"`
Email string `json:"email"`
Name string `json:"name"`
MailHost string `json:"mail_host"`
AuthMethod string `json:"auth_method"`
Status string `json:"status"`
Cause string `json:"cause,omitempty"`
Problem string `json:"problem,omitempty"`
SMTP *MailboxImportEndpoint `json:"smtp,omitempty"`
IMAP *MailboxImportEndpoint `json:"imap,omitempty"`
}
type MailboxImportDomainAuth struct {
State string `json:"state"`
SPF bool `json:"spf"`
DKIM bool `json:"dkim"`
DMARC bool `json:"dmarc"`
}
type MailboxImportDomain struct {
Domain string `json:"domain"`
MailHost string `json:"mail_host"`
Label string `json:"label"`
Source string `json:"source"`
Rows int `json:"rows"`
PasswordAuth string `json:"password_auth"`
AppPasswordURL string `json:"app_password_url,omitempty"`
SMTP *MailboxImportEndpoint `json:"smtp,omitempty"`
IMAP *MailboxImportEndpoint `json:"imap,omitempty"`
Auth *MailboxImportDomainAuth `json:"auth,omitempty"`
// Tracking is the tracking host this domain's mailboxes can use; nil when tracking is off here.
Tracking *TrackingSuggestion `json:"tracking,omitempty"`
// Redirect is the domain's root redirect, when one exists.
Redirect *DomainRedirect `json:"redirect,omitempty"`
// VendorDomain is set when a connected vendor account holds the domain.
VendorDomain *VendorDomainLink `json:"vendor_domain,omitempty"`
}
// MailboxImportIssue is one cause shared by several rows, with its fix.
type MailboxImportIssue struct {
Cause string `json:"cause"`
Title string `json:"title"`
Fix string `json:"fix"`
Count int `json:"count"`
Lines []int `json:"lines"`
}
type MailboxImportSummary struct {
Total int `json:"total"`
Ready int `json:"ready"`
NeedsSignin int `json:"needs_signin"`
Invalid int `json:"invalid"`
Existing int `json:"existing"`
Duplicate int `json:"duplicate"`
}
type MailboxImportPreview struct {
Format string `json:"format"`
HasHeader bool `json:"has_header"`
Vendor *MailboxImportVendor `json:"vendor"`
Columns []MailboxImportColumn `json:"columns"`
Mapping MailboxImportMapping `json:"mapping"`
SavedMapping bool `json:"saved_mapping"`
Summary MailboxImportSummary `json:"summary"`
Rows []MailboxImportPreviewRow `json:"rows"`
Domains []MailboxImportDomain `json:"domains"`
Issues []MailboxImportIssue `json:"issues"`
Allowance *MailboxAllowance `json:"allowance,omitempty"`
MaxRows int `json:"max_rows"`
}
type MailboxImportCounts struct {
Queued int `json:"queued"`
Running int `json:"running"`
Connected int `json:"connected"`
Updated int `json:"updated"`
Skipped int `json:"skipped"`
Failed int `json:"failed"`
NeedsSignin int `json:"needs_signin"`
Cancelled int `json:"cancelled"`
}
type MailboxImportCause struct {
Cause string `json:"cause"`
Title string `json:"title"`
Fix string `json:"fix"`
Count int `json:"count"`
Retryable bool `json:"retryable"`
}
type MailboxImport struct {
ID uuid.UUID `json:"id"`
OrganizationID uuid.UUID `json:"-"`
CreatedBy *uuid.UUID `json:"created_by,omitempty"`
Status string `json:"status"`
Source string `json:"source"`
Filename string `json:"filename"`
Vendor string `json:"vendor"`
OnExisting string `json:"on_existing"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
FinishedAt *time.Time `json:"finished_at,omitempty"`
CredentialsExpireAt *time.Time `json:"credentials_expire_at,omitempty"`
Total int `json:"total"`
Counts MailboxImportCounts `json:"counts"`
Causes []MailboxImportCause `json:"causes"`
// Authorizing counts the rows an inbox vendor is still authorizing, by provider ("google", "microsoft").
Authorizing map[string]int `json:"authorizing"`
}
type MailboxImportRow struct {
Line int `json:"line"`
Email string `json:"email"`
Name string `json:"name"`
MailHost string `json:"mail_host"`
Status string `json:"status"`
Code string `json:"code"`
Cause string `json:"cause"`
Message string `json:"message"`
EmailAccountID *uuid.UUID `json:"email_account_id,omitempty"`
Retryable bool `json:"retryable"`
Fields map[string]string `json:"fields"`
UpdatedAt time.Time `json:"updated_at"`
}
type MailboxImportLegFix struct {
Host *string `json:"host"`
Port *int `json:"port"`
Security *string `json:"security"`
Username *string `json:"username"`
Password *string `json:"password"`
}
// MailboxImportRowFix corrects one failed row; nil fields keep what was uploaded.
type MailboxImportRowFix struct {
Password *string `json:"password"`
AppPassword *string `json:"app_password"`
Username *string `json:"username"`
SMTP *MailboxImportLegFix `json:"smtp"`
IMAP *MailboxImportLegFix `json:"imap"`
}
// MailboxImportRetry requeues failed rows: all of them, one cause, or named lines.
type MailboxImportRetry struct {
Cause string `json:"cause"`
Lines []int `json:"lines"`
Password string `json:"password"`
}
// Admin grant providers, mirroring the mailbox_domain_grants CHECK.
const (
GrantProviderGoogle = "google"
GrantProviderMicrosoft = "microsoft"
)
// DomainGrant is an administrator's grant over a Google Workspace domain or a Microsoft 365 tenant.
type DomainGrant struct {
ID uuid.UUID `json:"id"`
OrganizationID uuid.UUID `json:"-"`
Provider string `json:"provider"`
Tenant string `json:"tenant"`
AdminEmail string `json:"admin_email,omitempty"`
Domains []string `json:"domains"`
Status string `json:"status"`
LastError string `json:"last_error,omitempty"`
VerifiedAt *time.Time `json:"verified_at,omitempty"`
CreatedAt time.Time `json:"created_at"`
Mailboxes int `json:"mailboxes"`
}
// DirectoryUser is one account in a granted domain or tenant.
type DirectoryUser struct {
// ID is the subject tokens are minted for: the address (Google) or the Graph user id (Microsoft).
ID string `json:"id"`
Email string `json:"email"`
Name string `json:"name"`
Enabled bool `json:"enabled"`
Connected bool `json:"connected"`
AccountID *uuid.UUID `json:"email_account_id,omitempty"`
// Upgrade is a connected mailbox that signs in on its own; connecting it moves it onto the grant, keeping its history.
Upgrade bool `json:"upgrade"`
}
// SigninMigration groups a workspace's mailboxes on the retiring per-mailbox Google sign-in by domain.
type SigninMigration struct {
Domain string `json:"domain"`
// Kind is "workspace" (moves to an administrator's grant) or "personal" (a shared address like gmail.com, moves to an app password).
Kind string `json:"kind"`
// GrantID is the workspace's active grant covering the domain, when there is one.
GrantID *uuid.UUID `json:"grant_id,omitempty"`
Mailboxes []MigrationMailbox `json:"mailboxes"`
}
// MigrationMailbox is one mailbox still on per-mailbox Google sign-in.
type MigrationMailbox struct {
ID uuid.UUID `json:"id"`
Email string `json:"email"`
Name string `json:"name"`
Status string `json:"status"`
}
// VendorConnection is an inbox vendor account the workspace imports from. Its credentials never leave the server.
type VendorConnection struct {
ID uuid.UUID `json:"id"`
OrganizationID uuid.UUID `json:"-"`
Vendor string `json:"vendor"`
Label string `json:"label"`
Status string `json:"status"`
LastError string `json:"last_error,omitempty"`
LastUsedAt *time.Time `json:"last_used_at,omitempty"`
CreatedAt time.Time `json:"created_at"`
Mailboxes int `json:"mailboxes"`
// Credentials is the sealed field map; never serialized.
Credentials string `json:"-"`
}
// VendorMailbox is one mailbox in a vendor account, as the picker shows it.
type VendorMailbox struct {
ID string `json:"id"`
Email string `json:"email"`
Name string `json:"name"`
Domain string `json:"domain"`
Provider string `json:"provider"`
Status string `json:"status"`
Workspace string `json:"workspace,omitempty"`
Connected bool `json:"connected"`
AccountID *uuid.UUID `json:"email_account_id,omitempty"`
}
// SendingDomain is one domain the workspace sends from, with what makes it look like a real business.
type SendingDomain struct {
Domain string `json:"domain"`
Mailboxes int `json:"mailboxes"`
MailHosts []string `json:"mail_hosts"`
AuthState string `json:"auth_state"`
AuthSPF bool `json:"auth_spf"`
AuthDKIM bool `json:"auth_dkim"`
AuthDMARC bool `json:"auth_dmarc"`
TrackingDomains []TrackingDomainUse `json:"tracking_domains"`
Redirect *DomainRedirect `json:"redirect,omitempty"`
// Vendors are the inbox vendors this domain's mailboxes were imported from.
Vendors []string `json:"vendors"`
VendorConnectionIDs []uuid.UUID `json:"vendor_connection_ids"`
// VendorDomain is set when a connected vendor account holds this domain.
VendorDomain *VendorDomainLink `json:"vendor_domain,omitempty"`
}
// TrackingDomainUse is one custom tracking host the domain's mailboxes use.
type TrackingDomainUse struct {
Host string `json:"host"`
Verified bool `json:"verified"`
Mailboxes int `json:"mailboxes"`
}
// DomainRedirect sends a sending domain's root to the workspace's main website.
type DomainRedirect struct {
ID uuid.UUID `json:"id"`
Domain string `json:"domain"`
TargetURL string `json:"target_url"`
IncludeWWW bool `json:"include_www"`
Verified bool `json:"verified"`
VerifiedAt *time.Time `json:"verified_at,omitempty"`
LastCheckedAt *time.Time `json:"last_checked_at,omitempty"`
LastError string `json:"last_error,omitempty"`
CreatedAt time.Time `json:"created_at"`
Records []DNSRecord `json:"records"`
// ServedBy is who answers visitors: this instance or Warmbly Cloud.
ServedBy RedirectServer `json:"served_by"`
// ServeHost is the tracking host that answers, which a proxy routes the domain to.
ServeHost string `json:"serve_host,omitempty"`
// Reach is the last HTTP check of the domain; nil until DNS verifies.
Reach *RedirectReach `json:"reach,omitempty"`
// VerifyToken is written into the TXT record; never serialized on its own.
VerifyToken string `json:"-"`
OrganizationID uuid.UUID `json:"-"`
CreatedBy *uuid.UUID `json:"-"`
// RemoteRecords and RemoteHost mirror Warmbly Cloud's answer for a cloud-served row.
RemoteRecords []DNSRecord `json:"-"`
RemoteHost string `json:"-"`
// LinkedInstanceID marks, on Cloud, a row served for a linked instance.
LinkedInstanceID *uuid.UUID `json:"-"`
}
// RedirectServer is where a redirect is served from.
type RedirectServer string
const (
RedirectServedByInstance RedirectServer = "instance"
RedirectServedByCloud RedirectServer = "cloud"
)
// RedirectReachStatus is whether a visitor to the domain gets the redirect.
type RedirectReachStatus string
const (
RedirectReachOK RedirectReachStatus = "ok"
// RedirectReachNotReaching: something answered, but not with the redirect.
RedirectReachNotReaching RedirectReachStatus = "not_reaching"
// RedirectReachHTTPSError: the redirect answers over http, but https has no valid certificate.
RedirectReachHTTPSError RedirectReachStatus = "https_error"
// RedirectReachUnreachable: nothing answered, which may be this server failing to reach its own address.
RedirectReachUnreachable RedirectReachStatus = "unreachable"
)
// RedirectReachHint names the likely cause, so the dashboard can show the matching fix.
type RedirectReachHint string
const (
RedirectHintNone RedirectReachHint = ""
// RedirectHintNotRouted: a proxy in front answered itself, without sending the visit to Warmbly.
RedirectHintNotRouted RedirectReachHint = "not_routed"
// RedirectHintHostHeader: the tracking service got the visit under another hostname.
RedirectHintHostHeader RedirectReachHint = "host_header"
// RedirectHintWrongTarget: the domain redirects, but somewhere else.
RedirectHintWrongTarget RedirectReachHint = "wrong_target"
RedirectHintCertificate RedirectReachHint = "certificate"
RedirectHintNoListener RedirectReachHint = "no_listener"
// RedirectHintSettling: the tracking service answered for the domain but has not picked the redirect up yet.
RedirectHintSettling RedirectReachHint = "settling"
)
// RedirectReach is one HTTP check of a verified redirect's domain.
type RedirectReach struct {
Status RedirectReachStatus `json:"status"`
Hint RedirectReachHint `json:"hint,omitempty"`
// Detail is one sentence describing what the check saw.
Detail string `json:"detail,omitempty"`
// Proxy is the web server that answered in Warmbly's place, when it could be told: traefik, nginx, caddy, ...
Proxy string `json:"proxy,omitempty"`
CheckedAt *time.Time `json:"checked_at,omitempty"`
}
// DomainRedirectRequest sets a domain's redirect. An empty ServedBy keeps the current choice.
type DomainRedirectRequest struct {
TargetURL string `json:"target_url"`
IncludeWWW *bool `json:"include_www"`
ServedBy RedirectServer `json:"served_by,omitempty"`
}
// PoolLinkRedirectOffer is what Warmbly Cloud offers a linked instance for root redirects.
type PoolLinkRedirectOffer struct {
Available bool `json:"available"`
Host string `json:"host,omitempty"`
Limit int `json:"limit"`
Used int `json:"used"`
}
// DNSRecord is one record the customer adds at their DNS provider, and whether it is in place.
type DNSRecord struct {
Purpose string `json:"purpose"`
Type string `json:"type"`
Name string `json:"name"`
Value string `json:"value"`
OK bool `json:"ok"`
// Optional records improve the result but are not required to verify.
Optional bool `json:"optional,omitempty"`
}
// TrackingSuggestion is the tracking host a domain's import can use.
type TrackingSuggestion struct {
Host string `json:"host"`
// Status is "active" (a mailbox here already uses it, verified), "found" (DNS already points it at this instance) or "suggested".
Status string `json:"status"`
CNAMETarget string `json:"cname_target"`
// VendorDomain is set when a connected vendor account holds the domain, so its API may write the CNAME.
VendorDomain *VendorDomainLink `json:"vendor_domain,omitempty"`
}
// VendorDomainLink is a sending domain held by an inbox vendor account, with what its API can do for it.
type VendorDomainLink struct {
Vendor string `json:"vendor"`
ConnectionID uuid.UUID `json:"connection_id"`
Forwarding string `json:"forwarding,omitempty"`
CanForward bool `json:"can_forward"`
// CanUnforward is whether an empty target removes the vendor's forwarding.
CanUnforward bool `json:"can_unforward"`
// ForwardingReviewed means the vendor's staff apply a change later.
ForwardingReviewed bool `json:"forwarding_reviewed"`
CanDNS bool `json:"can_dns"`
DNSTypes []string `json:"dns_types"`
}