// Package postmark uses the free https://spamcheck.postmarkapp.com/ // See https://spamcheck.postmarkapp.com/doc/ for more details. package postmark import ( "bytes" "encoding/json" "fmt" "net/http" "regexp" "strings" "time" ) // Response struct type Response struct { Success bool `json:"success"` Message string `json:"message"` // for errors only Score string `json:"score"` Rules []Rule `json:"rules"` Report string `json:"report"` // ignored } // Rule struct type Rule struct { Score string `json:"score"` // Name not returned by postmark but rather extracted from description Name string `json:"name"` Description string `json:"description"` } // Check will post the email data to Postmark func Check(email []byte, timeout int) (Response, error) { r := Response{} // '{"email":"raw dump of email", "options":"short"}' var d struct { // The raw dump of the email to be filtered, including all headers. Email string `json:"email"` // Default "long". Must either be "long" for a full report of processing rules, or "short" for a score request. Options string `json:"options"` } d.Email = string(email) d.Options = "long" data, err := json.Marshal(d) if err != nil { return r, err } client := http.Client{ Timeout: time.Duration(timeout) * time.Second, } resp, err := client.Post("https://spamcheck.postmarkapp.com/filter", "application/json", bytes.NewBuffer(data)) if err != nil { return r, err } defer resp.Body.Close() err = json.NewDecoder(resp.Body).Decode(&r) // remove trailing line spaces for all lines in report re := regexp.MustCompile("\r?\n") lines := re.Split(r.Report, -1) reportLines := []string{} for _, l := range lines { line := strings.TrimRight(l, " ") reportLines = append(reportLines, line) } reportRaw := strings.Join(reportLines, "\n") // join description lines to make a single line per rule re2 := regexp.MustCompile("\n ") report := re2.ReplaceAllString(reportRaw, "") for i, rule := range r.Rules { // populate rule name r.Rules[i].Name = nameFromReport(rule.Score, rule.Description, report) } return r, err } // Extract the name of the test from the report as Postmark does not include this in the JSON reports func nameFromReport(score, description, report string) string { score = regexp.QuoteMeta(score) description = regexp.QuoteMeta(description) str := fmt.Sprintf("%s\\s+([A-Z0-9\\_]+)\\s+%s", score, description) re := regexp.MustCompile(str) matches := re.FindAllStringSubmatch(report, 1) if len(matches) > 0 && len(matches[0]) == 2 { return strings.TrimSpace(matches[0][1]) } return "" }