Skip to content

Commit

Permalink
Merge pull request #2 from SparkPost/master
Browse files Browse the repository at this point in the history
up from master
  • Loading branch information
kakysha committed May 27, 2016
2 parents 6651000 + b0b70d1 commit b6fcdc3
Show file tree
Hide file tree
Showing 2 changed files with 271 additions and 0 deletions.
268 changes: 268 additions & 0 deletions cmd/sparks/sparks.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,268 @@
// Sparks is a command-line tool for quickly sending email using SparkPost.
// It's like swaks, and apiaks sounded awkward.
package main

import (
"encoding/base64"
"encoding/json"
"flag"
"io/ioutil"
"log"
"os"
"regexp"
"strings"
"time"

sp "github.com/SparkPost/gosparkpost"
)

type Strings []string

func (s *Strings) String() string {
return strings.Join([]string(*s), ",")
}

func (s *Strings) Set(value string) error {
*s = append([]string(*s), value)
return nil
}

var to Strings
var cc Strings
var bcc Strings
var headers Strings
var images Strings
var attachments Strings

func init() {
flag.Var(&to, "to", "where the mail goes to")
flag.Var(&cc, "cc", "carbon copy this address")
flag.Var(&bcc, "bcc", "blind carbon copy this address")
flag.Var(&headers, "header", "custom header for your content")
flag.Var(&images, "img", "mimetype:cid:path for image to include")
flag.Var(&attachments, "attach", "mimetype:name:path for file to attach")
}

var from = flag.String("from", "[email protected]", "where the mail came from")
var subject = flag.String("subject", "", "email subject")
var htmlFlag = flag.String("html", "", "string/filename containing html content")
var textFlag = flag.String("text", "", "string/filename containing text content")
var subsFlag = flag.String("subs", "", "string/filename containing substitution data (json object)")
var sendDelay = flag.String("send-delay", "", "delay delivery the specified amount of time")
var inline = flag.Bool("inline-css", false, "automatically inline css")
var dryrun = flag.Bool("dry-run", false, "dump json that would be sent to server")
var url = flag.String("url", "", "base url for api requests (optional)")
var help = flag.Bool("help", false, "display a help message")

func main() {
flag.Parse()

if *help {
flag.Usage()
os.Exit(0)
}

if len(to) <= 0 {
log.Fatal("SUCCESS: send mail to nobody!\n")
}

apiKey := os.Getenv("SPARKPOST_API_KEY")
if strings.TrimSpace(apiKey) == "" {
log.Fatal("FATAL: API key not found in environment!\n")
}

hasHtml := strings.TrimSpace(*htmlFlag) != ""
hasText := strings.TrimSpace(*textFlag) != ""
hasSubs := strings.TrimSpace(*subsFlag) != ""

if !hasHtml && !hasText {
log.Fatal("FATAL: must specify one of --html or --text!\n")
}

cfg := &sp.Config{ApiKey: apiKey}
if strings.TrimSpace(*url) != "" {
if !strings.HasPrefix(*url, "https://") {
log.Fatal("FATAL: base url must be https!\n")
}
cfg.BaseUrl = *url
}

var sparky sp.Client
err := sparky.Init(cfg)
if err != nil {
log.Fatalf("SparkPost client init failed: %s\n", err)
}

content := sp.Content{
From: *from,
Subject: *subject,
}

if hasHtml {
if strings.Contains(*htmlFlag, "/") {
// read file to get html
htmlBytes, err := ioutil.ReadFile(*htmlFlag)
if err != nil {
log.Fatal(err)
}
content.HTML = string(htmlBytes)
} else {
// html string passed on command line
content.HTML = *htmlFlag
}
}

if hasText {
if strings.Contains(*textFlag, "/") {
// read file to get text
textBytes, err := ioutil.ReadFile(*textFlag)
if err != nil {
log.Fatal(err)
}
content.Text = string(textBytes)
} else {
// text string passed on command line
content.Text = *textFlag
}
}

if len(images) > 0 {
for _, imgStr := range images {
img := strings.SplitN(imgStr, ":", 3)
if len(img) != 3 {
log.Fatalf("--img format is mimetype:cid:path")
}
imgBytes, err := ioutil.ReadFile(img[2])
if err != nil {
log.Fatal(err)
}
iimg := sp.InlineImage{
MIMEType: img[0],
Filename: img[1],
B64Data: base64.StdEncoding.EncodeToString(imgBytes),
}
content.InlineImages = append(content.InlineImages, iimg)
}
}

if len(attachments) > 0 {
for _, attStr := range attachments {
att := strings.SplitN(attStr, ":", 3)
if len(att) != 3 {
log.Fatalf("--attach format is mimetype:name:path")
}
attBytes, err := ioutil.ReadFile(att[2])
if err != nil {
log.Fatal(err)
}
attach := sp.Attachment{
MIMEType: att[0],
Filename: att[1],
B64Data: base64.StdEncoding.EncodeToString(attBytes),
}
content.Attachments = append(content.Attachments, attach)
}
}

tx := &sp.Transmission{}

var subJson *json.RawMessage
if hasSubs {
var subsBytes []byte
if strings.Contains(*subsFlag, "/") {
// read file to get substitution data
subsBytes, err = ioutil.ReadFile(*subsFlag)
if err != nil {
log.Fatal(err)
}
} else {
subsBytes = []byte(*subsFlag)
}

subJson = &json.RawMessage{}
err = json.Unmarshal(subsBytes, subJson)
if err != nil {
log.Fatal(err)
}
}

headerTo := strings.Join(to, ",")

tx.Recipients = []sp.Recipient{}
for _, r := range to {
tx.Recipients = append(tx.Recipients.([]sp.Recipient), sp.Recipient{
Address: sp.Address{Email: r, HeaderTo: headerTo},
SubstitutionData: subJson,
})
}

if len(cc) > 0 {
for _, r := range cc {
tx.Recipients = append(tx.Recipients.([]sp.Recipient), sp.Recipient{
Address: sp.Address{Email: r, HeaderTo: headerTo},
SubstitutionData: subJson,
})
}
if content.Headers == nil {
content.Headers = map[string]string{}
}
content.Headers["cc"] = strings.Join(cc, ",")
}

if len(bcc) > 0 {
for _, r := range bcc {
tx.Recipients = append(tx.Recipients.([]sp.Recipient), sp.Recipient{
Address: sp.Address{Email: r, HeaderTo: headerTo},
SubstitutionData: subJson,
})
}
}

if len(headers) > 0 {
if content.Headers == nil {
content.Headers = map[string]string{}
}
hb := regexp.MustCompile(`:\s*`)
for _, hstr := range headers {
hra := hb.Split(hstr, 2)
content.Headers[hra[0]] = hra[1]
}
}

tx.Content = content

if strings.TrimSpace(*sendDelay) != "" {
if tx.Options == nil {
tx.Options = &sp.TxOptions{}
}
dur, err := time.ParseDuration(*sendDelay)
if err != nil {
log.Fatal(err)
}
start := sp.RFC3339(time.Now().Add(dur))
tx.Options.StartTime = &start
}

if *inline != false {
if tx.Options == nil {
tx.Options = &sp.TxOptions{}
}
tx.Options.InlineCSS = true
}

if *dryrun != false {
jsonBytes, err := json.Marshal(tx)
if err != nil {
log.Fatal(err)
}
os.Stdout.Write(jsonBytes)
os.Exit(0)
}

id, req, err := sparky.Send(tx)
if err != nil {
log.Fatal(err)
}

log.Printf("HTTP [%s] TX %s\n", req.HTTP.Status, id)
}
3 changes: 3 additions & 0 deletions recipient_lists.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,9 @@ func ParseAddress(addr interface{}) (a Address, err error) {
a.Email = addrVal
}

case Address:
a = addr.(Address)

case map[string]interface{}:
// auto-parsed nested json object
for k, v := range addrVal {
Expand Down

0 comments on commit b6fcdc3

Please sign in to comment.