email

package module
v1.5.1 Latest Latest
Warning

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

Go to latest
Published: Jul 9, 2026 License: MIT Imports: 27 Imported by: 0

README

go-email

Go Version License GoDoc Go Report Card

A simple, provider-agnostic Go package for sending emails through Outlook 365 and Gmail.

🚀 Features

  • Simple, intuitive API - Send emails with just a few lines of code
  • Multiple Providers - Support for Outlook 365 (Microsoft Graph) and Gmail (Gmail API)
  • Read & manage mailboxes - List, read, search, move, label, flag, delete, and download attachments (v1.1.0+)
  • Calendar - List, read, create, update, and delete Outlook calendar events (v1.3.0+, Outlook only)
  • Rich Email Features - HTML content, attachments, CC/BCC recipients
  • Secure Authentication - OAuth2 authentication for both providers
  • Environment Configuration - Easy setup via environment variables
  • Zero External Dependencies - Only provider SDKs required
  • Context Support - Full context.Context support for timeouts and cancellation
  • Well Tested - Comprehensive test coverage

📦 Installation

go get github.com/go-email/go-email@v1.0.0

🏃 Quick Start

Outlook 365
package main

import (
    "log"
    "github.com/go-email/go-email"
)

func main() {
    config := &email.Config{
        Provider: "outlook365",
        Outlook: &email.OutlookConfig{
            TenantID:     "your-tenant-id",
            ClientID:     "your-client-id",
            ClientSecret: "your-client-secret",
        },
    }

    client, err := email.NewClient(config)
    if err != nil {
        log.Fatal(err)
    }

    msg := &email.Message{
        From:    "sender@company.com",
        To:      []string{"recipient@example.com"},
        Subject: "Hello from go-email",
        Body:    "This is a test email!",
    }

    if err := client.Send(msg); err != nil {
        log.Fatal(err)
    }
    
    log.Println("Email sent successfully!")
}
Gmail
package main

import (
    "log"
    "os"
    "github.com/go-email/go-email"
)

func main() {
    // Read credentials and token
    creds, _ := os.ReadFile("credentials.json")
    token, _ := os.ReadFile("token.json")

    config := &email.Config{
        Provider: "gmail",
        Gmail: &email.GmailConfig{
            CredentialsJSON: creds,
            TokenJSON:       token,
        },
    }

    client, err := email.NewClient(config)
    if err != nil {
        log.Fatal(err)
    }

    msg := &email.Message{
        From:    "sender@gmail.com",
        To:      []string{"recipient@example.com"},
        Subject: "Hello from go-email",
        Body:    "This is a test email!",
    }

    if err := client.Send(msg); err != nil {
        log.Fatal(err)
    }
    
    log.Println("Email sent successfully!")
}

📚 Documentation

🔧 Configuration

Environment Variables

Configure the package using environment variables:

# Provider selection
EMAIL_PROVIDER=outlook365  # or "gmail"

# Outlook 365
OUTLOOK_TENANT_ID=your-tenant-id
OUTLOOK_CLIENT_ID=your-client-id
OUTLOOK_CLIENT_SECRET=your-client-secret

# Gmail
GMAIL_CREDENTIALS_FILE=path/to/credentials.json
GMAIL_TOKEN_FILE=path/to/token.json

Then use the simplified client creation:

client, err := email.QuickClientFromEnv()
Provider Setup
Outlook 365 Setup
  1. Register an application in Azure Portal
  2. Grant Mail.Send permission
  3. Create a client secret
  4. Use the tenant ID, client ID, and client secret in your configuration

See the Outlook Setup Guide for detailed instructions.

Gmail Setup
  1. Create a project in Google Cloud Console
  2. Enable Gmail API
  3. Create OAuth2 credentials (Desktop application type)
  4. Download credentials.json
  5. Run the authentication to get your token

See the Gmail Setup Guide for detailed instructions.

📧 Advanced Usage

HTML Email with Attachments
// Read file content
content, _ := os.ReadFile("document.pdf")

msg := &email.Message{
    From:    "sender@company.com",
    To:      []string{"recipient@example.com"},
    Cc:      []string{"cc@example.com"},
    Bcc:     []string{"bcc@example.com"},
    Subject: "Monthly Report",
    Body:    `
        <h1>Monthly Report</h1>
        <p>Please find the attached report for this month.</p>
        <p>Best regards,<br>Your Team</p>
    `,
    HTML:    true,
    Attachments: []email.Attachment{
        {
            Filename: "report.pdf",
            Content:  content,
            MimeType: "application/pdf",
        },
    },
}

err := client.Send(msg)
Context with Timeout
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()

err := client.SendWithContext(ctx, msg)
Error Handling
err := client.Send(msg)
if err != nil {
    switch {
    case strings.Contains(err.Error(), "authentication"):
        // Handle auth errors - check credentials
        log.Printf("Authentication failed: %v", err)
    case strings.Contains(err.Error(), "rate limit"):
        // Handle rate limiting - implement backoff
        log.Printf("Rate limited, retry later: %v", err)
    case strings.Contains(err.Error(), "invalid recipient"):
        // Handle validation errors
        log.Printf("Invalid recipient address: %v", err)
    default:
        // Handle other errors
        log.Printf("Failed to send email: %v", err)
    }
}

📥 Reading & Managing Mail (v1.1.0+)

Beyond sending, the same Client can list, read, search, move, label, flag, delete, and download attachments. These operations are additive — send-only code is unaffected. Both Outlook 365 and Gmail implement them.

client, _ := email.NewClient(&email.Config{
    Provider: "outlook365",
    Outlook: &email.OutlookConfig{
        TenantID: "...", ClientID: "...", ClientSecret: "...",
        UserID:   "info@deltalegal.com.au", // mailbox to read (required for read ops)
    },
})

// List the 20 most recent unread messages in the inbox.
msgs, _ := client.List(email.ListOptions{UnreadOnly: true, Limit: 20})
for _, m := range msgs {
    fmt.Printf("%s  %s  (%s)\n", m.Received.Format("2006-01-02"), m.Subject, m.From)
}

// Read one message's body.
full, _ := client.Read(msgs[0].ID)
fmt.Println(full.BodyText)

// Provider-native search (Graph $search KQL / Gmail operators).
hits, _ := client.Search(`subject:invoice hasAttachments:true`, email.ListOptions{Limit: 50})

// Download attachments, move to a folder, mark read, categorise.
client.SaveAttachments(msgs[0].ID, "/path/to/matter")
client.Move(msgs[0].ID, "archive")          // Outlook folder name / Gmail label
client.MarkRead(msgs[0].ID, true)
client.SetLabels(msgs[0].ID, []string{"WOO-402"})

Provider differences (handled for you):

Outlook 365 Gmail
Folders real folders (inbox, sentitems, archive, …) labels (Move = add label + remove INBOX)
SetLabels message categories labels (created on demand)
Delete(permanent: true) unsupported (use false → Deleted Items) requires gmail.MailGoogleComScope
Auth scope for reads app perm Mail.ReadWrite gmail.modify (re-consent required if token was gmail.send-only)

Gmail re-consent: v1.1.0 requests gmail.send + gmail.modify by default. A token previously minted for gmail.send alone must be re-authorised (re-run the auth helper) or read/modify calls return 403. Override with GmailConfig.Scopes.

A provider that does not support these operations returns email.ErrUnsupported.

🏗️ Architecture

The package follows a clean architecture with provider abstraction:

email.Client
    ├── Provider Interface
    │   ├── OutlookProvider (Microsoft Graph API)
    │   └── GmailProvider (Gmail API)
    └── Message
        ├── Recipients (To, Cc, Bcc)
        ├── Content (Plain/HTML)
        └── Attachments

🧪 Testing

Run the test suite:

go test ./...

For integration tests with real email sending:

# Set up test credentials
export EMAIL_PROVIDER=gmail
export TEST_FROM_EMAIL=test@example.com
export TEST_TO_EMAIL=recipient@example.com

# Run integration tests
go test -tags=integration ./...

📊 Benchmarks

go test -bench=. ./...

🤝 Contributing

Contributions are welcome! Please feel free to submit a Pull Request. For major changes, please open an issue first to discuss what you would like to change.

  1. Fork the repository
  2. Create your feature branch (git checkout -b feature/amazing-feature)
  3. Commit your changes (git commit -m 'Add some amazing feature')
  4. Push to the branch (git push origin feature/amazing-feature)
  5. Open a Pull Request

See CONTRIBUTING.md for more details.

📄 License

This project is licensed under the MIT License - see the LICENSE file for details.

🙏 Acknowledgments

  • Microsoft Graph SDK for Go
  • Google Gmail API Client Library for Go
  • The Go community for excellent tools and libraries

📞 Support

🗺️ Roadmap

  • Add support for SendGrid provider
  • Add support for AWS SES provider
  • Add email template engine
  • Add webhook support for email events
  • Add batch sending optimization
  • Add email validation utilities

Made with ❤️ by the go-email team

Documentation

Overview

auth.go - Authentication helpers for OAuth2 providers

calendar.go - provider-agnostic calendar types and the Client wrappers that expose them. The Outlook implementation lives in outlook_calendar.go; Gmail does not implement CalendarProvider (the build-in gmailProvider returns ErrUnsupported via the type assertion in calendar()).

config.go - Configuration helpers for the email package

Package email provides a simple, provider-agnostic interface for sending emails through various providers including Outlook 365 and Gmail.

Basic usage:

config := &email.Config{
    Provider: "outlook365",
    Outlook: &email.OutlookConfig{
        TenantID:     "your-tenant-id",
        ClientID:     "your-client-id",
        ClientSecret: "your-client-secret",
    },
}

client, err := email.NewClient(config)
if err != nil {
    log.Fatal(err)
}

msg := &email.Message{
    From:    "sender@company.com",
    To:      []string{"recipient@example.com"},
    Subject: "Hello",
    Body:    "This is a test email!",
}

if err := client.Send(msg); err != nil {
    log.Fatal(err)
}

errors.go - Sentinel errors for the email package.

gmail.go - Gmail provider implementation using Gmail API

gmail_read.go - Gmail implementation of the MailboxProvider read/management operations. The send path lives in gmail.go; this file adds list/read/ search/move/attachments/flags/delete/folders.

Gmail differences baked in here, per the verified API reference:

  • No folders, only labels. "Folder" == label; Move == relabel.
  • Messages.List returns only {Id, ThreadId} stubs; each needs a Get.
  • Bodies and attachments are base64URL WITHOUT padding -> RawURLEncoding.
  • Modify/Trash/Labels need gmail.modify scope; permanent Delete needs the full mail.google.com scope.

mailbox.go - Read/manage operations (list, read, search, move, labels, attachments, delete, folders) layered additively on top of the send-only core. Existing send-only callers are unaffected: they simply never call these methods. Both the Outlook 365 and Gmail providers implement them.

Provider-portability note: Outlook has real folders; Gmail has only labels. The interface speaks a common vocabulary of folder/label NAMES (e.g. "Inbox", "Archive"); each provider maps names to its own identifiers. Gmail "Move" is implemented as a label change (add destination, remove INBOX). These quirks are documented per method rather than leaked into the type system.

outlook.go - Outlook 365 provider implementation using Microsoft Graph API

outlook_calendar.go - Outlook 365 (Microsoft Graph) implementation of the CalendarProvider interface. Mirrors the SDK idiom of outlook_read.go: the configured UserID is the mailbox, builder/config type names are verified against msgraph-sdk-go v1.59.0, and @odata.nextLink is followed for list.

Graph permission required: Calendars.ReadWrite (application) on the same Azure app dl/go-email already use. With only Mail.* the event calls 403.

outlook_read.go - Outlook 365 (Microsoft Graph) implementation of the MailboxProvider read/management operations. The send path lives in outlook.go; this file adds list/read/search/move/attachments/flags/delete/ folders. All builder and query-parameter type names here were verified against the generated msgraph-sdk-go source (stable across v1.59.0 the standalone pin and the workspace's newer pin).

outlook_retry.go - Idempotency-aware retry policy for the Microsoft Graph client (bd dl-jbb).

WHY THIS EXISTS --------------- The kiota default RetryHandler retries HTTP 429/503/504 for ANY method, including a POST, as long as the request body has a known Content-Length (which a sendMail POST always does). A 504 Gateway Timeout is AMBIGUOUS: Graph may already have accepted and queued the message before the gateway timed out. Retrying a sendMail POST on a 504 therefore risks DELIVERING A DUPLICATE email with no error surfaced to the caller. See the audit at outlook-tools/owner-inbox/gus-go-email-retry-audit-2026-07-03.md.

This file installs a RetryHandler whose ShouldRetry mirrors the hardened classification in outlook-tools' Python graph_client.py:

  • 429 / 503 -> retried for ANY method (server signalled "not processed", so a retry cannot duplicate a side effect).
  • 500 / 502 / 504 -> AMBIGUOUS; retried ONLY for idempotent methods (GET/HEAD/OPTIONS). Never retried for POST/PATCH/PUT/ DELETE — this closes the sendMail double-send hole and equally protects calendar create/update/delete and message move (all POST/PATCH/DELETE on the shared client).
  • everything else -> handled by the default retriable-status set (isRetriableErrorCode already limits retries to 429/503/504, so 500/502 are not retried at all by the handler; our predicate only ever narrows, never widens).

The whole Outlook surface (mail send, mail read, calendar, move) shares one *GraphServiceClient built in newOutlookProvider, so wiring the policy at the client-construction seam covers every Graph call in one place.

sanitize.go - Filesystem-safety helpers shared by the provider .eml/raw-MIME filing paths. Pure functions, no network, no provider knowledge: they turn a message subject into a single safe path element with a guaranteed ".eml" suffix, so the consumer (e.g. dl) can pass a raw subject and the library owns "make this a safe .eml name". Sits beside writeUniqueAttachment in spirit; both providers reuse it.

Package email version information

Index

Constants

View Source
const (
	ProviderOutlook365 = "outlook365"
	ProviderGmail      = "gmail"
)

Provider name constants, used as the Config.Provider value and the EMAIL_PROVIDER env var.

View Source
const (
	// Version is the current version of the go-email package
	Version = "v1.5.1"

	// VersionMajor is the major version number
	VersionMajor = 1

	// VersionMinor is the minor version number
	VersionMinor = 5

	// VersionPatch is the patch version number
	VersionPatch = 1

	// VersionPreRelease is the pre-release version identifier
	VersionPreRelease = ""

	// BuildDate is the date the binary was built (set during build)
	BuildDate = ""

	// GitCommit is the git commit hash (set during build)
	GitCommit = ""
)

Version information

View Source
const MaxDirectAttachmentBytes = 3 * 1024 * 1024

MaxDirectAttachmentBytes is the ceiling for a single DIRECT attachment upload (a whole-content POST to /messages/{id}/attachments). Microsoft Graph accepts a fileAttachment posted inline this way only up to 3 MB; a larger file requires a createUploadSession chunked upload, which AddAttachment does NOT implement. We reject over-size content up front with a clear error rather than let Graph fail mid-upload with a less actionable 413/400.

Variables

View Source
var (
	// ErrUnsupported is returned when a configured provider does not implement
	// the requested mailbox operation (i.e. it is not a MailboxProvider).
	ErrUnsupported = errors.New("operation not supported by provider")

	// ErrNotFound is returned when a referenced message, folder, or label does
	// not exist.
	ErrNotFound = errors.New("not found")
)

Functions

func AuthenticateGmailFromFile

func AuthenticateGmailFromFile(credentialsFile string) ([]byte, error)

AuthenticateGmailFromFile is a convenience function that reads credentials from a file and performs the OAuth2 authentication flow.

This is useful for one-time authentication setup.

Example:

token, err := email.AuthenticateGmailFromFile("credentials.json")
if err != nil {
    log.Fatal(err)
}

// Save the token for future use
err = os.WriteFile("token.json", token, 0600)
if err != nil {
    log.Fatal(err)
}

func GetVersion

func GetVersion() string

GetVersion returns the full version string

func QuickSend

func QuickSend(provider string, creds interface{}, from, to, subject, body string) error

QuickSend provides a simple way to send an email with minimal configuration. This is useful for simple use cases where you don't need to reuse the client.

Example:

err := email.QuickSend("gmail",
    &email.GmailConfig{
        CredentialsJSON: creds,
        TokenJSON:       token,
    },
    "from@example.com",
    "to@example.com",
    "Subject",
    "Body")

Types

type Attachment

type Attachment struct {
	// Filename is the name of the file as it will appear in the email
	Filename string

	// Content is the file content as bytes
	Content []byte

	// MimeType is the MIME type of the file (optional).
	// If empty, it will be automatically detected based on the filename.
	MimeType string
}

Attachment represents a file attachment for an email.

type AttachmentMeta added in v1.1.0

type AttachmentMeta struct {
	// ID is the provider attachment identifier (used to fetch content).
	ID string

	// Filename is the attachment's file name.
	Filename string

	// MimeType is the attachment's content type.
	MimeType string

	// Size is the attachment size in bytes (0 if unknown).
	Size int64
}

AttachmentMeta describes an attachment without downloading its content.

type CalendarProvider added in v1.3.0

type CalendarProvider interface {
	// ListEvents returns events in the option's date range, soonest first.
	ListEvents(ctx context.Context, opts EventListOptions) ([]Event, error)

	// ReadEvent returns one event by id, including body and attendees.
	ReadEvent(ctx context.Context, id string) (*Event, error)

	// CreateEvent creates an event and returns it with its assigned id.
	CreateEvent(ctx context.Context, e Event) (*Event, error)

	// UpdateEvent applies the non-zero fields of e to the event with the given
	// id and returns the updated event. Categories, when non-nil, REPLACE the
	// event's category list wholesale (Graph PATCH semantics).
	UpdateEvent(ctx context.Context, id string, e Event) (*Event, error)

	// DeleteEvent removes an event by id.
	DeleteEvent(ctx context.Context, id string) error
}

CalendarProvider is implemented by providers that support calendar operations (Outlook 365). All methods take a context and act on the mailbox the provider was configured for (OutlookConfig.UserID).

type Client

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

Client is the main email client that wraps a provider implementation. It is thread-safe and can be used concurrently.

func NewClient

func NewClient(config *Config) (*Client, error)

NewClient creates a new email client with the specified configuration. It returns an error if the configuration is invalid or the provider fails to initialize.

Example:

config := &email.Config{
    Provider: "gmail",
    Gmail: &email.GmailConfig{
        CredentialsJSON: credentialsJSON,
        TokenJSON:       tokenJSON,
    },
}

client, err := email.NewClient(config)

func QuickClientFromEnv

func QuickClientFromEnv() (*Client, error)

QuickClientFromEnv creates a client using environment variables. This combines ConfigFromEnv and NewClient for convenience.

Example:

client, err := email.QuickClientFromEnv()
if err != nil {
    log.Fatal(err)
}

err = client.Send(&email.Message{
    From:    "sender@example.com",
    To:      []string{"recipient@example.com"},
    Subject: "Test",
    Body:    "Hello!",
})

func (*Client) AddAttachment added in v1.5.0

func (c *Client) AddAttachment(id string, att Attachment) error

AddAttachment adds a single file attachment to an existing message (e.g. a draft), with a default timeout. See MailboxProvider.AddAttachment.

func (*Client) AddAttachmentWithContext added in v1.5.0

func (c *Client) AddAttachmentWithContext(ctx context.Context, id string, att Attachment) error

AddAttachmentWithContext is AddAttachment with a caller-supplied context.

func (*Client) CreateEvent added in v1.3.0

func (c *Client) CreateEvent(e Event) (*Event, error)

CreateEvent creates a calendar event (context.Background).

func (*Client) CreateEventWithContext added in v1.3.0

func (c *Client) CreateEventWithContext(ctx context.Context, e Event) (*Event, error)

CreateEventWithContext creates a calendar event.

func (*Client) CreateMasterCategory added in v1.5.0

func (c *Client) CreateMasterCategory(name, color string) error

CreateMasterCategory adds a category to the mailbox's master list, with a default timeout. See MailboxProvider.CreateMasterCategory.

func (*Client) CreateMasterCategoryWithContext added in v1.5.0

func (c *Client) CreateMasterCategoryWithContext(ctx context.Context, name, color string) error

CreateMasterCategoryWithContext is CreateMasterCategory with a caller-supplied context.

func (*Client) CreateReplyDraft added in v1.4.0

func (c *Client) CreateReplyDraft(id, htmlBody string, replyAll bool) (*DraftInfo, error)

CreateReplyDraft creates a draft reply to a message and returns the draft's id and web link, with a default timeout. See MailboxProvider.CreateReplyDraft.

func (*Client) CreateReplyDraftWithContext added in v1.4.0

func (c *Client) CreateReplyDraftWithContext(ctx context.Context, id, htmlBody string, replyAll bool) (*DraftInfo, error)

CreateReplyDraftWithContext is CreateReplyDraft with a caller-supplied context.

func (*Client) Delete added in v1.1.0

func (c *Client) Delete(id string, permanent bool) error

Delete removes a message (trash if permanent is false), with a default timeout.

func (*Client) DeleteEvent added in v1.3.0

func (c *Client) DeleteEvent(id string) error

DeleteEvent deletes a calendar event (context.Background).

func (*Client) DeleteEventWithContext added in v1.3.0

func (c *Client) DeleteEventWithContext(ctx context.Context, id string) error

DeleteEventWithContext deletes a calendar event.

func (*Client) DeleteWithContext added in v1.2.0

func (c *Client) DeleteWithContext(ctx context.Context, id string, permanent bool) error

DeleteWithContext is Delete with a caller-supplied context.

func (*Client) List added in v1.1.0

func (c *Client) List(opts ListOptions) ([]Summary, error)

List returns message headers from a folder (default inbox), newest first, with a default timeout.

func (*Client) ListAttachments added in v1.1.0

func (c *Client) ListAttachments(id string) ([]AttachmentMeta, error)

ListAttachments returns metadata for a message's file attachments, with a default timeout.

func (*Client) ListAttachmentsWithContext added in v1.2.0

func (c *Client) ListAttachmentsWithContext(ctx context.Context, id string) ([]AttachmentMeta, error)

ListAttachmentsWithContext is ListAttachments with a caller-supplied context.

func (*Client) ListEvents added in v1.3.0

func (c *Client) ListEvents(opts EventListOptions) ([]Event, error)

ListEvents lists calendar events in the given range (context.Background).

func (*Client) ListEventsWithContext added in v1.3.0

func (c *Client) ListEventsWithContext(ctx context.Context, opts EventListOptions) ([]Event, error)

ListEventsWithContext lists calendar events in the given range.

func (*Client) ListFolders added in v1.1.0

func (c *Client) ListFolders() ([]Folder, error)

ListFolders returns the mailbox's folders (Outlook) or labels (Gmail), with a default timeout.

func (*Client) ListFoldersWithContext added in v1.2.0

func (c *Client) ListFoldersWithContext(ctx context.Context) ([]Folder, error)

ListFoldersWithContext is ListFolders with a caller-supplied context.

func (*Client) ListMasterCategories added in v1.5.0

func (c *Client) ListMasterCategories() ([]MasterCategory, error)

ListMasterCategories returns the mailbox's master category list, with a default timeout. See MailboxProvider.ListMasterCategories.

func (*Client) ListMasterCategoriesWithContext added in v1.5.0

func (c *Client) ListMasterCategoriesWithContext(ctx context.Context) ([]MasterCategory, error)

ListMasterCategoriesWithContext is ListMasterCategories with a caller-supplied context.

func (*Client) ListWithContext added in v1.1.0

func (c *Client) ListWithContext(ctx context.Context, opts ListOptions) ([]Summary, error)

ListWithContext is List with a caller-supplied context.

func (*Client) MarkRead added in v1.1.0

func (c *Client) MarkRead(id string, read bool) error

MarkRead sets a message's read state, with a default timeout.

func (*Client) MarkReadWithContext added in v1.2.0

func (c *Client) MarkReadWithContext(ctx context.Context, id string, read bool) error

MarkReadWithContext is MarkRead with a caller-supplied context.

func (*Client) Move added in v1.1.0

func (c *Client) Move(id, dest string) error

Move relocates a message to the destination folder/label, with a default timeout. See MailboxProvider.Move for Gmail's archive-style semantics.

func (*Client) MoveWithContext added in v1.1.0

func (c *Client) MoveWithContext(ctx context.Context, id, dest string) error

MoveWithContext is Move with a caller-supplied context.

func (*Client) Read added in v1.1.0

func (c *Client) Read(id string) (*FullMessage, error)

Read returns one message including its body, with a default timeout.

func (*Client) ReadEvent added in v1.3.0

func (c *Client) ReadEvent(id string) (*Event, error)

ReadEvent reads one event by id (context.Background).

func (*Client) ReadEventWithContext added in v1.3.0

func (c *Client) ReadEventWithContext(ctx context.Context, id string) (*Event, error)

ReadEventWithContext reads one event by id.

func (*Client) ReadWithContext added in v1.1.0

func (c *Client) ReadWithContext(ctx context.Context, id string) (*FullMessage, error)

ReadWithContext is Read with a caller-supplied context.

func (*Client) SaveAttachments added in v1.1.0

func (c *Client) SaveAttachments(id, destDir string) ([]string, error)

SaveAttachments writes a message's file attachments into destDir, with a default timeout, and returns the paths written.

func (*Client) SaveAttachmentsWithContext added in v1.1.0

func (c *Client) SaveAttachmentsWithContext(ctx context.Context, id, destDir string) ([]string, error)

SaveAttachmentsWithContext is SaveAttachments with a caller-supplied context.

func (*Client) SaveMessageRaw added in v1.3.2

func (c *Client) SaveMessageRaw(id, destDir, baseName string) (string, error)

SaveMessageRaw writes a message's raw RFC822 MIME (.eml) into destDir under a collision-free name derived from baseName, with a default timeout, and returns the path written. See MailboxProvider.SaveMessageRaw.

func (*Client) SaveMessageRawWithContext added in v1.3.2

func (c *Client) SaveMessageRawWithContext(ctx context.Context, id, destDir, baseName string) (string, error)

SaveMessageRawWithContext is SaveMessageRaw with a caller-supplied context.

func (*Client) Search added in v1.1.0

func (c *Client) Search(query string, opts ListOptions) ([]Summary, error)

Search runs a provider-native full-text search, with a default timeout.

func (*Client) SearchWithContext added in v1.1.0

func (c *Client) SearchWithContext(ctx context.Context, query string, opts ListOptions) ([]Summary, error)

SearchWithContext is Search with a caller-supplied context.

func (*Client) Send

func (c *Client) Send(msg *Message) error

Send sends an email message with a default timeout of 30 seconds. It validates the message before sending and returns an error if validation fails or the send operation fails.

func (*Client) SendWithContext

func (c *Client) SendWithContext(ctx context.Context, msg *Message) error

SendWithContext sends an email message with a custom context. This allows for custom timeouts, cancellation, and passing request-scoped values. The message is validated before sending.

Example:

ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
defer cancel()

err := client.SendWithContext(ctx, msg)

func (*Client) SetLabels added in v1.1.0

func (c *Client) SetLabels(id string, labels []string) error

SetLabels replaces a message's labels/categories, with a default timeout.

func (*Client) SetLabelsWithContext added in v1.2.0

func (c *Client) SetLabelsWithContext(ctx context.Context, id string, labels []string) error

SetLabelsWithContext is SetLabels with a caller-supplied context.

func (*Client) UpdateEvent added in v1.3.0

func (c *Client) UpdateEvent(id string, e Event) (*Event, error)

UpdateEvent updates a calendar event (context.Background).

func (*Client) UpdateEventWithContext added in v1.3.0

func (c *Client) UpdateEventWithContext(ctx context.Context, id string, e Event) (*Event, error)

UpdateEventWithContext updates a calendar event.

type Config

type Config struct {
	// Provider specifies which email provider to use.
	// Supported values: "outlook365", "gmail"
	Provider string

	// Outlook contains Outlook 365 specific configuration.
	// Required when Provider is "outlook365".
	Outlook *OutlookConfig

	// Gmail contains Gmail specific configuration.
	// Required when Provider is "gmail".
	Gmail *GmailConfig

	// Custom is reserved for future provider extensions
	Custom map[string]interface{}
}

Config holds the configuration for creating email providers. Only one provider configuration should be set.

func ConfigFromEnv

func ConfigFromEnv() (*Config, error)

ConfigFromEnv creates an email configuration from environment variables. This is a convenient way to configure the email client without hardcoding credentials.

Environment variables:

  • EMAIL_PROVIDER: The email provider to use ("outlook365" or "gmail"), defaults to "outlook365"
  • For Outlook 365:
  • OUTLOOK_TENANT_ID: Azure AD tenant ID (required)
  • OUTLOOK_CLIENT_ID: Azure AD application client ID (required)
  • OUTLOOK_CLIENT_SECRET: Azure AD application client secret (required)
  • For Gmail:
  • GMAIL_CREDENTIALS_FILE: Path to the OAuth2 credentials JSON file (required)
  • GMAIL_TOKEN_FILE: Path to the OAuth2 token JSON file (defaults to "token.json")

Example:

os.Setenv("EMAIL_PROVIDER", "gmail")
os.Setenv("GMAIL_CREDENTIALS_FILE", "credentials.json")
os.Setenv("GMAIL_TOKEN_FILE", "token.json")

config, err := email.ConfigFromEnv()
if err != nil {
    log.Fatal(err)
}

type DraftInfo added in v1.4.0

type DraftInfo struct {
	// ID is the provider message id of the created draft (Graph message id for
	// Outlook). Treat it as opaque; it can be passed to Read/Delete/Move.
	ID string

	// WebLink is the Outlook-on-the-web URL to open the draft, if the provider
	// returns one (empty otherwise).
	WebLink string
}

DraftInfo identifies a draft message created by CreateReplyDraft, with enough for a caller to report it or open it. The draft lives in the mailbox's Drafts folder until sent; go-email does not send it.

type Event added in v1.3.0

type Event struct {
	// ID is the provider event identifier. Treat it as opaque. Empty when an
	// Event is passed to Create (the provider assigns it).
	ID string

	// Subject is the event title.
	Subject string

	// Start, End are the event's wall-clock start/end in TimeZone.
	Start time.Time
	End   time.Time

	// TimeZone is the IANA zone the Start/End are expressed in (e.g.
	// "Australia/Perth"). Empty on input means the provider's default zone.
	TimeZone string

	// AllDay reports an all-day event (Start/End are dates).
	AllDay bool

	// Location is the free-text venue, if any.
	Location string

	// BodyText is the plain-text event description.
	BodyText string

	// Organizer is the organiser's email address (output only).
	Organizer string

	// Attendees holds attendee email addresses.
	Attendees []string

	// Categories holds the event's Outlook category tags.
	Categories []string
}

Event is a calendar event. Times are wall-clock values paired with TimeZone (an IANA name, e.g. "Australia/Perth"); for an all-day event the Start/End carry the date with a zero time-of-day and AllDay is true.

type EventListOptions added in v1.3.0

type EventListOptions struct {
	// Start, End bound the query (inclusive of events overlapping the range).
	// Zero Start means "now"; zero End means "no upper bound" for the
	// upcoming-events list.
	Start time.Time
	End   time.Time

	// Limit caps the number of events returned (0 = provider default).
	Limit int
}

EventListOptions bounds a calendar list query to a date range.

type Folder added in v1.1.0

type Folder struct {
	// ID is the provider identifier, suitable for ListOptions.Folder and Move.
	ID string

	// Name is the human-readable display name.
	Name string

	// Unread is the count of unread items, where the provider reports it
	// (Outlook); 0 if unknown.
	Unread int
}

Folder is a mail folder (Outlook) or label (Gmail).

type FullMessage added in v1.1.0

type FullMessage struct {
	Summary

	// To, Cc are the recipient addresses.
	To []string
	Cc []string

	// BodyText is the plain-text body, if available.
	BodyText string

	// BodyHTML is the HTML body, if the message was HTML.
	BodyHTML string
}

FullMessage is a message with its body, returned by Read.

type GmailAuthHelper

type GmailAuthHelper struct {
	// CredentialsJSON contains the OAuth2 client credentials from Google Cloud Console
	CredentialsJSON []byte

	// Scopes overrides the OAuth2 scopes requested during the consent flow.
	// If empty, the helper requests gmail.send + gmail.modify so the resulting
	// token works for both sending and the MailboxProvider read/move/label
	// operations. Set this (e.g. add gmail.MailGoogleComScope) before calling
	// Authenticate to widen or narrow the grant.
	Scopes []string
}

GmailAuthHelper provides utilities for Gmail OAuth2 authentication. It handles the OAuth2 flow for obtaining access tokens for Gmail API.

func NewGmailAuthHelper

func NewGmailAuthHelper(credentialsJSON []byte) *GmailAuthHelper

NewGmailAuthHelper creates a new Gmail authentication helper with the provided credentials.

The credentials should be the JSON file downloaded from Google Cloud Console when creating OAuth2 credentials for a desktop application.

Example:

creds, err := os.ReadFile("credentials.json")
if err != nil {
    log.Fatal(err)
}

helper := email.NewGmailAuthHelper(creds)

func (*GmailAuthHelper) Authenticate

func (g *GmailAuthHelper) Authenticate() ([]byte, error)

Authenticate performs the OAuth2 authentication flow and returns the access token as JSON. This method will prompt the user to visit a URL and enter an authorization code.

The returned token can be saved and reused for future email sending without requiring re-authentication.

Example:

helper := email.NewGmailAuthHelper(credentialsJSON)
token, err := helper.Authenticate()
if err != nil {
    log.Fatal(err)
}

// Save token for future use
err = os.WriteFile("token.json", token, 0600)

type GmailConfig

type GmailConfig struct {
	// CredentialsJSON contains the OAuth2 credentials downloaded from Google Cloud Console
	CredentialsJSON []byte

	// TokenJSON contains the stored OAuth2 token.
	// If not provided, authentication will be required on first use.
	TokenJSON []byte

	// Scopes overrides the OAuth2 scopes requested. If empty, the provider
	// requests gmail.send + gmail.modify, which covers sending plus the
	// MailboxProvider read/move/label/trash operations. Add
	// gmail.MailGoogleComScope (full access) here if you need permanent
	// deletion. Widening scopes requires re-running the consent flow and
	// replacing the stored token.
	Scopes []string
}

GmailConfig holds Gmail specific configuration for OAuth2 authentication.

type ListOptions added in v1.1.0

type ListOptions struct {
	// Folder is the folder/label to list. Empty means "inbox". For Outlook
	// this is a well-known folder name or folder id (e.g. "inbox",
	// "sentitems", "archive"); for Gmail it is a label name or system label
	// id (e.g. "INBOX", "SENT"). Ignored by Search.
	Folder string

	// UnreadOnly restricts results to unread messages.
	UnreadOnly bool

	// Since restricts results to messages received at or after this time.
	// The zero value means no lower bound.
	Since time.Time

	// Limit caps the number of messages returned (0 means provider default,
	// no explicit cap). Acts as a ceiling across pages.
	Limit int
}

ListOptions filters and bounds a List or Search call. The zero value lists the inbox with provider defaults.

type MailboxProvider added in v1.1.0

type MailboxProvider interface {
	Provider

	// List returns message headers from a folder per opts (default inbox),
	// newest first.
	List(ctx context.Context, opts ListOptions) ([]Summary, error)

	// Read returns one message including its body.
	Read(ctx context.Context, id string) (*FullMessage, error)

	// Search runs a provider-native full-text search. The query uses the
	// provider's own syntax (Graph $search KQL / Gmail search operators).
	// opts bounds the results (Folder is ignored).
	Search(ctx context.Context, query string, opts ListOptions) ([]Summary, error)

	// Move relocates a message to the destination folder/label. For Outlook
	// the message is moved; for Gmail the destination label is added and
	// INBOX removed (archive-style move). dest is a folder/label name or id.
	Move(ctx context.Context, id, dest string) error

	// ListAttachments returns metadata for a message's file attachments.
	ListAttachments(ctx context.Context, id string) ([]AttachmentMeta, error)

	// SaveAttachments writes a message's file attachments into destDir and
	// returns the paths written. destDir is created if it does not exist.
	SaveAttachments(ctx context.Context, id, destDir string) ([]string, error)

	// SaveMessageRaw writes the message's raw RFC822 MIME (.eml) into destDir
	// under a collision-free name derived from baseName (".eml" is appended if
	// absent; reserved/control chars in baseName are sanitized), and returns the
	// path written. destDir is created if it does not exist. The raw MIME is the
	// provider's verbatim wire form (Graph $value / Gmail raw), suitable for an
	// .eml->PDF converter. Providers that cannot export raw MIME return
	// ErrUnsupported.
	SaveMessageRaw(ctx context.Context, id, destDir, baseName string) (string, error)

	// CreateReplyDraft creates a DRAFT reply to the message identified by id and
	// returns the new draft's id and web link. It never sends. The server
	// (Graph createReply / createReplyAll) seeds the draft with the correct
	// recipients and the quoted original message, preserving thread context;
	// htmlBody is then PREPENDED to that quoted content as the reply text, so the
	// caller's content appears above the quoted original. htmlBody is always
	// treated as HTML (so formatted tables render); pass "" to create a draft
	// containing only the quoted original. If replyAll is true the draft replies
	// to all recipients (To + Cc of the original), preserving the CCs; otherwise
	// it replies to the sender only. Providers that cannot create draft replies
	// return ErrUnsupported.
	CreateReplyDraft(ctx context.Context, id, htmlBody string, replyAll bool) (*DraftInfo, error)

	// AddAttachment adds a single file attachment to an EXISTING message (typically
	// a draft, e.g. one from CreateReplyDraft) identified by id. att.Filename names
	// the file and att.Content carries its raw bytes; att.MimeType is used verbatim
	// when set, otherwise it is detected from the filename extension. This is a
	// DIRECT attachment upload (a single POST of the whole content), so it is
	// bounded to small files: content larger than MaxDirectAttachmentBytes is
	// rejected with an error rather than attempted, because Graph requires an upload
	// session for larger attachments (which is out of scope). It never sends the
	// message. Providers that cannot add attachments to a stored message return
	// ErrUnsupported.
	AddAttachment(ctx context.Context, id string, att Attachment) error

	// ListMasterCategories returns the mailbox's master category list — the named,
	// colored categories Outlook renders as chips (outlook/masterCategories). A
	// caller ensuring a category exists should fetch this once and match
	// case-insensitively (master categories are unique case-insensitively).
	// Providers without a master-category concept (Gmail) return ErrUnsupported.
	ListMasterCategories(ctx context.Context) ([]MasterCategory, error)

	// CreateMasterCategory adds a category to the mailbox's master list so Outlook
	// renders it in color. name is the display name; color is an Outlook preset
	// token ("preset0".."preset24"); an empty/invalid color falls back to a default
	// preset. The caller is responsible for the case-insensitive existence check —
	// creating a name that already exists (in any case) fails with a Graph 409.
	// Providers without a master-category concept (Gmail) return ErrUnsupported.
	CreateMasterCategory(ctx context.Context, name, color string) error

	// MarkRead sets a message's read state.
	MarkRead(ctx context.Context, id string, read bool) error

	// SetLabels replaces a message's labels (Gmail) or categories (Outlook)
	// with the given set. Names are used; for Gmail, missing user labels are
	// created on demand.
	SetLabels(ctx context.Context, id string, labels []string) error

	// Delete removes a message. If permanent is false the message is moved to
	// the trash/deleted-items and is recoverable; if true it is permanently
	// deleted where the provider supports it (Gmail requires the full-access
	// scope for permanent deletion).
	Delete(ctx context.Context, id string, permanent bool) error

	// ListFolders returns the mailbox's folders (Outlook) or labels (Gmail).
	ListFolders(ctx context.Context) ([]Folder, error)
}

MailboxProvider extends Provider with read and management operations. Both built-in providers (Outlook 365, Gmail) implement it. Code that only sends can continue to use Provider; code that needs the wider surface can type-assert a provider to MailboxProvider or use the Client methods below, which require it.

All methods take a context for timeout/cancellation. The mailbox operated on is the one the provider was configured for (Outlook: the address passed as the message From / the configured user; Gmail: the authenticated "me").

type MasterCategory added in v1.5.0

type MasterCategory struct {
	// DisplayName is the category name (e.g. "e-Filed"). Master categories are
	// unique case-insensitively within a mailbox.
	DisplayName string

	// Color is the Outlook preset color token ("preset0".."preset24", or "none").
	// preset0 is red and preset4 is green in the standard Outlook palette.
	Color string
}

MasterCategory is one entry in a mailbox's master category list (Outlook's named, colored categories under outlook/masterCategories). Only master-list entries render as colored chips in Outlook; a category applied to a message that is NOT in this list shows without a color. Gmail has no equivalent.

type Message

type Message struct {
	// From is the sender's email address (required)
	From string

	// To contains the primary recipient email addresses (at least one required)
	To []string

	// Cc contains carbon copy recipient email addresses (optional)
	Cc []string

	// Bcc contains blind carbon copy recipient email addresses (optional)
	Bcc []string

	// Subject is the email subject line (required)
	Subject string

	// Body is the email content (required)
	Body string

	// HTML indicates whether the body should be treated as HTML.
	// If false, the body is treated as plain text.
	HTML bool

	// Attachments contains file attachments (optional)
	Attachments []Attachment
}

Message represents an email message with all necessary fields for sending.

func (*Message) Validate

func (m *Message) Validate() error

Validate checks if the message has all required fields. It returns an error describing the first validation failure found.

type OutlookConfig

type OutlookConfig struct {
	// TenantID is the Azure AD tenant ID
	TenantID string

	// ClientID is the Azure AD application client ID
	ClientID string

	// ClientSecret is the Azure AD application client secret
	ClientSecret string

	// UserID is the mailbox (user principal name or object id) that the read
	// and management operations of MailboxProvider act on, e.g.
	// "info@deltalegal.com.au". It is not required for sending — Send keys off
	// the message's From address — but the mailbox operations (List, Read,
	// Move, ...) need a concrete target and return an error if it is empty.
	UserID string
}

OutlookConfig holds Outlook 365 specific configuration for OAuth2 authentication.

type Provider

type Provider interface {
	// Send sends an email message using the provider's implementation.
	// The context can be used for timeout and cancellation.
	Send(ctx context.Context, msg *Message) error
}

Provider is the interface that all email providers must implement. This allows for easy addition of new email providers.

type Summary added in v1.1.0

type Summary struct {
	// ID is the provider-specific message identifier. For Outlook it is the
	// Graph message id; for Gmail the Gmail message id. Treat it as opaque.
	ID string

	// ThreadID groups a conversation. Populated for Gmail; empty for Outlook
	// (Outlook exposes conversationId, which is not currently surfaced).
	ThreadID string

	// From is the sender's email address.
	From string

	// Subject is the message subject (UTF-8, may be non-ASCII).
	Subject string

	// Received is the time the message was received.
	Received time.Time

	// HasAttachments reports whether the message carries file attachments.
	HasAttachments bool

	// Unread reports whether the message is unread.
	Unread bool

	// Labels holds the message's labels (Gmail) or categories (Outlook).
	Labels []string
}

Summary is a lightweight message header returned by List and Search. It deliberately omits the body so listing a folder is cheap.

type VersionInfo

type VersionInfo struct {
	Version    string `json:"version"`
	Major      int    `json:"major"`
	Minor      int    `json:"minor"`
	Patch      int    `json:"patch"`
	PreRelease string `json:"preRelease,omitempty"`
	BuildDate  string `json:"buildDate,omitempty"`
	GitCommit  string `json:"gitCommit,omitempty"`
}

VersionInfo contains detailed version information

func GetVersionInfo

func GetVersionInfo() VersionInfo

GetVersionInfo returns detailed version information

Jump to

Keyboard shortcuts

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