postdog

package module
v0.3.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 19, 2020 License: MIT Imports: 5 Imported by: 0

README

postdog - GO mailing toolkit

Test GoDoc Version

postdog is a mailing toolkit for GO applications, providing:

  • Simple mail writing
  • Sending mails with support for different transports (SMTP, Gmail etc.)
  • Middleware
  • Hooks
  • Configuration via YAML
  • Plugin support

Getting Started

Visit the docs at go.dev for examples.

Installation

go get github.com/bounoable/postdog

Main packages

Package Description
postdog Queued sending and support for logging, middlewares, hooks & plugins
letter Provides the Letter type and write helpers
autowire Automatic Office setup through a YAML config file

Configuration

You can configure your transports in a YAML config file or configure them manually.

YAML
# /app/configs/postdog.yml

default: test # Set the default transport

transports:
  test: # Specify a unique name
    provider: smtp # Set the transport provider
    config: # Provider configuration
      host: smtp.mailtrap.io
      port: 587
      username: abcdef123456
      password: 123456abcdef
  
  production:
    provider: gmail
    config:
      serviceAccount: ${SERVICE_ACCOUNT_PATH} # Use environment variable
package main

import (
  "context"

  "github.com/bounoable/postdog/autowire"
  "github.com/bounoable/postdog/letter"
  "github.com/bounoable/postdog/transport/smtp"
  "github.com/bounoable/postdog/transport/gmail"
)

func main() {
  // Load the configuration
  cfg, err := autowire.File(
    "/app/configs/postdog.yml",
    // Register the SMTP and Gmail providers.
    smtp.Register,
    gmail.Register,
  )
  if err != nil {
    panic(err)
  }

  po, err := cfg.Office(
    context.Background(),
    // Office options ... (plugins etc.)
  )
  if err != nil {
    panic(err)
  }

  // Send via the default transport ("test")
  if err = po.Send(context.Background(), letter.Write(
    letter.From("Bob", "bob@example.com"),
    letter.To("Linda", "linda@example.com"),
    // ...
  )); err != nil {
    panic(err)
  }

  // Send via a specific transport ("production")
  if err = po.SendWith(context.Background(), "production", letter.Write(
    letter.From("Bob", "bob@example.com"),
    letter.To("Linda", "linda@example.com"),
    // ...
  )); err != nil {
    panic(err)
  }
}
Manual

You can also configure the transports manually or use them directly:

package main

import (
  "context"

  "github.com/bounoable/postdog"
  "github.com/bounoable/postdog/letter"
  "github.com/bounoable/postdog/transport/smtp"
  "github.com/bounoable/postdog/transport/gmail"
)

func main() {
  test := smtp.NewTransport("smtp.mailtrap.io", 587, "abcdef123456", "123456abcdef")
  prod, err := gmail.NewTransport(context.Background(), gmail.CredentialsFile("/path/to/google/service/account.json"))

  if err != nil {
    panic(err)
  }

  po := postdog.New()
  po.ConfigureTransport("test", test)
  po.ConfigureTransport("production", prod, postdog.DefaultTransport()) // make it the default transport

  err = po.Send(context.Background(), letter.Write())

  // or use transport directly
  err = prod.Send(context.Background(), letter.Write())
}

Sending mails / letters

package main

import (
  "context"

  "github.com/bounoable/postdog"
  "github.com/bounoable/postdog/autowire"
  "github.com/bounoable/postdog/letter"
  "github.com/bounoable/postdog/transport/smtp"
  "github.com/bounoable/postdog/transport/gmail"
)

func main() {
  // Load config
  cfg, err := autowire.File("/path/to/config.yml", smtp.Register, gmail.Register)
  if err != nil {
    panic(err)
  }

  // Initialize office
  po, err := cfg.Office(context.Background())
  if err != nil {
    panic(err)
  }

  // Send letter directly
  err = po.Send(context.Background(), letter.Write())

  // Queued sending

  // 1. Make sure postdog is running
  go po.Run(context.Background(), postdog.Workers(3))

  // 2. Dispatch the letter
  err = po.Dispatch(context.Background(), letter.Write())
}

Plugins

You can extend postdog with plugins that register custom middleware and hooks:

Plugin Description
Markdown Markdown support in letters
Store Store sent letters in a database
Template Template support in letters

Writing plugins

Plugins have to provide a single Install() method that accepts a plugin context. Here is an example of a bad word filter:

package main

import (
  "context"
  "strings"

  "github.com/bounoable/postdog"
  "github.com/bounoable/postdog/letter"
)

type badWordFilterPlugin struct {
  words []string
}

func (plug badWordFilterPlugin) Install(ctx postdog.PluginContext) {
  // register middleware
  ctx.WithMiddleware(
    postdog.MiddlewareFunc(
      ctx context.Context,
      let letter.Letter,
      next func(context.Context, letter.Letter) (letter.Letter, error),
    ) (letter.Letter, error) {
      for _, word := range plug.words {
        let.Text = strings.Replace(let.Text, word, "")
        let.HTML = strings.Replace(let.HTML, word, "")
      }

      // call the next middleware
      return next(ctx, let)
    }),
  )
}

func main() {
  po := postdog.New(
    postdog.WithPlugin(badWordFilterPlugin{
      words: []string{"very", "bad", "words"},
    }),
  )
}

You can also use a function as a plugin with postdog.PluginFunc:

package main

import (
  "context"
  "strings"

  "github.com/bounoable/postdog"
  "github.com/bounoable/postdog/letter"
)

func BadWordPlugin(words ...string) postdog.PluginFunc {
  return func(ctx postdog.PluginContext) {
    // register middleware
    ctx.WithMiddleware(
      postdog.MiddlewareFunc(
        ctx context.Context,
        let letter.Letter,
        next func(context.Context, letter.Letter) (letter.Letter, error),
      ) (letter.Letter, error) {
        for _, word := range plug.words {
          let.Text = strings.Replace(let.Text, word, "")
          let.HTML = strings.Replace(let.HTML, word, "")
        }

        // call the next middleware
        return next(ctx, let)
      }),
    )
  }
}

func main() {
  po := postdog.New(
    postdog.WithPlugin(
      BadWordPlugin("very", "bad", "words"),
    ),
  )
}

Documentation

Overview

Example

Configure postdog from YAML file.

package main

import (
	"bytes"
	"context"

	"github.com/bounoable/postdog/autowire"
	"github.com/bounoable/postdog/letter"
)

func main() {
	// Load YAML config
	cfg, err := autowire.File("/path/to/config.yml")
	if err != nil {
		panic(err)
	}

	// Build office
	off, err := cfg.Office(context.Background())
	if err != nil {
		panic(err)
	}

	// Send mail with default transport
	err = off.Send(
		context.Background(),
		letter.Write(
			letter.From("Bob", "bob@belcher.test"),
			letter.To("Calvin", "calvin@fishoeder.test"),
			letter.To("Felix", "felix@fishoeder.test"),
			letter.BCC("Jimmy", "jimmy@pesto.test"),
			letter.Subject("Hi, buddy."),
			letter.Text("Have a drink later?"),
			letter.HTML("Have a <strong>drink</strong> later?"),
			letter.MustAttach(bytes.NewReader([]byte("secret")), "my_burger_recipe.txt"),
		),
	)

	// or use a specific transport
	err = off.SendWith(
		context.Background(),
		"mytransport",
		letter.Write(
			letter.From("Bob", "bob@belcher.test"),
			// ...
		),
	)
}
Output:

Example (ManualConfiguration)
package main

import (
	"context"

	"github.com/bounoable/postdog"
	"github.com/bounoable/postdog/letter"
	"github.com/bounoable/postdog/transport/gmail"
	"github.com/bounoable/postdog/transport/smtp"
)

func main() {
	po := postdog.New()

	smtpTransport := smtp.NewTransport("smtp.mailtrap.io", 587, "abcdef123456", "123456abcdef")

	gmailTransport, err := gmail.NewTransport(
		context.Background(),
		gmail.CredentialsFile("/path/to/service_account.json"),
	)
	if err != nil {
		panic(err)
	}

	po.ConfigureTransport("mytransport1", smtpTransport)
	po.ConfigureTransport("mytransport2", gmailTransport, postdog.DefaultTransport()) // Make "mytransport2" the default

	err = po.Send(
		context.Background(),
		letter.Write(
			letter.From("Bob", "bob@belcher.test"),
			// ...
		),
	)
}
Output:

Example (Middleware)
package main

import (
	"context"
	"strings"

	"github.com/bounoable/postdog"
	"github.com/bounoable/postdog/letter"
)

func main() {
	off := postdog.New(
		postdog.WithMiddleware(
			postdog.MiddlewareFunc(func(
				ctx context.Context,
				let letter.Letter,
				next func(context.Context, letter.Letter) (letter.Letter, error),
			) (letter.Letter, error) {
				let.Subject = strings.Title(let.Subject)
				return next(ctx, let)
			}),
		),
	)

	if err := off.Send(context.Background(), letter.Write(
		letter.Subject("this is a title"), // will be set to "This Is A Title" by the middleware before sending
	)); err != nil {
		panic(err)
	}
}
Output:

Example (UseTransportDirectly)
package main

import (
	"context"

	"github.com/bounoable/postdog/letter"
	"github.com/bounoable/postdog/transport/gmail"
)

func main() {
	trans, err := gmail.NewTransport(
		context.Background(),
		gmail.CredentialsFile("/path/to/service_account.json"),
	)
	if err != nil {
		panic(err)
	}

	let := letter.Write(
		letter.From("Bob", "bob@belcher.test"),
		// ...
	)

	if err := trans.Send(context.Background(), let); err != nil {
		panic(err)
	}
}
Output:

Index

Examples

Constants

View Source
const (
	// BeforeSend hook is called before a letter is sent.
	BeforeSend = SendHook(iota)
	// AfterSend hook is called after a letter has been sent.
	AfterSend
)

Variables

View Source
var (
	// DefaultLogger is the default logger implementation.
	// It redirects all Log(v ...interface{}) calls to log.Println(v...)
	DefaultLogger defaultLogger

	// NopLogger is a nil logger and effectively a no-op logger.
	NopLogger Logger
)

Functions

func SendError

func SendError(ctx context.Context) error

SendError returns the send error from ctx. Returns nil, if ctx contains no send error.

Types

type Config

type Config struct {
	// Logger is an optional Logger implementation.
	Logger Logger
	// QueueBuffer is the channel buffer size for outgoing letters.
	QueueBuffer int
	// Middleware is the send middleware.
	Middleware []Middleware
	// SendHooks contains the callback functions for the send hooks.
	SendHooks map[SendHook][]func(context.Context, letter.Letter)
	// Plugins are the plugins to used.
	Plugins []Plugin
}

Config is the office configuration.

type ConfigureOption

type ConfigureOption func(*configureConfig)

ConfigureOption is a transport configuration option.

func DefaultTransport

func DefaultTransport() ConfigureOption

DefaultTransport makes a transport the default transport.

type DispatchOption

type DispatchOption func(*dispatchJob)

DispatchOption is a Dispatch() option.

func DispatchWith

func DispatchWith(transport string) DispatchOption

DispatchWith sets the transport to be used for sending the letter.

type Logger

type Logger interface {
	Log(v ...interface{})
}

Logger logs messages.

type Middleware

type Middleware interface {
	Handle(
		ctx context.Context,
		let letter.Letter,
		next func(context.Context, letter.Letter) (letter.Letter, error),
	) (letter.Letter, error)
}

Middleware intercepts and manipulates letters before they are sent. A Middleware that returns an error, aborts the sending of the letter with the returned error.

type MiddlewareFunc

type MiddlewareFunc func(
	ctx context.Context,
	let letter.Letter,
	next func(context.Context, letter.Letter) (letter.Letter, error),
) (letter.Letter, error)

MiddlewareFunc allows the use of functions as middlewares.

func (MiddlewareFunc) Handle

Handle intercepts and manipulates letters before they are sent.

type Office

type Office struct {
	// contains filtered or unexported fields
}

Office queues and dispatches outgoing letters. It is thread-safe (but the transports may not be).

func New

func New(opts ...Option) *Office

New initializes a new *Office with opts.

func NewWithConfig

func NewWithConfig(cfg Config) *Office

NewWithConfig initializes a new *Office with the given cfg.

func (*Office) Config

func (o *Office) Config() Config

Config returns the configration.

func (*Office) ConfigureTransport

func (o *Office) ConfigureTransport(name string, trans Transport, options ...ConfigureOption)

ConfigureTransport configures the transport with the given name. The first configured transport is the default transport, even if the Default() option is not used. Subsequent calls to ConfigureTransport() with the Default() option override the default transport.

func (*Office) DefaultTransport

func (o *Office) DefaultTransport() (Transport, error)

DefaultTransport returns the default transport. Returns an UnconfiguredTransportError if no transport has been configured.

func (*Office) Dispatch

func (o *Office) Dispatch(ctx context.Context, let letter.Letter, opts ...DispatchOption) error

Dispatch adds let to the send queue with the given opts. Dispatch returns an error only if ctx is canceled before let has been queued. Available options:

DispatchWith(): Set the name of the transport to use.

func (*Office) MakeDefault

func (o *Office) MakeDefault(name string) error

MakeDefault makes the transport with the given name the default transport.

func (*Office) Run

func (o *Office) Run(ctx context.Context, opts ...RunOption) error

Run processes the outgoing letter queue with the given options. Run blocks until ctx is canceled. Available options:

Workers(): Set the queue worker count.

func (*Office) Send

func (o *Office) Send(ctx context.Context, let letter.Letter) error

Send calls SendWith() with the default transport.

func (*Office) SendWith

func (o *Office) SendWith(ctx context.Context, transport string, let letter.Letter) error

SendWith sends a letter over the given transport. Returns an UnconfiguredTransportError, if the transport with the given name has not been registered. If a middleware returns an error, SendWith() will return that error. The BeforeSend hook is called after the middlewares, before Transport.Send(). The AfterSend hook is called after Transport.Send(), even if Transport.Send() returns an error. Hooks are called concurrently.

func (*Office) Transport

func (o *Office) Transport(name string) (Transport, error)

Transport returns the configured transport for the given name. Returns an UnconfiguredTransportError, if the transport with the given name has not been registered.

type Option

type Option func(*Config)

Option is an office option.

func QueueBuffer

func QueueBuffer(size int) Option

QueueBuffer sets the outgoing letter channel buffer size.

func WithLogger

func WithLogger(logger Logger) Option

WithLogger configures the office to use the given logger.

func WithMiddleware

func WithMiddleware(middleware ...Middleware) Option

WithMiddleware adds middleware to the office.

func WithPlugin

func WithPlugin(plugins ...Plugin) Option

WithPlugin installs the given office plugins.

func WithSendHook

func WithSendHook(h SendHook, fns ...func(context.Context, letter.Letter)) Option

WithSendHook registers the given fns to be called at the given SendHook h.

type Plugin

type Plugin interface {
	Install(PluginContext)
}

Plugin is an office plugin.

type PluginContext

type PluginContext interface {
	// Log logs messages.
	Log(...interface{})
	// WithSendHook adds callback functions for send hooks.
	WithSendHook(SendHook, ...func(context.Context, letter.Letter))
	// WithMiddleware adds send middleware.
	WithMiddleware(...Middleware)
}

PluginContext is the context for a plugin. It provides logging and configuration functions.

type PluginFunc

type PluginFunc func(PluginContext)

PluginFunc allows functions to be used as plugins.

func (PluginFunc) Install

func (fn PluginFunc) Install(ctx PluginContext)

Install installs an office plugin.

type RunOption

type RunOption func(*runConfig)

RunOption is a Run() option.

func Workers

func Workers(workers int) RunOption

Workers sets the worker count for the send queue.

type SendHook

type SendHook int

SendHook is a hook point for sending messages.

type Transport

type Transport interface {
	Send(context.Context, letter.Letter) error
}

Transport sends letters to the recipients.

type UnconfiguredTransportError

type UnconfiguredTransportError struct {
	Name string
}

UnconfiguredTransportError means a transport has not been registered.

func (UnconfiguredTransportError) Error

func (err UnconfiguredTransportError) Error() string

Directories

Path Synopsis
Package autowire provides office initialization through a YAML config.
Package autowire provides office initialization through a YAML config.
Package mock_postdog is a generated GoMock package.
Package mock_postdog is a generated GoMock package.
plugin
markdown
Package markdown provides Markdown support for letters.
Package markdown provides Markdown support for letters.
markdown/goldmark
Package goldmark provides an adapter for the goldmark Markdown parser.
Package goldmark provides an adapter for the goldmark Markdown parser.
markdown/mock_markdown
Package mock_markdown is a generated GoMock package.
Package mock_markdown is a generated GoMock package.
store
Package store provides a storage plugin for sent letters.
Package store provides a storage plugin for sent letters.
store/mock_store
Package mock_store is a generated GoMock package.
Package mock_store is a generated GoMock package.
store/mongostore
Package mongostore provides the mongodb store implementation.
Package mongostore provides the mongodb store implementation.
store/query
Package query provides functions to query letters from a store.
Package query provides functions to query letters from a store.
store/query/mock_query
Package mock_query is a generated GoMock package.
Package mock_query is a generated GoMock package.
store/storetest
Package storetest provides testing utilities that can be used to test store implementations.
Package storetest provides testing utilities that can be used to test store implementations.
template
Package template provides template support for letter bodies.
Package template provides template support for letter bodies.
transport
gmail
Package gmail provides the transport implementation for gmail.
Package gmail provides the transport implementation for gmail.
smtp
Package smtp provides the transport implementation for SMTP.
Package smtp provides the transport implementation for SMTP.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL