Skip to content
plexusonePublic

About

A vendor-free Go core for transactional email: verification links, security notices, receipts and other mail an application sends as itself.

Resources

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

OmniMail

Go CI Go Lint Go SAST Coverage Docs Docs License

A vendor-free Go core for transactional email: verification links, security notices, receipts and other mail an application sends as itself.

OmniMail defines one Sender interface, a validated message model, MIME assembly, templates and classified errors. Applications depend only on the core; the deployment chooses the delivery provider. Vendor SDKs live in separate adapter modules, so the core has no dependencies outside the Go standard library.

OmniMail is not a mailbox or chat client. For conversational messaging authenticated as a user, see OmniChat.

Features

  • One interface - Sender.Send(ctx, *Message) (*SendResult, error) for every provider
  • Safe by default - address validation via net/mail, internationalized addresses, header-injection protection (CR/LF rejected in every header value)
  • MIME assembly - RFC 5322 multipart/alternative, quoted-printable/base64 bodies, RFC 2047 headers, Date and Message-ID; Bcc never written
  • Templates - subject/text/HTML rendering with html/template autoescaping, from strings or an fs.FS (embed.FS)
  • Classified errors - InvalidMessage, InvalidAddress, Rejected, Throttled, Transient, Auth with Retryable()
  • Built-in senders - SMTP (STARTTLS / implicit TLS, PLAIN/LOGIN), log (bodies redacted), memory (tests)
  • Conformance suite - providertest keeps every adapter behaving the same

Providers

Provider Module / Package Status
SMTP github.com/plexusone/omnimail/smtp Available
Log (development) github.com/plexusone/omnimail/logsender Available
Memory (tests) github.com/plexusone/omnimail/memsender Available
AWS SES v2 github.com/plexusone/omni-aws/omnimail Planned
SendGrid github.com/plexusone/omni-twilio/omnimail Planned
Gmail API github.com/plexusone/omni-google/omnimail Planned

Installation

go get github.com/plexusone/omnimail

Quick Start

package main

import (
    "context"
    "log"
    "os"

    "github.com/plexusone/omnimail"
    "github.com/plexusone/omnimail/smtp"
)

func main() {
    sender, err := smtp.New(smtp.Config{
        Host:     os.Getenv("SMTP_HOST"),
        Username: os.Getenv("SMTP_USERNAME"),
        Password: os.Getenv("SMTP_PASSWORD"),
    })
    if err != nil {
        log.Fatal(err)
    }

    msg := &omnimail.Message{
        From:    omnimail.Address{Name: "Example", Email: "no-reply@example.com"},
        To:      []omnimail.Address{{Email: "user@example.org"}},
        Subject: "Verify your email",
        Text:    "Open this link to verify your address: https://example.com/verify?t=...",
        HTML:    `<p><a href="https://example.com/verify?t=...">Verify your address</a></p>`,
        Tags:    map[string]string{"purpose": "verify_email"},
    }

    res, err := sender.Send(context.Background(), msg)
    if err != nil {
        if omnimail.IsRetryable(err) {
            log.Printf("temporary failure, retry later: %v", err)
            return
        }
        log.Fatal(err)
    }
    log.Printf("sent %s via %s", res.MessageID, res.Provider)
}

Templates

//go:embed mail/*.tmpl
var mailFS embed.FS

tmpl, err := omnimail.ParseTemplateFS(mailFS, "mail/verify") // verify.subject.tmpl, verify.text.tmpl, verify.html.tmpl
if err != nil {
    return err
}
if err := tmpl.Apply(msg, map[string]any{"Name": user.Name, "Link": link}); err != nil {
    return err
}

Errors

_, err := sender.Send(ctx, msg)
switch {
case errors.Is(err, omnimail.ErrInvalidMessage): // fix the message; never retry
case errors.Is(err, omnimail.ErrThrottled):      // back off: omnimail.RetryAfter(err)
case omnimail.IsRetryable(err):                  // transient: retry with backoff
case errors.Is(err, omnimail.ErrAuth):           // credentials or permissions
}

Testing

s := memsender.New()
app := NewApp(s)
// ... exercise the app ...
if s.Len() != 1 || !strings.Contains(s.Last().Text, "/verify?") {
    t.Fatal("verification email not sent")
}

Writing a Provider Adapter

Adapters implement omnimail.Sender in their own module and run the conformance suite against a fake endpoint:

func TestConformance(t *testing.T) {
    providertest.RunAll(t, providertest.Config{
        Harness:      newFakeEndpointHarness(t),
        Provider:     "ses",
        SupportsTags: true,
    })
}

See the adapter guide and conformance suite.

Documentation

License

MIT License - see LICENSE for details.

About

A vendor-free Go core for transactional email: verification links, security notices, receipts and other mail an application sends as itself.

Resources

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Releases

Contributors

Languages