Documentation
¶
Index ¶
- Constants
- Variables
- func AccountETL(f io.Reader) error
- func ActionToType(a string) string
- func ConsoleAndLogFatal(s ...interface{})
- func Contains(arr []string, val string) bool
- func DecodePath(s string) ([]byte, error)
- func EncodeLink(encodeTrackingURL, encodeAction, encodeMessageID, encodeRcptTo, ... string, ...) (string, error)
- func EncodePath(data []byte) (string, error)
- func FeedEvents(client *redis.Client, host string, apiKey string, maxAge time.Duration) error
- func FeedForever(client *redis.Client, host string, apiKey string, maxAge time.Duration)
- func GetenvDefault(k string, d string) string
- func HostCleanup(host string) string
- func MakeSession(c *smtpproxy.Client, bkd *Backend) smtpproxy.Session
- func MyLogger(filename string)
- func MyRedis() (client *redis.Client)
- func PositionIn(arr []string, val string) (int, bool)
- func SafeStringToInt(s string) int
- func SparkPostEventNDJSON(eStr string, client *redis.Client) ([]byte, error)
- func SparkPostIngest(ingestData []byte, client *redis.Client, host string, apiKey string) error
- func StoreEvent(r []string, client *redis.Client) error
- func StoreHeaders(r []string, client *redis.Client) error
- func TrackingServer(w http.ResponseWriter, req *http.Request)
- func UniqMessageID() string
- type Backend
- type GeoIP
- type IngestResult
- type Session
- func (s *Session) Auth(expectcode int, cmd, arg string) (int, string, error)
- func (s *Session) Data(r io.Reader, w io.WriteCloser) (int, string, error)
- func (s *Session) DataCommand() (io.WriteCloser, int, string, error)
- func (s *Session) Greet(helotype string) ([]string, int, string, error)
- func (s *Session) Mail(expectcode int, cmd, arg string) (int, string, error)
- func (s *Session) Passthru(expectcode int, cmd, arg string) (int, string, error)
- func (s *Session) Quit(expectcode int, cmd, arg string) (int, string, error)
- func (s *Session) Rcpt(expectcode int, cmd, arg string) (int, string, error)
- func (s *Session) Reset(expectcode int, cmd, arg string) (int, string, error)
- func (s *Session) StartTLS() (int, string, error)
- func (s *Session) Unknown(expectcode int, cmd, arg string) (int, string, error)
- type SparkPostEvent
- type TimedBuffer
- type TrackEvent
- type Wrapper
- func (wrap *Wrapper) Active() bool
- func (wrap *Wrapper) HandleMessagePart(dst io.Writer, part io.Reader, cType string, cte string) error
- func (wrap *Wrapper) InitialOpenPixel() string
- func (wrap *Wrapper) MailCopy(dst io.Writer, src io.Reader) error
- func (wrap *Wrapper) OpenPixel() string
- func (wrap *Wrapper) ProcessMessageHeaders(h mail.Header) error
- func (wrap *Wrapper) SetMessageInfo(msgID string, rcpt string)
- func (wrap *Wrapper) TrackHTML(w io.Writer, r io.Reader) (int, error)
- func (wrap *Wrapper) WrapURL(url string) string
- type WrapperData
Constants ¶
const MsgIDTTL = time.Duration(time.Hour * 24 * 10)
MsgIDTTL defines the time-to-live for augmentation data
const RedisAcctHeaders = "acct_headers"
RedisAcctHeaders holds the PowerMTA accounting file headers
const RedisQueue = "trk_queue"
RedisQueue connects the tracker and feeder tasks
const SparkPostIngestBatchMaxAge = 30 * time.Second
SparkPostIngestBatchMaxAge sets the maximum time we wait before forwarding a batch of events
const SparkPostIngestMaxPayload = 5 * 1024 * 1024
SparkPostIngestMaxPayload set in accord with https://developers.sparkpost.com/api/events-ingest/#header-event-format
const SparkPostMessageIDHeader = "X-Sp-Message-Id"
SparkPostMessageIDHeader is the email header name that carries the unique message ID
const TrackingPrefix = "msgID_"
TrackingPrefix is the prefix for keys holding augmentation data
const XRealIPHeader = "X-Real-Ip"
XRealIPHeader is a header that will be used, if provided (e.g. from NGINX)
Variables ¶
var TransparentGif = []byte("GIF89a\x01\x00\x01\x00\x80\x00\x00\xff\xff\xff" +
"\xff\xff\xff\x21\xf9\x04\x01\x0a\x00\x01\x00\x2c\x00\x00\x00\x00" +
"\x01\x00\x01\x00\x00\x02\x02\x4c\x01\x00\x3b\x00")
TransparentGif contains the bytes that should be served back to the client for an open pixel
Functions ¶
func AccountETL ¶
AccountETL extracts, transforms accounting data from PowerMTA into Redis records
func ActionToType ¶
ActionToType maps the short "action" string used in URLs to SparkPost event type
func ConsoleAndLogFatal ¶
func ConsoleAndLogFatal(s ...interface{})
ConsoleAndLogFatal writes error to both log and stdout
func DecodePath ¶
DecodePath returns the zlib-decoded, base64-decoded version of a url path string as []byte
func EncodeLink ¶
func EncodeLink(encodeTrackingURL, encodeAction, encodeMessageID, encodeRcptTo, encodeTargetLinkURL string, trackOpen, trackInitialOpen, trackLink bool) (string, error)
EncodeLink - convenience function
func EncodePath ¶
EncodePath returns the base64-encoded, zlib-encoded version of data as a URL path string
func FeedEvents ¶
FeedEvents sends data arriving via Redis queue to SparkPost ingest API. Send a batch periodically, or every X MB, whichever comes first.
func FeedForever ¶
FeedForever processes events forever
func GetenvDefault ¶
GetenvDefault returns an environment variable, with default if unset
func HostCleanup ¶
HostCleanup returns a SparkPost host address in canonical form (with schema, without /api/v1 path)
func MakeSession ¶
MakeSession returns a session for this client and backend
func MyLogger ¶
func MyLogger(filename string)
MyLogger sets up a custom logger, if filename is given, emitting to stdout as well If filename is blank string, then output is stdout only
func PositionIn ¶
PositionIn returns the position of a value within an array of strings, if an element exactly matches val
func SafeStringToInt ¶
SafeStringToInt logs an error and returns zero if it can't convert
func SparkPostEventNDJSON ¶
SparkPostEventNDJSON formats a SparkPost event into NDJSON, augmenting with Redis data
func SparkPostIngest ¶
SparkPostIngest POSTs a batch of ingestData to SparkPost Ingest API
func StoreEvent ¶
StoreEvent puts a single accounting event r into redis, based on previously seen header format
func StoreHeaders ¶
StoreHeaders puts an acccounting header record (sent at PowerMTA startup).
Checks for required and optional fields. Writes these into persistent storage, so that we can decode "d" records in future, separate process invocations.
func TrackingServer ¶
func TrackingServer(w http.ResponseWriter, req *http.Request)
TrackingServer expects URL paths of the form /xyzzy where xyzzy = base64 urlsafe encoded, Zlib compressed, []byte These are written to the Redis queue
func UniqMessageID ¶
func UniqMessageID() string
UniqMessageID returns a SparkPost formatted unique messageID
Types ¶
type Backend ¶
type Backend struct {
// contains filtered or unexported fields
}
The Backend implements SMTP server methods.
func NewBackend ¶
func NewBackend(outHostPort string, verbose bool, upstreamDataDebug *os.File, wrap *Wrapper, insecureSkipVerify bool) *Backend
NewBackend init function
func (*Backend) SetVerbose ¶
SetVerbose allows changing logging options on-the-fly
func (*Backend) SetWrapper ¶
SetWrapper Verbose allows changing on-the-fly
type GeoIP ¶
type GeoIP struct { Country string Region string City string Latitude float64 Longitude float64 Zip int PostalCode string }
GeoIP data expected by SparkPost .. will be blank for now
type IngestResult ¶
type IngestResult struct { Results struct { ID string `json:"id"` } `json:"results"` Errors []struct { Message string `json:"message"` } `json:"errors"` }
IngestResult object coming back from SparkPost
type Session ¶
type Session struct {
// contains filtered or unexported fields
}
A Session is returned after successful login. Here hold information that needs to persist across message phases.
func (*Session) DataCommand ¶
DataCommand pass upstream, returning a place to write the data AND the usual responses
type SparkPostEvent ¶
type SparkPostEvent struct { EventWrapper struct { EventGrouping struct { Type string `json:"type"` DelvMethod string `json:"delv_method"` EventID string `json:"event_id"` IPAddress string `json:"ip_address"` GeoIP GeoIP `json:"geo_ip"` MessageID string `json:"message_id"` RcptTo string `json:"rcpt_to"` TimeStamp string `json:"timestamp"` TargetLinkURL string `json:"target_link_url"` UserAgent string `json:"user_agent"` SubaccountID int `json:"subaccount_id"` } `json:"track_event"` } `json:"msys"` }
SparkPostEvent structure for SparkPost Ingest API. Note the nesting. There are some fields we're not populating here, as they will automatically be "enriched" by SparkPost, providing there is a injection event matching for this message_id.
ab_test_id, ab_test_version, amp_enabled, binding, binding_group, campaign_id, click_tracking, friendly_from, initial_pixel, injection_time, ip_pool, ip_pool_raw, msg_from, msg_size, open_tracking, rcpt_meta, rcpt_tags, rcpt_type, recv_method, routing_domain, sending_ip, subject, template_id, template_version, transactional, transmission_id
We are also not populating: num_retries, queue_time, raw_rcpt_to, target_link_name A future implementation could usefully populate target_link_name, geo_ip if desired.
type TimedBuffer ¶
TimedBuffer associates content with a time started and a maximum age it should be held for
func (*TimedBuffer) AgedContent ¶
func (t *TimedBuffer) AgedContent() bool
AgedContent returns true if the buffer has non-nil contents that are older than the specified maxAge
type TrackEvent ¶
type TrackEvent struct { WD WrapperData TimeStamp string `json:"ts"` UserAgent string `json:"ua"` IPAddress string `json:"ip"` }
TrackEvent is the augmented info passed from "tracker" via the Redis event queue to "feeder"
type Wrapper ¶
Wrapper carries the per-message information as each message is processed
func NewWrapper ¶
NewWrapper returns a tracker with the persistent info set up from params
func (*Wrapper) HandleMessagePart ¶
func (wrap *Wrapper) HandleMessagePart(dst io.Writer, part io.Reader, cType string, cte string) error
HandleMessagePart walks the MIME structure, and may be called recursively. The incoming content type and cte (content transfer encoding) are passed separately
func (*Wrapper) InitialOpenPixel ¶
InitialOpenPixel returns an html fragment with pixel for initial open tracking. If there are problems, empty string is returned.
func (*Wrapper) MailCopy ¶
MailCopy transfers the mail body from downstream (client) to upstream (server), using the engagement wrapper The writer should be closed by the parent function
func (*Wrapper) OpenPixel ¶
OpenPixel returns an html fragment with pixel for bottom open tracking. If there are problems, empty string is returned.
func (*Wrapper) ProcessMessageHeaders ¶
ProcessMessageHeaders reads the message's current headers and updates/inserts any new ones required. The RCPT TO address is grabbed
func (*Wrapper) SetMessageInfo ¶
SetMessageInfo sets the per-message specifics
type WrapperData ¶
type WrapperData struct { Action string `json:"act"` // carries "c" = click, "o" = open, "i" = initial open TargetLinkURL string `json:"t_url"` MessageID string `json:"msg_id"` RcptTo string `json:"rcpt"` }
WrapperData is used to build the tracking URL
func DecodeLink ¶
func DecodeLink(urlStr string) ([]byte, WrapperData, string, error)
DecodeLink - convenience function. returns JSON intermediate form, decoded Wrapper data, and tracking domain