Documentation
¶
Index ¶
- Constants
- Variables
- func BuildCustomFieldsJSON(fields map[string]string) string
- func BuildRFCMessage(p EmailParams) string
- func BuildRawMessage(p EmailParams) string
- func CampaignStateTransition(db *sql.DB, name, action, fromStatus, toStatus string) error
- func CheckGWSInstalled() error
- func ConfigPath() string
- func DBPath() string
- func DataDir() string
- func DatabaseDisplayTarget() string
- func DeleteCampaign(db *sql.DB, name string) (int64, error)
- func EnsureDataDir() error
- func ExtractDomain(email string) string
- func ExtractPlaceholders(s string) []string
- func FormatSendDays(s string) string
- func FormatTickResult(r *TickResult) string
- func GWSAuthLogin(configDir string) error
- func GWSConfigDirForAccount(email string) string
- func GenerateRFCMessageID(fromEmail string) string
- func GetLastPollAt(db *sql.DB) time.Time
- func IsUnsubscribeRequest(subject, snippet string) bool
- func LoadEnvFile(path string) error
- func OpenDB(path string) (*sql.DB, error)
- func ParseSendDays(s string) ([]time.Weekday, error)
- func PrepareFollowUp(p *EmailParams, parentMessageID, threadID, originalSubject string)
- func ProcessBounces(db *sql.DB, gws GWSClient, accounts []Account) (int, error)
- func ProcessIMAPBounces(db *sql.DB, imap IMAPMessageLister, accounts []Account) (int, error)
- func ProcessIMAPReplies(db *sql.DB, imap IMAPMessageLister, accounts []Account) (replies int, unsubscribes int, err error)
- func ProcessReplies(db *sql.DB, gws GWSClient, accounts []Account) (replies int, unsubscribes int, err error)
- func RebalancePendingSchedules(db *sql.DB, accountIDs []int64) error
- func RenderTemplate(tmpl string, fields map[string]string) string
- func ResolveAlias(name string) string
- func ResolveCampaignName(db *sql.DB, nameOrID string) (string, error)
- func ResolveLeadScheduleTimezone(fields map[string]string, defaultTZ *time.Location) (*time.Location, error)
- func ResolveSecretRef(ref string) (string, error)
- func SetLastPollAt(db *sql.DB, t time.Time)
- func StripUnresolved(s string) (string, []string)
- func SuggestField(name string, available []string) string
- func UpdateAccount(db *sql.DB, email string, opts UpdateAccountOpts) error
- func UpdateCampaign(db *sql.DB, name string, opts UpdateCampaignOpts) error
- func ValidateLeadFields(leads []LeadRecord, placeholders []string) ([]string, error)
- func ValidateLeadScheduleOverrides(leads []LeadRecord) error
- func ValidateSecretRef(ref string) error
- func WriteDefaultConfig(path string) error
- type Account
- type AccountVerifyResult
- type AddAccountResult
- type AddLeadsResult
- type AddSMTPIMAPAccountOpts
- type AddSMTPIMAPAccountResult
- type BackfillEmailMessagesConfig
- type BackfillEmailMessagesResult
- type BlacklistResult
- type Campaign
- type CampaignLead
- type CampaignListRow
- type CampaignStats
- type CampaignStatusInfo
- type CloneCampaignOpts
- type Config
- type CreateCampaignOpts
- type CreateCampaignResult
- type CreateDraftCampaignOpts
- type DailyLimitWarning
- type Dialect
- type DomainCheck
- type DomainDiagnostic
- type EmailMessage
- type EmailParams
- type EnvSecretResolver
- type Event
- type EventLogRow
- type FailureReason
- type GWSCLI
- func (g *GWSCLI) GetMessage(account, msgID string) (*GWSMessage, error)
- func (g *GWSCLI) GetThreadMessages(account, threadID string) ([]GWSMessage, error)
- func (g *GWSCLI) ListMessages(account, query string, includeSpamTrash ...bool) ([]GWSMessage, error)
- func (g *GWSCLI) SendEmail(account, to, rawMsg, threadID string) (string, string, error)
- func (g *GWSCLI) SetConfigDir(account, configDir string)
- type GWSClient
- type GWSMessage
- type IMAPAccountVerifier
- type IMAPMessageLister
- type IMAPTransport
- type Lead
- type LeadForSchedule
- type LeadListRow
- type LeadRecord
- type LeadStatsRow
- type ListAccountsRow
- type ListEmailThreadMessagesOpts
- type PauseAccountResult
- type PauseLeadResult
- type PreviewRow
- type RemoveLeadResult
- type RenderedEmail
- type ResumeAccountResult
- type ResumeLeadResult
- type RetryCampaignResult
- type SMTPAccountVerifier
- type SMTPEmailSender
- type SMTPIMAPAccountSecrets
- type SMTPTransport
- type ScheduleConfig
- type ScheduledSend
- type ScheduledSendRow
- type SecretResolver
- type SecretResolverFunc
- type SendInboxReplyConfig
- type SendInboxReplyResult
- type SendNowResult
- type SendSMTPTestEmailOpts
- type SendSMTPTestEmailResult
- type Sequence
- type SequenceDefaults
- type SequenceStep
- type SequenceVariant
- type StepStats
- type Store
- func (s *Store) AcquireTickLock(ctx context.Context) (TickLock, error)
- func (s *Store) Begin() (*Tx, error)
- func (s *Store) Close() error
- func (s *Store) DisplayTarget() string
- func (s *Store) Exec(query string, args ...any) (sql.Result, error)
- func (s *Store) Query(query string, args ...any) (*sql.Rows, error)
- func (s *Store) QueryRow(query string, args ...any) *sql.Row
- func (s *Store) Rebind(query string) string
- type TickConfig
- type TickLock
- type TickResult
- type Tx
- type UpdateAccountOpts
- type UpdateCampaignOpts
- type UpdateSMTPIMAPAccountOpts
- type VariantStats
Constants ¶
const ( AccountProviderGWS = "gws" AccountProviderSMTPIMAP = "smtp_imap" )
const ( EmailMessageDirectionOutbound = "outbound" EmailMessageDirectionInbound = "inbound" EmailMessageTypeSent = "sent" EmailMessageTypeReply = "reply" EmailMessageTypeUnsubscribe = "unsubscribe" EmailMessageTypeManualReply = "manual_reply" )
const ScheduleTimezoneField = "schedule_timezone"
Variables ¶
var BuiltinFields = []string{"email", "first_name", "last_name", "company", "domain"}
BuiltinFields are fields always available for template rendering from the leads table.
var DefaultConfig = Config{
DefaultTimezone: "America/New_York",
DefaultDailyLimit: 50,
MinGapSeconds: 90,
MaxGapSeconds: 140,
SendWindowStart: "09:00",
SendWindowEnd: "17:00",
SendDays: "1,2,3,4,5",
UnsubscribeSubject: "Unsubscribe",
}
Functions ¶
func BuildCustomFieldsJSON ¶
BuildCustomFieldsJSON extracts non-builtin fields from a LeadRecord and returns them as JSON.
func BuildRFCMessage ¶
func BuildRFCMessage(p EmailParams) string
BuildRFCMessage constructs an RFC 2822 message.
func BuildRawMessage ¶
func BuildRawMessage(p EmailParams) string
BuildRawMessage constructs an RFC 2822 message and returns it as a base64url-encoded string.
func CampaignStateTransition ¶
CampaignStateTransition changes a campaign's status with validation.
func CheckGWSInstalled ¶
func CheckGWSInstalled() error
CheckGWSInstalled verifies gws binary is available on PATH.
func ConfigPath ¶
func ConfigPath() string
func DataDir ¶
func DataDir() string
DataDir returns the cold-cli data directory path. Respects COLD_CLI_DATA_DIR env var for testing.
func DatabaseDisplayTarget ¶
func DatabaseDisplayTarget() string
DatabaseDisplayTarget returns a user-facing database target description.
func DeleteCampaign ¶
DeleteCampaign deletes a campaign and all associated data.
func EnsureDataDir ¶
func EnsureDataDir() error
EnsureDataDir creates the data directory if it doesn't exist.
func ExtractDomain ¶
ExtractDomain returns the domain part of an email address.
func ExtractPlaceholders ¶
ExtractPlaceholders returns all unique {{placeholder}} names from a string.
func FormatSendDays ¶
FormatSendDays converts "1,2,3,4,5" to "Mon-Fri" or similar human-readable format.
func FormatTickResult ¶
func FormatTickResult(r *TickResult) string
FormatTickResult returns a human-readable summary of a tick result.
func GWSAuthLogin ¶
GWSAuthLogin runs 'gws auth login' for a specific account config dir.
func GWSConfigDirForAccount ¶
GWSConfigDirForAccount returns the config dir path for an account. Creates the directory and copies client_secret.json from the default gws config.
func GenerateRFCMessageID ¶
GenerateRFCMessageID creates a Message-ID scoped to the sender domain.
func GetLastPollAt ¶
GetLastPollAt returns the last poll timestamp, or a default (24h ago).
func IsUnsubscribeRequest ¶
IsUnsubscribeRequest checks if a message appears to be an unsubscribe request based on its subject and snippet text.
func LoadEnvFile ¶
LoadEnvFile loads KEY=VALUE pairs from an explicit env file into the process environment. It intentionally does not auto-discover .env files.
func ParseSendDays ¶
ParseSendDays converts a comma-separated day string to weekday slice. Accepts numbers (0=Sun,1=Mon,...,6=Sat) or names (sun,mon,...,sat).
func PrepareFollowUp ¶
func PrepareFollowUp(p *EmailParams, parentMessageID, threadID, originalSubject string)
PrepareFollowUp adds threading headers to an EmailParams for step 2+.
func ProcessBounces ¶
ProcessBounces checks inbox messages for bounce NDRs. Uses two strategies:
- Thread matching — if the NDR shares a thread_id with a sent email, we know the lead
- Snippet/header parsing — fallback: extract bounced email from NDR text
Returns the number of new bounces detected.
func ProcessIMAPBounces ¶
ProcessIMAPBounces checks IMAP messages for bounce NDRs.
func ProcessIMAPReplies ¶
func ProcessIMAPReplies(db *sql.DB, imap IMAPMessageLister, accounts []Account) (replies int, unsubscribes int, err error)
ProcessIMAPReplies checks IMAP inbox messages for replies to sent SMTP/IMAP emails.
func ProcessReplies ¶
func ProcessReplies(db *sql.DB, gws GWSClient, accounts []Account) (replies int, unsubscribes int, err error)
ProcessReplies checks inbox messages for replies to our sent emails. Returns the number of new replies and unsubscribes detected.
func RebalancePendingSchedules ¶
RebalancePendingSchedules rewrites pending send_at timestamps for the affected accounts so daily limits are respected across all active and draft campaigns.
func RenderTemplate ¶
RenderTemplate replaces all {{placeholder}} occurrences with values from the fields map. It also resolves known aliases (e.g., {{name}} → first_name value).
func ResolveAlias ¶
ResolveAlias returns the canonical field name for a known alias, or the name unchanged.
func ResolveCampaignName ¶
ResolveCampaignName accepts a campaign name or numeric ID and returns the campaign name. This lets users reference campaigns by either their name or the ID shown in "campaign list".
func ResolveLeadScheduleTimezone ¶
func ResolveLeadScheduleTimezone(fields map[string]string, defaultTZ *time.Location) (*time.Location, error)
ResolveLeadScheduleTimezone returns the lead's effective scheduling timezone.
func ResolveSecretRef ¶
ResolveSecretRef resolves a secret reference without exposing the value in errors.
func SetLastPollAt ¶
SetLastPollAt updates the last poll timestamp.
func StripUnresolved ¶
StripUnresolved removes any remaining {{...}} placeholders from a string, collapses double spaces left behind, and returns the names of stripped variables.
func SuggestField ¶
SuggestField returns the closest matching field name if within Levenshtein distance 3, or "".
func UpdateAccount ¶
func UpdateAccount(db *sql.DB, email string, opts UpdateAccountOpts) error
UpdateAccount modifies account settings.
func UpdateCampaign ¶
func UpdateCampaign(db *sql.DB, name string, opts UpdateCampaignOpts) error
UpdateCampaign updates campaign settings with validation.
func ValidateLeadFields ¶
func ValidateLeadFields(leads []LeadRecord, placeholders []string) ([]string, error)
ValidateLeadFields checks that every placeholder in the sequence maps to a known field and that every lead has non-empty values for all required placeholders. Returns alias-mapping warnings (if any) and an error if validation fails.
func ValidateLeadScheduleOverrides ¶
func ValidateLeadScheduleOverrides(leads []LeadRecord) error
ValidateLeadScheduleOverrides checks optional per-lead scheduling override fields.
func ValidateSecretRef ¶
ValidateSecretRef verifies that a secret reference uses a supported scheme.
The env: scheme is resolved by the local CLI. The secret: scheme is an opaque hosted-product reference resolved by callers that provide their own SecretResolver.
func WriteDefaultConfig ¶
Types ¶
type Account ¶
type Account struct {
ID int64 `json:"id"`
Email string `json:"email"`
DailyLimit int `json:"daily_limit"`
LastSendAt *time.Time `json:"last_send_at,omitempty"`
Status string `json:"status"`
Provider string `json:"provider"`
GWSConfigDir string `json:"gws_config_dir,omitempty"`
SMTPHost string `json:"smtp_host,omitempty"`
SMTPPort int `json:"smtp_port,omitempty"`
SMTPUsername string `json:"smtp_username,omitempty"`
SMTPPasswordRef string `json:"smtp_password_ref,omitempty"`
SMTPTLSMode string `json:"smtp_tls_mode,omitempty"`
IMAPHost string `json:"imap_host,omitempty"`
IMAPPort int `json:"imap_port,omitempty"`
IMAPUsername string `json:"imap_username,omitempty"`
IMAPPasswordRef string `json:"imap_password_ref,omitempty"`
IMAPTLSMode string `json:"imap_tls_mode,omitempty"`
}
type AccountVerifyResult ¶
type AccountVerifyResult struct {
Email string `json:"email"`
Provider string `json:"provider"`
SMTPOK bool `json:"smtp_ok,omitempty"`
IMAPOK bool `json:"imap_ok,omitempty"`
SMTPError string `json:"smtp_error,omitempty"`
IMAPError string `json:"imap_error,omitempty"`
}
func VerifySMTPIMAPAccount ¶
func VerifySMTPIMAPAccount(account Account, smtpVerifier SMTPAccountVerifier, imapVerifier IMAPAccountVerifier) (*AccountVerifyResult, error)
type AddAccountResult ¶
type AddAccountResult struct {
ID int64 `json:"id"`
Email string `json:"email"`
DailyLimit int `json:"daily_limit"`
Status string `json:"status"`
Provider string `json:"provider"`
GWSConfigDir string `json:"gws_config_dir"`
}
AddAccountResult is returned by AddAccount.
func AddAccount ¶
func AddAccount(db *sql.DB, email string, dailyLimit int, configDir string) (*AddAccountResult, error)
AddAccount inserts a new sending account into the database. If the account was previously removed, it is reactivated with the new settings.
type AddLeadsResult ¶
type AddLeadsResult struct {
Campaign string `json:"campaign"`
LeadsAdded int `json:"leads_added"`
LeadsSkipped int `json:"leads_skipped"`
ScheduledSends int `json:"scheduled_sends"`
Warnings []string `json:"warnings,omitempty"`
}
AddLeadsResult is returned by AddLeadsToCampaign.
func AddLeadsToCampaign ¶
func AddLeadsToCampaign(db *sql.DB, campaignName, leadsFile, leadsInline string) (*AddLeadsResult, error)
AddLeadsToCampaign adds new leads to an existing campaign and schedules their sends. Pass leadsFile for file path, or leadsInline for inline CSV content (one should be non-empty).
type AddSMTPIMAPAccountOpts ¶
type AddSMTPIMAPAccountOpts struct {
Email string
DailyLimit int
SMTPHost string
SMTPPort int
SMTPUsername string
SMTPPasswordRef string
SMTPTLSMode string
IMAPHost string
IMAPPort int
IMAPUsername string
IMAPPasswordRef string
IMAPTLSMode string
}
AddSMTPIMAPAccountOpts holds settings for a generic SMTP/IMAP account.
type AddSMTPIMAPAccountResult ¶
type AddSMTPIMAPAccountResult struct {
ID int64 `json:"id"`
Email string `json:"email"`
DailyLimit int `json:"daily_limit"`
Status string `json:"status"`
Provider string `json:"provider"`
SMTPHost string `json:"smtp_host"`
SMTPPort int `json:"smtp_port"`
SMTPUsername string `json:"smtp_username"`
SMTPTLSMode string `json:"smtp_tls_mode"`
IMAPHost string `json:"imap_host"`
IMAPPort int `json:"imap_port"`
IMAPUsername string `json:"imap_username"`
IMAPTLSMode string `json:"imap_tls_mode"`
}
AddSMTPIMAPAccountResult is returned by AddSMTPIMAPAccount.
func AddSMTPIMAPAccount ¶
func AddSMTPIMAPAccount(db *sql.DB, opts AddSMTPIMAPAccountOpts) (*AddSMTPIMAPAccountResult, error)
AddSMTPIMAPAccount inserts a generic SMTP/IMAP sending account. Password fields are stored as references, not raw secret values.
func UpdateSMTPIMAPAccount ¶
func UpdateSMTPIMAPAccount(db *sql.DB, email string, opts UpdateSMTPIMAPAccountOpts) (*AddSMTPIMAPAccountResult, error)
UpdateSMTPIMAPAccount modifies provider settings on a generic SMTP/IMAP account.
type BackfillEmailMessagesResult ¶
type BackfillEmailMessagesResult struct {
Scanned int `json:"scanned"`
Backfilled int `json:"backfilled"`
Sent int `json:"sent"`
Inbound int `json:"inbound"`
Skipped int `json:"skipped"`
Unsupported int `json:"unsupported"`
Failed int `json:"failed"`
DryRun bool `json:"dry_run"`
}
func BackfillEmailMessages ¶
func BackfillEmailMessages(cfg BackfillEmailMessagesConfig) (*BackfillEmailMessagesResult, error)
type BlacklistResult ¶
type BlacklistResult struct {
Target string `json:"target"`
IsDomain bool `json:"is_domain"`
BlacklistedLeads int64 `json:"blacklisted_leads"`
CancelledSends int64 `json:"cancelled_sends"`
}
BlacklistResult is returned by BlacklistLead.
func BlacklistLead ¶
func BlacklistLead(db *sql.DB, target string) (*BlacklistResult, error)
BlacklistLead blacklists a lead by email or all leads on a domain.
type Campaign ¶
type Campaign struct {
ID int64 `json:"id"`
Name string `json:"name"`
Status string `json:"status"`
SequenceFile string `json:"sequence_file"`
StopOnReply bool `json:"stop_on_reply"`
StopOnDomainReply bool `json:"stop_on_domain_reply"`
SendWindowStart string `json:"send_window_start"`
SendWindowEnd string `json:"send_window_end"`
SendDays string `json:"send_days"`
Timezone string `json:"timezone"`
MinGapSeconds int `json:"min_gap_seconds"`
MaxGapSeconds int `json:"max_gap_seconds"`
CreatedAt time.Time `json:"created_at"`
}
type CampaignLead ¶
type CampaignListRow ¶
type CampaignListRow struct {
ID int64 `json:"id"`
Name string `json:"name"`
Status string `json:"status"`
Leads int `json:"leads"`
Sends int `json:"sends"`
SendWindow string `json:"send_window"`
SendDays string `json:"send_days"`
}
CampaignListRow is a row from ListCampaigns.
func ListCampaigns ¶
func ListCampaigns(db *sql.DB) ([]CampaignListRow, error)
ListCampaigns returns all campaigns with lead and send counts.
type CampaignStats ¶
type CampaignStats struct {
Name string `json:"name"`
Status string `json:"status"`
Sent int `json:"sent"`
Replies int `json:"replies"`
Unsubscribes int `json:"unsubscribes"`
Bounces int `json:"bounces"`
}
CampaignStats is a row from GetAllCampaignStats.
func GetAllCampaignStats ¶
func GetAllCampaignStats(db *sql.DB) ([]CampaignStats, error)
GetAllCampaignStats returns sent/replied/bounced counts per campaign.
type CampaignStatusInfo ¶
type CampaignStatusInfo struct {
Name string `json:"name"`
Status string `json:"status"`
Sequence string `json:"sequence"`
Timezone string `json:"timezone"`
SendWindow string `json:"send_window"`
SendDays string `json:"send_days"`
Leads int `json:"leads"`
Accounts int `json:"accounts"`
TotalSends int `json:"total_sends"`
SendCounts map[string]int `json:"send_counts"`
CreatedAt string `json:"created_at"`
ReplyRate *float64 `json:"reply_rate,omitempty"`
NextSendAt *string `json:"next_send_at,omitempty"`
LastSendAt *string `json:"last_send_at,omitempty"`
FailureReasons []FailureReason `json:"failure_reasons,omitempty"`
}
CampaignStatusInfo is returned by GetCampaignStatus.
func GetCampaignStatus ¶
func GetCampaignStatus(db *sql.DB, name string) (*CampaignStatusInfo, error)
GetCampaignStatus returns campaign details and send counts.
type CloneCampaignOpts ¶
type CloneCampaignOpts struct {
SourceName string
NewName string
LeadsFile string
LeadsInline string // inline CSV content (alternative to LeadsFile)
Accounts []string // optional: override accounts; empty = reuse source accounts
}
CloneCampaignOpts holds options for CloneCampaign.
type Config ¶
type Config struct {
DefaultTimezone string `yaml:"default_timezone"`
DefaultDailyLimit int `yaml:"default_daily_limit"`
MinGapSeconds int `yaml:"min_gap_seconds"`
MaxGapSeconds int `yaml:"max_gap_seconds"`
SendWindowStart string `yaml:"send_window_start"`
SendWindowEnd string `yaml:"send_window_end"`
SendDays string `yaml:"send_days"`
UnsubscribeHeader bool `yaml:"unsubscribe_header"`
UnsubscribeSubject string `yaml:"unsubscribe_subject"`
}
func LoadConfig ¶
type CreateCampaignOpts ¶
type CreateCampaignOpts struct {
Name string
SequenceFile string
SequenceInline string // inline YAML content (alternative to SequenceFile)
LeadsFile string
LeadsInline string // inline CSV content (alternative to LeadsFile)
AccountEmails []string
StartDate string // optional "YYYY-MM-DD"; empty = now
SendWindowStart string // optional HH:MM override; empty = config default
SendWindowEnd string // optional HH:MM override; empty = config default
SendDays string // optional send days override for this campaign
Timezone string // optional IANA timezone override; empty = config default
}
CreateCampaignOpts holds options for CreateCampaign.
type CreateCampaignResult ¶
type CreateCampaignResult struct {
ID int64 `json:"id"`
Name string `json:"name"`
Status string `json:"status"`
Leads int `json:"leads"`
ScheduledSends int `json:"scheduled_sends"`
Accounts int `json:"accounts"`
Warnings []string `json:"warnings,omitempty"`
}
CreateCampaignResult is returned by CreateCampaign.
func CloneCampaign ¶
func CloneCampaign(db *sql.DB, opts CloneCampaignOpts) (*CreateCampaignResult, error)
CloneCampaign creates a new campaign by copying settings from an existing one with new leads.
func CreateCampaign ¶
func CreateCampaign(db *sql.DB, opts CreateCampaignOpts) (*CreateCampaignResult, error)
CreateCampaign parses sequence+CSV, validates, computes schedule, and inserts everything.
func CreateDraftCampaign ¶
func CreateDraftCampaign(db *sql.DB, opts CreateDraftCampaignOpts) (*CreateCampaignResult, error)
CreateDraftCampaign inserts a draft campaign shell without sequence steps or leads.
type CreateDraftCampaignOpts ¶
type CreateDraftCampaignOpts struct {
Name string
AccountEmails []string
SendWindowStart string
SendWindowEnd string
SendDays string
Timezone string
}
CreateDraftCampaignOpts holds options for CreateDraftCampaign.
type DailyLimitWarning ¶
type DailyLimitWarning struct {
Date string `json:"date"`
Account string `json:"account"`
Scheduled int `json:"scheduled"`
Limit int `json:"limit"`
Overflow int `json:"overflow"`
}
DailyLimitWarning describes a day where scheduled sends exceed an account's daily limit.
func GetDailyLimitWarnings ¶
func GetDailyLimitWarnings(db *sql.DB) ([]DailyLimitWarning, error)
GetDailyLimitWarnings checks all pending sends across all active campaigns for each account and returns warnings for days that exceed the account's daily limit.
type Dialect ¶
type Dialect string
func CurrentDialect ¶
func CurrentDialect() Dialect
CurrentDialect returns the active database dialect from environment.
type DomainCheck ¶
type DomainCheck struct {
Name string `json:"name"`
Passed bool `json:"passed"`
Detail string `json:"detail"`
Fix string `json:"fix,omitempty"`
}
DomainCheck is the result of checking one DNS aspect.
type DomainDiagnostic ¶
type DomainDiagnostic struct {
Domain string `json:"domain"`
Checks []DomainCheck `json:"checks"`
Score int `json:"score"`
MaxScore int `json:"max_score"`
}
DomainDiagnostic is the full result of CheckDomain.
func CheckDomain ¶
func CheckDomain(domain string) (*DomainDiagnostic, error)
CheckDomain runs DNS diagnostics for email deliverability.
type EmailMessage ¶
type EmailMessage struct {
ID int64 `json:"id"`
CampaignID int64 `json:"campaign_id"`
LeadID int64 `json:"lead_id"`
AccountID int64 `json:"account_id"`
Direction string `json:"direction"`
Type string `json:"type"`
StepNumber int `json:"step_number"`
ScheduledSendID *int64 `json:"scheduled_send_id,omitempty"`
EventID *int64 `json:"event_id,omitempty"`
MessageID string `json:"message_id"`
ThreadID string `json:"thread_id"`
InReplyTo string `json:"in_reply_to,omitempty"`
FromEmail string `json:"from_email"`
ToEmails string `json:"to_emails"`
Subject string `json:"subject"`
TextBody string `json:"text_body"`
DisplayBody string `json:"display_body"`
DisplayHTML string `json:"display_html"`
HTMLBody string `json:"html_body"`
Snippet string `json:"snippet"`
RawHeaders string `json:"raw_headers,omitempty"`
OccurredAt time.Time `json:"occurred_at"`
CreatedAt time.Time `json:"created_at"`
}
func ListEmailThreadMessages ¶
func ListEmailThreadMessages(db *sql.DB, opts ListEmailThreadMessagesOpts) ([]EmailMessage, error)
type EmailParams ¶
type EmailParams struct {
FromName string
FromEmail string
ToEmail string
Subject string
Body string
// For follow-ups (step 2+)
InReplyTo string // Message-ID of the previous step
References string // same as InReplyTo for simple chains
ThreadID string // Gmail thread ID for threading
MessageID string // RFC Message-ID to set before sending
// Unsubscribe
UnsubscribeEmail string // mailto address for List-Unsubscribe header
UnsubscribeSubject string // subject for the mailto unsubscribe
// Optional Date header. If zero, no Date header is added.
Date time.Time
// Stripped unresolved template variables (for logging)
StrippedVars []string
}
EmailParams holds everything needed to construct and send an email.
func BuildEmailForSend ¶
func BuildEmailForSend( seq *Sequence, stepNumber int, variantIndex int, lead map[string]string, fromEmail string, ) EmailParams
BuildEmailForSend constructs the full email for a scheduled send, applying template rendering and selecting the correct variant.
type EnvSecretResolver ¶
type EnvSecretResolver struct{}
EnvSecretResolver resolves env:NAME references from the process environment.
func (EnvSecretResolver) ResolveSecret ¶
func (EnvSecretResolver) ResolveSecret(ref string) (string, error)
type Event ¶
type Event struct {
ID int64 `json:"id"`
CampaignID int64 `json:"campaign_id"`
LeadID int64 `json:"lead_id"`
AccountID int64 `json:"account_id"`
Type string `json:"type"`
StepNumber int `json:"step_number"`
MessageID string `json:"message_id"`
ThreadID string `json:"thread_id"`
Timestamp time.Time `json:"timestamp"`
Metadata string `json:"metadata,omitempty"`
}
type EventLogRow ¶
type EventLogRow struct {
Timestamp string `json:"timestamp"`
Type string `json:"type"`
Campaign string `json:"campaign"`
LeadEmail string `json:"lead_email"`
AccountEmail string `json:"account_email"`
StepNumber int `json:"step_number"`
MessageID string `json:"message_id,omitempty"`
}
EventLogRow is a row from GetEventLog.
func GetEventLog ¶
GetEventLog returns the most recent events, optionally filtered by campaign.
type FailureReason ¶
FailureReason is an error message and its count from failed sends.
type GWSCLI ¶
type GWSCLI struct {
Timeout time.Duration
ConfigDirs map[string]string // account email → gws config dir
}
GWSCLI is the real implementation that calls gws as a subprocess.
func (*GWSCLI) GetMessage ¶
func (g *GWSCLI) GetMessage(account, msgID string) (*GWSMessage, error)
GetMessage retrieves a single message with full payload (headers).
func (*GWSCLI) GetThreadMessages ¶
func (g *GWSCLI) GetThreadMessages(account, threadID string) ([]GWSMessage, error)
func (*GWSCLI) ListMessages ¶
func (g *GWSCLI) ListMessages(account, query string, includeSpamTrash ...bool) ([]GWSMessage, error)
ListMessages lists messages matching a Gmail search query.
func (*GWSCLI) SendEmail ¶
SendEmail sends an email via gws and returns the Gmail message ID and thread ID.
func (*GWSCLI) SetConfigDir ¶
SetConfigDir registers a gws config directory for a specific account.
type GWSClient ¶
type GWSClient interface {
SendEmail(account, to, rawMsg, threadID string) (msgID, sentThreadID string, err error)
ListMessages(account, query string, includeSpamTrash ...bool) ([]GWSMessage, error)
GetMessage(account, msgID string) (*GWSMessage, error)
GetThreadMessages(account, threadID string) ([]GWSMessage, error)
}
GWSClient is the interface for Gmail operations via gws CLI.
type GWSMessage ¶
type GWSMessage struct {
ID string `json:"id"`
ThreadID string `json:"threadId"`
Snippet string `json:"snippet"`
TextBody string
HTMLBody string
LabelIDs []string `json:"labelIds"`
Headers map[string]string // parsed from payload.headers
From string
To string
Subject string
InReplyTo string
Date time.Time
}
GWSMessage represents a parsed Gmail message from gws output.
func ParseIMAPRawMessage ¶
type IMAPAccountVerifier ¶
IMAPAccountVerifier verifies IMAP connectivity and authentication.
type IMAPMessageLister ¶
type IMAPMessageLister interface {
ListMessages(account Account, since time.Time, includeSpamTrash bool) ([]GWSMessage, error)
}
IMAPMessageLister lists mailbox messages for reply and bounce polling.
type IMAPTransport ¶
type IMAPTransport struct {
Resolver SecretResolver
Timeout time.Duration
Mailboxes []string
SpamTrashBoxes []string
MaxBodyBytes int64
// contains filtered or unexported fields
}
IMAPTransport is the production IMAP polling transport.
func NewIMAPTransport ¶
func NewIMAPTransport(resolver SecretResolver) *IMAPTransport
func (*IMAPTransport) ListMessages ¶
func (t *IMAPTransport) ListMessages(account Account, since time.Time, includeSpamTrash bool) ([]GWSMessage, error)
func (*IMAPTransport) VerifyAccount ¶
func (t *IMAPTransport) VerifyAccount(account Account) error
type Lead ¶
type Lead struct {
ID int64 `json:"id"`
Email string `json:"email"`
FirstName string `json:"first_name"`
LastName string `json:"last_name"`
Company string `json:"company"`
Domain string `json:"domain"`
CustomFields string `json:"custom_fields,omitempty"`
GlobalStatus string `json:"global_status"`
CreatedAt time.Time `json:"created_at"`
}
type LeadForSchedule ¶
LeadForSchedule is the minimal lead info needed for scheduling.
type LeadListRow ¶
type LeadListRow struct {
ID int64 `json:"id"`
Email string `json:"email"`
FirstName string `json:"first_name"`
Company string `json:"company"`
Domain string `json:"domain"`
GlobalStatus string `json:"global_status"`
Campaigns int `json:"campaigns"`
}
LeadListRow is a row from ListLeads.
type LeadRecord ¶
LeadRecord represents a parsed CSV row with all fields available by header name.
func ParseLeadsCSV ¶
func ParseLeadsCSV(path string) ([]LeadRecord, []string, error)
ParseLeadsCSV reads a CSV file and returns parsed lead records. Headers are normalized to lowercase with spaces replaced by underscores. Strips UTF-8 BOM if present.
func ParseLeadsCSVFromReader ¶
func ParseLeadsCSVFromReader(r io.Reader) ([]LeadRecord, []string, error)
ParseLeadsCSVFromReader parses leads CSV from a reader. Returns the lead records and the normalized header names.
type LeadStatsRow ¶
type LeadStatsRow struct {
Email string `json:"email"`
Status string `json:"status"`
StepsSent int `json:"steps_sent"`
ReplyAt *string `json:"reply_at,omitempty"`
}
LeadStatsRow is a row from GetCampaignLeadStats.
func GetCampaignLeadStats ¶
func GetCampaignLeadStats(db *sql.DB, campaignID int64) ([]LeadStatsRow, error)
GetCampaignLeadStats returns per-lead stats for a campaign.
type ListAccountsRow ¶
type ListAccountsRow struct {
ID int64 `json:"id"`
Email string `json:"email"`
DailyLimit int `json:"daily_limit"`
Status string `json:"status"`
Provider string `json:"provider"`
}
ListAccountsRow is a row from ListAccounts.
func ListAccounts ¶
func ListAccounts(db *sql.DB) ([]ListAccountsRow, error)
ListAccounts returns all accounts ordered by ID.
type PauseAccountResult ¶
type PauseAccountResult struct {
Email string `json:"email"`
CancelledSends int64 `json:"cancelled_sends"`
}
PauseAccountResult is returned by PauseAccount.
func PauseAccount ¶
func PauseAccount(db *sql.DB, email string) (*PauseAccountResult, error)
PauseAccount deactivates an account and cancels its pending sends.
func RemoveAccount ¶
func RemoveAccount(db *sql.DB, email string) (*PauseAccountResult, error)
RemoveAccount deactivates an account permanently and cancels its pending sends. The account row is kept (status='removed') because historical sends/events reference it.
type PauseLeadResult ¶
type PauseLeadResult struct {
Email string `json:"email"`
PausedCampaigns int64 `json:"paused_campaigns"`
CancelledSends int64 `json:"cancelled_sends"`
}
PauseLeadResult is returned by PauseLead.
type PreviewRow ¶
type PreviewRow struct {
StepNumber int `json:"step_number"`
VariantIndex int `json:"variant_index"`
SendAt string `json:"send_at"`
Status string `json:"status"`
LeadEmail string `json:"lead_email"`
AccountEmail string `json:"account_email"`
ErrorMessage string `json:"error_message,omitempty"`
}
PreviewRow is a row from GetCampaignPreview.
func GetCampaignPreview ¶
func GetCampaignPreview(db *sql.DB, name string) (campaignID int64, status string, preview []PreviewRow, err error)
GetCampaignPreview returns the full scheduled send list for a campaign.
type RemoveLeadResult ¶
type RemoveLeadResult struct {
Email string `json:"email"`
Campaign string `json:"campaign"`
CancelledSends int64 `json:"cancelled_sends"`
}
RemoveLeadResult is returned by RemoveLeadFromCampaign.
func RemoveLeadFromCampaign ¶
func RemoveLeadFromCampaign(db *sql.DB, campaignName, email string) (*RemoveLeadResult, error)
RemoveLeadFromCampaign removes a single lead from a specific campaign.
type RenderedEmail ¶
type RenderedEmail struct {
StepNumber int `json:"step_number"`
VariantIndex int `json:"variant_index"`
LeadEmail string `json:"lead_email"`
AccountEmail string `json:"account_email"`
Subject string `json:"subject"`
Body string `json:"body"`
StrippedVars []string `json:"stripped_vars,omitempty"`
}
RenderedEmail is a preview of an actual email with templates filled in.
func GetCampaignRenderedPreview ¶
GetCampaignRenderedPreview returns rendered emails for a specific lead (or the first lead) in a campaign.
type ResumeAccountResult ¶
type ResumeAccountResult struct {
Email string `json:"email"`
RestoredSends int64 `json:"restored_sends"`
}
ResumeAccountResult is returned by ResumeAccount.
func ResumeAccount ¶
func ResumeAccount(db *sql.DB, email string) (*ResumeAccountResult, error)
ResumeAccount reactivates a paused account and restores its eligible sends.
type ResumeLeadResult ¶
type ResumeLeadResult struct {
Email string `json:"email"`
ResumedCampaigns int64 `json:"resumed_campaigns"`
RestoredSends int64 `json:"restored_sends"`
}
ResumeLeadResult is returned by ResumeLead.
func ResumeLead ¶
func ResumeLead(db *sql.DB, email string) (*ResumeLeadResult, error)
ResumeLead resumes a paused lead: reactivates campaign_leads and restores cancelled sends.
type RetryCampaignResult ¶
RetryCampaignResult is returned by RetryCampaign.
func RetryCampaign ¶
RetryCampaign resets failed sends back to pending with send_at = now. If step is non-nil, only retry failed sends for that specific step.
type SMTPAccountVerifier ¶
SMTPAccountVerifier verifies SMTP connectivity and authentication.
type SMTPEmailSender ¶
type SMTPEmailSender interface {
SendEmail(account Account, params EmailParams) (messageID string, threadID string, err error)
}
SMTPEmailSender sends rendered emails through generic SMTP accounts.
type SMTPIMAPAccountSecrets ¶
func ResolveSMTPIMAPAccountSecrets ¶
func ResolveSMTPIMAPAccountSecrets(account Account, resolver SecretResolver) (*SMTPIMAPAccountSecrets, error)
ResolveSMTPIMAPAccountSecrets resolves the password references for an SMTP/IMAP account.
type SMTPTransport ¶
type SMTPTransport struct {
Resolver SecretResolver
Timeout time.Duration
Now func() time.Time
// contains filtered or unexported fields
}
SMTPTransport is the production SMTP sender.
func NewSMTPTransport ¶
func NewSMTPTransport(resolver SecretResolver) *SMTPTransport
func (*SMTPTransport) SendEmail ¶
func (s *SMTPTransport) SendEmail(account Account, params EmailParams) (string, string, error)
func (*SMTPTransport) VerifyAccount ¶
func (s *SMTPTransport) VerifyAccount(account Account) error
type ScheduleConfig ¶
type ScheduleConfig struct {
CampaignID int64
AccountIDs []int64
Leads []LeadForSchedule
Sequence *Sequence
StartDate string
SendWindowStart string // "09:00"
SendWindowEnd string // "17:00"
SendDays []time.Weekday
Timezone *time.Location
MinGapSeconds int
MaxGapSeconds int
StartTime time.Time // when the campaign starts (typically now)
}
ScheduleConfig holds parameters for schedule computation.
type ScheduledSend ¶
type ScheduledSend struct {
ID int64 `json:"id"`
CampaignID int64 `json:"campaign_id"`
LeadID int64 `json:"lead_id"`
AccountID int64 `json:"account_id"`
StepNumber int `json:"step_number"`
VariantIndex int `json:"variant_index"`
SendAt time.Time `json:"send_at"`
Status string `json:"status"`
ThreadID string `json:"thread_id,omitempty"`
ParentMessageID string `json:"parent_message_id,omitempty"`
MessageID string `json:"message_id,omitempty"`
SentAt *time.Time `json:"sent_at,omitempty"`
ErrorMessage string `json:"error_message,omitempty"`
}
type ScheduledSendRow ¶
type ScheduledSendRow struct {
CampaignID int64
LeadID int64
AccountID int64
StepNumber int
VariantIndex int
SendAt time.Time
}
ScheduledSendRow is a row to insert into scheduled_sends.
func ComputeSchedule ¶
func ComputeSchedule(cfg ScheduleConfig) ([]ScheduledSendRow, error)
ComputeSchedule generates all scheduled_sends rows for a campaign.
type SecretResolver ¶
SecretResolver resolves stored secret references into runtime secret values.
type SecretResolverFunc ¶
SecretResolverFunc adapts a function to SecretResolver.
func (SecretResolverFunc) ResolveSecret ¶
func (fn SecretResolverFunc) ResolveSecret(ref string) (string, error)
type SendInboxReplyConfig ¶
type SendInboxReplyResult ¶
type SendInboxReplyResult struct {
CampaignID int64 `json:"campaign_id"`
LeadID int64 `json:"lead_id"`
AccountID int64 `json:"account_id"`
FromEmail string `json:"from_email"`
ToEmail string `json:"to_email"`
Subject string `json:"subject"`
MessageID string `json:"message_id"`
ThreadID string `json:"thread_id"`
}
func SendInboxReply ¶
func SendInboxReply(cfg SendInboxReplyConfig) (*SendInboxReplyResult, error)
type SendNowResult ¶
SendNowResult is returned by SendNowCampaign.
func SendNowCampaign ¶
func SendNowCampaign(db *sql.DB, name string) (*SendNowResult, error)
SendNowCampaign sets send_at to now for all pending sends in a campaign, so the next tick picks them up immediately.
type SendSMTPTestEmailOpts ¶
type SendSMTPTestEmailOpts struct {
RecipientEmail string `json:"recipient_email"`
Subject string `json:"subject"`
Body string `json:"body"`
}
SendSMTPTestEmailOpts holds a one-off SMTP test email request.
type SendSMTPTestEmailResult ¶
type SendSMTPTestEmailResult struct {
Email string `json:"email"`
RecipientEmail string `json:"recipient_email"`
MessageID string `json:"message_id"`
ThreadID string `json:"thread_id"`
}
SendSMTPTestEmailResult is returned after a test email is accepted by SMTP.
func SendSMTPTestEmail ¶
func SendSMTPTestEmail(account Account, opts SendSMTPTestEmailOpts, resolver SecretResolver) (*SendSMTPTestEmailResult, error)
SendSMTPTestEmail sends a one-off test email through an SMTP/IMAP account.
type Sequence ¶
type Sequence struct {
Name string `yaml:"name"`
Defaults SequenceDefaults `yaml:"defaults"`
Steps []SequenceStep `yaml:"steps"`
}
Sequence represents a parsed sequence YAML file.
func ParseSequence ¶
ParseSequence reads and parses a sequence YAML file.
func ParseSequenceFromBytes ¶
ParseSequenceFromBytes parses sequence YAML from bytes.
func (*Sequence) CollectPlaceholders ¶
CollectPlaceholders returns all unique placeholders used across all steps and variants.
type SequenceDefaults ¶
type SequenceDefaults struct {
FromName string `yaml:"from_name"`
}
type SequenceStep ¶
type SequenceStep struct {
Step int `yaml:"step"`
Delay int `yaml:"delay"` // days after previous step
Subject string `yaml:"subject"`
Body string `yaml:"body"`
Variants []SequenceVariant `yaml:"variants"`
}
type SequenceVariant ¶
type StepStats ¶
type StepStats struct {
Step int `json:"step"`
Sent int `json:"sent"`
Replies int `json:"replies"`
Unsubscribes int `json:"unsubscribes"`
Bounces int `json:"bounces"`
}
StepStats is a row from GetCampaignStepStats.
type Store ¶
Store is a thin wrapper around the database handle plus dialect metadata and dialect-specific tick locking.
func (*Store) AcquireTickLock ¶
AcquireTickLock acquires the dialect-specific tick lock and returns a handle that must be closed to release it.
func (*Store) DisplayTarget ¶
DisplayTarget returns a user-facing database target description.
type TickConfig ¶
type TickConfig struct {
DB *sql.DB
GWS GWSClient
DryRun bool
SendNow bool // ignore send_at timestamps, send all pending
Now time.Time // injectable for testing
NoSleep bool // skip inter-send sleep (for testing)
Timezone *time.Location // for daily limit day boundary; defaults to UTC
UnsubscribeHeader bool // add List-Unsubscribe header (off by default for cold email)
UnsubscribeSubject string // subject for List-Unsubscribe mailto header
SecretResolver SecretResolver // optional resolver for SMTP/IMAP password refs
SMTPSender SMTPEmailSender
IMAP IMAPMessageLister
}
TickConfig holds configuration for a tick invocation.
type TickResult ¶
type TickResult struct {
RepliesDetected int `json:"replies_detected"`
UnsubscribesDetected int `json:"unsubscribes_detected"`
BouncesDetected int `json:"bounces_detected"`
Sent int `json:"sent"`
Failed int `json:"failed"`
Skipped int `json:"skipped"`
DryRun bool `json:"dry_run"`
}
TickResult holds the summary of a tick invocation.
func Tick ¶
func Tick(cfg TickConfig) (*TickResult, error)
Tick runs one tick cycle: poll replies, poll bounces, send due emails.
type UpdateAccountOpts ¶
type UpdateAccountOpts struct {
DailyLimit *int
}
UpdateAccountOpts holds fields to update on an account.
type UpdateCampaignOpts ¶
type UpdateCampaignOpts struct {
SendWindowStart *string
SendWindowEnd *string
SendDays *string
Timezone *string
MinGapSeconds *int
MaxGapSeconds *int
SequenceFile *string // path to new sequence YAML
}
UpdateCampaignOpts holds fields to update. Zero values are ignored.
type UpdateSMTPIMAPAccountOpts ¶
type UpdateSMTPIMAPAccountOpts struct {
DailyLimit *int
SMTPHost *string
SMTPPort *int
SMTPUsername *string
SMTPPasswordRef *string
SMTPTLSMode *string
IMAPHost *string
IMAPPort *int
IMAPUsername *string
IMAPPasswordRef *string
IMAPTLSMode *string
}
UpdateSMTPIMAPAccountOpts holds fields to update on a generic SMTP/IMAP account.
type VariantStats ¶
type VariantStats struct {
Step int `json:"step"`
Variant int `json:"variant"`
Sent int `json:"sent"`
Replies int `json:"replies"`
ReplyRate float64 `json:"reply_rate"`
Unsubscribes int `json:"unsubscribes"`
Bounces int `json:"bounces"`
}
VariantStats is a row from GetCampaignVariantStats.
func GetCampaignVariantStats ¶
func GetCampaignVariantStats(db *sql.DB, campaignID int64) ([]VariantStats, error)
GetCampaignVariantStats returns per-step, per-variant stats for a campaign.