Documentation
¶
Overview ¶
Package lists manages newsletter lists and their audience: contacts, memberships with double opt-in and stored consent proof, unsubscribes, suppression after bounces and complaints, and import and export.
Index ¶
- Constants
- Variables
- func ConsentsToTracking(consent string) bool
- type Contact
- type ImportResult
- type Input
- type List
- type Member
- type Query
- type Seconds
- type Service
- func (s *Service) Add(ctx context.Context, l *List, email, name string, confirmed bool, in Signup) (*Member, bool, error)
- func (s *Service) All(ctx context.Context) ([]*List, error)
- func (s *Service) ByHost(ctx context.Context, host string) (*List, error)
- func (s *Service) ByID(ctx context.Context, id int64) (*List, error)
- func (s *Service) CheckHealth(ctx context.Context, listID int64) (string, error)
- func (s *Service) Confirm(ctx context.Context, memberID int64, ip string) (*Member, error)
- func (s *Service) ConfirmsLastHour(ctx context.Context, listID int64) int
- func (s *Service) Contact(ctx context.Context, email string) (*Contact, error)
- func (s *Service) Create(ctx context.Context, slug string, in Input) (*List, error)
- func (s *Service) Engaged(ctx context.Context, contactID int64)
- func (s *Service) ExportCSV(ctx context.Context, slug string, w io.Writer) (int, error)
- func (s *Service) Find(ctx context.Context, slug, email string) (*Member, error)
- func (s *Service) Forget(ctx context.Context, email string) error
- func (s *Service) Get(ctx context.Context, slug string) (*List, error)
- func (s *Service) Hosts(ctx context.Context, domainID int64) ([]string, error)
- func (s *Service) ImportCSV(ctx context.Context, l *List, r io.Reader, confirmed bool, in Signup) (ImportResult, error)
- func (s *Service) MarkConfirmSent(ctx context.Context, memberID int64)
- func (s *Service) Member(ctx context.Context, id int64) (*Member, error)
- func (s *Service) Members(ctx context.Context, slug string, q Query) ([]*Member, error)
- func (s *Service) Pause(ctx context.Context, slug, reason string) (*List, error)
- func (s *Service) Remove(ctx context.Context, slug string) error
- func (s *Service) SetTexts(ctx context.Context, slug string, values map[string]string) (*List, error)
- func (s *Service) SoftBounce(ctx context.Context, contactID int64, reason string) (suppressed bool, err error)
- func (s *Service) Subscribe(ctx context.Context, l *List, email string, in Signup) (*Member, bool, error)
- func (s *Service) Suppress(ctx context.Context, email, reason string) error
- func (s *Service) Unsubscribe(ctx context.Context, memberID, issueID int64, how string) (*Member, error)
- func (s *Service) Unsuppress(ctx context.Context, email string) error
- func (s *Service) Update(ctx context.Context, slug string, in Input) (*List, error)
- type Signup
- type TextValue
Constants ¶
const ( ConfirmCooldown = time.Hour MaxConfirms = 3 ConfirmWindow = 30 * 24 * time.Hour )
ConfirmCooldown is the least time between two confirmation mails to one address for one list; MaxConfirms caps them per address over ConfirmWindow, across all lists.
const ( SoftBounceLimit = 3 SoftBounceWindow = 14 * 24 * time.Hour )
Soft bounce limits: an address whose deliveries keep failing temporarily is gone in practice. M3AAWG removes it after two consecutive bounces over two weeks or more, Spamhaus after three in a row (lfr:E054, lfr:E057).
const ConfirmValidity = 30 * 24 * time.Hour
ConfirmValidity is how long a confirmation link works.
const DefaultMinGap = 20 * time.Hour
DefaultMinGap keeps two issues of a list from landing on the same day by accident; a list that sends daily sets it lower.
Variables ¶
var ErrExpired = errors.New("this confirmation link has expired; sign up again")
ErrExpired means a confirmation link is too old.
var ErrOptedOut = errors.New("unsubscribed, complained, bounced or erased; only a new signup with confirmation adds it again")
ErrOptedOut means an address left, complained, bounced or was erased, so only a new double opt-in may add it.
Functions ¶
func ConsentsToTracking ¶
ConsentsToTracking reports whether a consent text tells readers about open and click measurement. Only readers who agreed to such a text are measured (TDDDG §25, GDPR Art. 6).
Types ¶
type Contact ¶
type Contact struct {
Email string `json:"email"`
Name string `json:"name,omitempty"`
Created time.Time `json:"created"`
Suppressed string `json:"suppressed,omitempty"`
Lists []*Member `json:"lists"`
Sent int `json:"sent"`
Opens int `json:"opens"`
Clicks int `json:"clicks"`
}
Contact is an address with its memberships.
type ImportResult ¶
type ImportResult struct {
Added int `json:"added"`
Existing int `json:"existing"`
OptedOut int `json:"opted_out"` // left, complained, bounced or erased: kept off
Reconfirm int `json:"reconfirm"` // on the list already, asked to confirm again
Pending []int64 `json:"-"` // memberships that need a confirmation mail
Invalid []string `json:"invalid,omitempty"`
}
ImportResult counts an import.
type Input ¶
type Input struct {
Name, Description, From, FromName, ReplyTo, Host, Language *string
Tracking *bool
ImprintURL, PrivacyURL, Address, Consent, Cadence *string
Headline, Pitch *string
MinGap *time.Duration
}
Input sets list fields; nil pointers keep the current value.
type List ¶
type List struct {
ID int64 `json:"id"`
DomainID int64 `json:"-"`
Domain string `json:"domain"`
Slug string `json:"slug"`
Name string `json:"name"`
Description string `json:"description,omitempty"`
From string `json:"from"` // sender address
FromName string `json:"from_name"`
ReplyTo string `json:"reply_to,omitempty"`
Host string `json:"host,omitempty"`
Language string `json:"language"`
Tracking bool `json:"tracking"`
ImprintURL string `json:"imprint_url,omitempty"`
PrivacyURL string `json:"privacy_url,omitempty"`
Address string `json:"address,omitempty"`
Consent string `json:"consent,omitempty"`
Paused string `json:"paused,omitempty"`
Cadence string `json:"cadence,omitempty"` // how often mail comes, shown at signup
Headline string `json:"headline,omitempty"` // the website's first line; the name when empty
Pitch string `json:"pitch,omitempty"` // under the headline; the description when empty
MinGap Seconds `json:"min_gap"` // time that must pass between two issues
Created time.Time `json:"created"`
Confirmed int `json:"confirmed"`
Pending int `json:"pending"`
Unsubscribed int `json:"unsubscribed"`
// Texts is loaded with the list in the same query.
Texts map[string]string `json:"texts,omitempty"`
// contains filtered or unexported fields
}
List is one newsletter.
func (*List) ConsentText ¶
ConsentText is the statement a signup agrees to.
func (*List) TextValues ¶ added in v0.2.2
TextValues returns all effective reader texts.
type Member ¶
type Member struct {
ID int64 `json:"id"`
ListID int64 `json:"-"`
List string `json:"list"`
ContactID int64 `json:"contact_id"`
Email string `json:"email"`
Name string `json:"name,omitempty"`
Status string `json:"status"`
Source string `json:"source,omitempty"`
Consent string `json:"consent,omitempty"`
SignupAt time.Time `json:"signup_at"`
SignupIP string `json:"signup_ip,omitempty"`
ConfirmAt time.Time `json:"confirm_at,omitzero"`
ConfirmIP string `json:"confirm_ip,omitempty"`
UnsubAt time.Time `json:"unsubscribed_at,omitzero"`
UnsubHow string `json:"unsubscribed_how,omitempty"`
Suppressed string `json:"suppressed,omitempty"` // why the address gets nothing at all
}
Member is one address on one list.
type Query ¶
type Query struct {
Status string // pending, confirmed, unsubscribed; empty for all
Search string // part of the address
Limit int
After int64
}
Query selects members.
type Service ¶
Service is the list store.
func (*Service) Add ¶
func (s *Service) Add(ctx context.Context, l *List, email, name string, confirmed bool, in Signup) (*Member, bool, error)
Add puts an address on a list directly. confirmed asserts that consent exists outside lfr (e.g. a migrated list); otherwise it is a normal pending signup and the second return value says to send a confirmation.
func (*Service) CheckHealth ¶
CheckHealth pauses a list whose complaint or bounce rate over the last 30 days is too high. It returns the reason when it paused the list.
func (*Service) ConfirmsLastHour ¶
ConfirmsLastHour counts confirmation mails a list sent in the last hour.
func (*Service) Hosts ¶
Hosts returns the web hosts of lists on a domain (or all when domainID is 0).
func (*Service) ImportCSV ¶
func (s *Service) ImportCSV(ctx context.Context, l *List, r io.Reader, confirmed bool, in Signup) (ImportResult, error)
ImportCSV reads addresses (first column, or a column named email; an optional name column) and adds them. Without confirmed every address gets a confirmation request instead.
func (*Service) MarkConfirmSent ¶
MarkConfirmSent records that a confirmation mail was queued.
func (*Service) SetTexts ¶ added in v0.2.2
func (s *Service) SetTexts(ctx context.Context, slug string, values map[string]string) (*List, error)
SetTexts validates the whole batch before applying it atomically.
func (*Service) SoftBounce ¶
func (s *Service) SoftBounce(ctx context.Context, contactID int64, reason string) (suppressed bool, err error)
SoftBounce counts a temporary failure that ended a delivery to a contact and suppresses the address once the limits are reached.
func (*Service) Subscribe ¶
func (s *Service) Subscribe(ctx context.Context, l *List, email string, in Signup) (*Member, bool, error)
Subscribe records a signup. It returns the membership and whether a confirmation mail should go out now. Signing up again while confirmed changes nothing; while pending it resends the confirmation after the cooldown; after unsubscribing it starts over with a new confirmation.
func (*Service) Unsubscribe ¶
func (s *Service) Unsubscribe(ctx context.Context, memberID, issueID int64, how string) (*Member, error)
Unsubscribe ends a membership; how records the channel (link, header, mail, operator).
func (*Service) Unsuppress ¶
Unsuppress lets an address receive mail again.
type Signup ¶
type Signup struct {
IP, UserAgent, Source, Consent string
// From an import: when and from where the reader opted in on the old
// platform, so consent proof and footers keep the real date.
At, ConfirmedAt time.Time
ConfirmIP string
// Reconfirm asks readers already on the list to confirm again, for a
// stale import: they turn pending and get a confirmation mail.
Reconfirm bool
}
Signup describes where a subscription came from: the consent proof.