Add comprehensive documentation comments
This commit is contained in:
parent
d653000f18
commit
9db82f92c5
6 changed files with 128 additions and 26 deletions
4
.gitignore
vendored
4
.gitignore
vendored
|
|
@ -1,2 +1,6 @@
|
||||||
.idea/
|
.idea/
|
||||||
.vscode/
|
.vscode/
|
||||||
|
|
||||||
|
CLAUDE.md
|
||||||
|
.claude/
|
||||||
|
.claude
|
||||||
|
|
|
||||||
35
client.go
35
client.go
|
|
@ -1,3 +1,17 @@
|
||||||
|
// Package sendamatic provides a Go client library for the Sendamatic email delivery API.
|
||||||
|
//
|
||||||
|
// The library offers a simple and idiomatic Go API with context support, a fluent message
|
||||||
|
// builder interface, and comprehensive error handling for sending transactional emails.
|
||||||
|
//
|
||||||
|
// Example usage:
|
||||||
|
//
|
||||||
|
// client := sendamatic.NewClient("your-user-id", "your-password")
|
||||||
|
// msg := sendamatic.NewMessage().
|
||||||
|
// SetSender("sender@example.com").
|
||||||
|
// AddTo("recipient@example.com").
|
||||||
|
// SetSubject("Hello").
|
||||||
|
// SetTextBody("Hello World")
|
||||||
|
// resp, err := client.Send(context.Background(), msg)
|
||||||
package sendamatic
|
package sendamatic
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|
@ -11,16 +25,28 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
// defaultBaseURL is the default Sendamatic API endpoint.
|
||||||
defaultBaseURL = "https://send.api.sendamatic.net"
|
defaultBaseURL = "https://send.api.sendamatic.net"
|
||||||
|
// defaultTimeout is the default HTTP client timeout for API requests.
|
||||||
defaultTimeout = 30 * time.Second
|
defaultTimeout = 30 * time.Second
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Client represents a Sendamatic API client that handles authentication and HTTP communication
|
||||||
|
// with the Sendamatic email delivery service.
|
||||||
type Client struct {
|
type Client struct {
|
||||||
apiKey string
|
apiKey string
|
||||||
baseURL string
|
baseURL string
|
||||||
httpClient *http.Client
|
httpClient *http.Client
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewClient creates and returns a new Client configured with the provided Sendamatic credentials.
|
||||||
|
// The userID and password are combined to form the API key used for authentication.
|
||||||
|
// Optional configuration functions can be provided to customize the client behavior.
|
||||||
|
//
|
||||||
|
// Example:
|
||||||
|
//
|
||||||
|
// client := sendamatic.NewClient("user-id", "password",
|
||||||
|
// sendamatic.WithTimeout(60*time.Second))
|
||||||
func NewClient(userID, password string, opts ...Option) *Client {
|
func NewClient(userID, password string, opts ...Option) *Client {
|
||||||
c := &Client{
|
c := &Client{
|
||||||
apiKey: fmt.Sprintf("%s-%s", userID, password),
|
apiKey: fmt.Sprintf("%s-%s", userID, password),
|
||||||
|
|
@ -30,7 +56,7 @@ func NewClient(userID, password string, opts ...Option) *Client {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// Optionen anwenden
|
// Apply configuration options
|
||||||
for _, opt := range opts {
|
for _, opt := range opts {
|
||||||
opt(c)
|
opt(c)
|
||||||
}
|
}
|
||||||
|
|
@ -38,7 +64,12 @@ func NewClient(userID, password string, opts ...Option) *Client {
|
||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send versendet eine E-Mail über die Sendamatic API
|
// Send sends an email message through the Sendamatic API using the provided context.
|
||||||
|
// The message is validated before sending. If validation fails or the API request fails,
|
||||||
|
// an error is returned. On success, a SendResponse containing per-recipient delivery
|
||||||
|
// information is returned.
|
||||||
|
//
|
||||||
|
// The context can be used to set deadlines, timeouts, or cancel the request.
|
||||||
func (c *Client) Send(ctx context.Context, msg *Message) (*SendResponse, error) {
|
func (c *Client) Send(ctx context.Context, msg *Message) (*SendResponse, error) {
|
||||||
if err := msg.Validate(); err != nil {
|
if err := msg.Validate(); err != nil {
|
||||||
return nil, fmt.Errorf("message validation failed: %w", err)
|
return nil, fmt.Errorf("message validation failed: %w", err)
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,9 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
)
|
)
|
||||||
|
|
||||||
// APIError repräsentiert einen API-Fehler
|
// APIError represents an error response from the Sendamatic API.
|
||||||
|
// It includes the HTTP status code, error message, and optional additional context
|
||||||
|
// such as validation errors, JSON path information, and SMTP codes.
|
||||||
type APIError struct {
|
type APIError struct {
|
||||||
StatusCode int `json:"-"`
|
StatusCode int `json:"-"`
|
||||||
Message string `json:"error"`
|
Message string `json:"error"`
|
||||||
|
|
@ -15,6 +17,8 @@ type APIError struct {
|
||||||
SMTPCode int `json:"smtp_code,omitempty"`
|
SMTPCode int `json:"smtp_code,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Error implements the error interface and returns a formatted error message.
|
||||||
|
// If validation errors are present, they are included with the JSON path context.
|
||||||
func (e *APIError) Error() string {
|
func (e *APIError) Error() string {
|
||||||
if e.ValidationErrors != "" {
|
if e.ValidationErrors != "" {
|
||||||
return fmt.Sprintf("sendamatic api error (status %d): %s (path: %s)",
|
return fmt.Sprintf("sendamatic api error (status %d): %s (path: %s)",
|
||||||
|
|
@ -23,6 +27,8 @@ func (e *APIError) Error() string {
|
||||||
return fmt.Sprintf("sendamatic api error (status %d): %s", e.StatusCode, e.Message)
|
return fmt.Sprintf("sendamatic api error (status %d): %s", e.StatusCode, e.Message)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// parseErrorResponse attempts to parse an API error response body into an APIError.
|
||||||
|
// If the body cannot be parsed as JSON, it uses the raw body as the error message.
|
||||||
func parseErrorResponse(statusCode int, body []byte) error {
|
func parseErrorResponse(statusCode int, body []byte) error {
|
||||||
var apiErr APIError
|
var apiErr APIError
|
||||||
apiErr.StatusCode = statusCode
|
apiErr.StatusCode = statusCode
|
||||||
|
|
|
||||||
54
message.go
54
message.go
|
|
@ -6,7 +6,9 @@ import (
|
||||||
"os"
|
"os"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Message repräsentiert eine E-Mail-Nachricht
|
// Message represents an email message with all its components including recipients,
|
||||||
|
// content, headers, and attachments. Messages are constructed using the fluent builder
|
||||||
|
// pattern provided by the setter methods.
|
||||||
type Message struct {
|
type Message struct {
|
||||||
To []string `json:"to"`
|
To []string `json:"to"`
|
||||||
CC []string `json:"cc,omitempty"`
|
CC []string `json:"cc,omitempty"`
|
||||||
|
|
@ -19,20 +21,21 @@ type Message struct {
|
||||||
Attachments []Attachment `json:"attachments,omitempty"`
|
Attachments []Attachment `json:"attachments,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Header repräsentiert einen benutzerdefinierten E-Mail-Header
|
// Header represents a custom email header as a name-value pair.
|
||||||
type Header struct {
|
type Header struct {
|
||||||
Header string `json:"header"`
|
Header string `json:"header"`
|
||||||
Value string `json:"value"`
|
Value string `json:"value"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Attachment repräsentiert einen E-Mail-Anhang
|
// Attachment represents an email attachment with its filename, MIME type, and base64-encoded data.
|
||||||
type Attachment struct {
|
type Attachment struct {
|
||||||
Filename string `json:"filename"`
|
Filename string `json:"filename"`
|
||||||
Data string `json:"data"` // Base64-kodiert
|
Data string `json:"data"` // Base64-encoded file content
|
||||||
MimeType string `json:"mimetype"`
|
MimeType string `json:"mimetype"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewMessage erstellt eine neue Message
|
// NewMessage creates and returns a new empty Message with initialized slices for recipients,
|
||||||
|
// headers, and attachments. Use the setter methods to populate the message fields.
|
||||||
func NewMessage() *Message {
|
func NewMessage() *Message {
|
||||||
return &Message{
|
return &Message{
|
||||||
To: []string{},
|
To: []string{},
|
||||||
|
|
@ -43,49 +46,58 @@ func NewMessage() *Message {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddTo fügt einen Empfänger hinzu
|
// AddTo adds a recipient email address to the To field.
|
||||||
|
// Returns the message for method chaining.
|
||||||
func (m *Message) AddTo(email string) *Message {
|
func (m *Message) AddTo(email string) *Message {
|
||||||
m.To = append(m.To, email)
|
m.To = append(m.To, email)
|
||||||
return m
|
return m
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddCC fügt einen CC-Empfänger hinzu
|
// AddCC adds a recipient email address to the CC (carbon copy) field.
|
||||||
|
// Returns the message for method chaining.
|
||||||
func (m *Message) AddCC(email string) *Message {
|
func (m *Message) AddCC(email string) *Message {
|
||||||
m.CC = append(m.CC, email)
|
m.CC = append(m.CC, email)
|
||||||
return m
|
return m
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddBCC fügt einen BCC-Empfänger hinzu
|
// AddBCC adds a recipient email address to the BCC (blind carbon copy) field.
|
||||||
|
// Returns the message for method chaining.
|
||||||
func (m *Message) AddBCC(email string) *Message {
|
func (m *Message) AddBCC(email string) *Message {
|
||||||
m.BCC = append(m.BCC, email)
|
m.BCC = append(m.BCC, email)
|
||||||
return m
|
return m
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetSender setzt den Absender
|
// SetSender sets the sender email address for the message.
|
||||||
|
// Returns the message for method chaining.
|
||||||
func (m *Message) SetSender(email string) *Message {
|
func (m *Message) SetSender(email string) *Message {
|
||||||
m.Sender = email
|
m.Sender = email
|
||||||
return m
|
return m
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetSubject setzt den Betreff
|
// SetSubject sets the email subject line.
|
||||||
|
// Returns the message for method chaining.
|
||||||
func (m *Message) SetSubject(subject string) *Message {
|
func (m *Message) SetSubject(subject string) *Message {
|
||||||
m.Subject = subject
|
m.Subject = subject
|
||||||
return m
|
return m
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetTextBody setzt den Text-Körper
|
// SetTextBody sets the plain text body of the email.
|
||||||
|
// Returns the message for method chaining.
|
||||||
func (m *Message) SetTextBody(body string) *Message {
|
func (m *Message) SetTextBody(body string) *Message {
|
||||||
m.TextBody = body
|
m.TextBody = body
|
||||||
return m
|
return m
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetHTMLBody setzt den HTML-Körper
|
// SetHTMLBody sets the HTML body of the email.
|
||||||
|
// Returns the message for method chaining.
|
||||||
func (m *Message) SetHTMLBody(body string) *Message {
|
func (m *Message) SetHTMLBody(body string) *Message {
|
||||||
m.HTMLBody = body
|
m.HTMLBody = body
|
||||||
return m
|
return m
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddHeader fügt einen benutzerdefinierten Header hinzu
|
// AddHeader adds a custom email header with the specified name and value.
|
||||||
|
// Common examples include "Reply-To", "X-Priority", or custom application headers.
|
||||||
|
// Returns the message for method chaining.
|
||||||
func (m *Message) AddHeader(name, value string) *Message {
|
func (m *Message) AddHeader(name, value string) *Message {
|
||||||
m.Headers = append(m.Headers, Header{
|
m.Headers = append(m.Headers, Header{
|
||||||
Header: name,
|
Header: name,
|
||||||
|
|
@ -94,7 +106,9 @@ func (m *Message) AddHeader(name, value string) *Message {
|
||||||
return m
|
return m
|
||||||
}
|
}
|
||||||
|
|
||||||
// AttachFile fügt eine Datei als Anhang hinzu
|
// AttachFile adds a file attachment to the message from a byte slice.
|
||||||
|
// The data is automatically base64-encoded for transmission.
|
||||||
|
// Returns the message for method chaining.
|
||||||
func (m *Message) AttachFile(filename, mimeType string, data []byte) *Message {
|
func (m *Message) AttachFile(filename, mimeType string, data []byte) *Message {
|
||||||
m.Attachments = append(m.Attachments, Attachment{
|
m.Attachments = append(m.Attachments, Attachment{
|
||||||
Filename: filename,
|
Filename: filename,
|
||||||
|
|
@ -104,7 +118,9 @@ func (m *Message) AttachFile(filename, mimeType string, data []byte) *Message {
|
||||||
return m
|
return m
|
||||||
}
|
}
|
||||||
|
|
||||||
// AttachFileFromPath lädt eine Datei vom Dateisystem und fügt sie als Anhang hinzu
|
// AttachFileFromPath reads a file from the filesystem and adds it as an attachment.
|
||||||
|
// The filename is extracted from the path. Returns an error if the file cannot be read.
|
||||||
|
// The file data is automatically base64-encoded for transmission.
|
||||||
func (m *Message) AttachFileFromPath(path, mimeType string) error {
|
func (m *Message) AttachFileFromPath(path, mimeType string) error {
|
||||||
data, err := os.ReadFile(path)
|
data, err := os.ReadFile(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -126,7 +142,13 @@ func (m *Message) AttachFileFromPath(path, mimeType string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate prüft, ob die Message gültig ist
|
// Validate checks whether the message meets all required criteria for sending.
|
||||||
|
// It returns an error if any validation rules are violated:
|
||||||
|
// - At least one recipient is required
|
||||||
|
// - Maximum of 255 recipients allowed
|
||||||
|
// - Sender must be specified
|
||||||
|
// - Subject must be specified
|
||||||
|
// - Either TextBody or HTMLBody (or both) must be provided
|
||||||
func (m *Message) Validate() error {
|
func (m *Message) Validate() error {
|
||||||
if len(m.To) == 0 {
|
if len(m.To) == 0 {
|
||||||
return errors.New("at least one recipient required")
|
return errors.New("at least one recipient required")
|
||||||
|
|
|
||||||
29
options.go
29
options.go
|
|
@ -5,20 +5,49 @@ import (
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Option is a function type that modifies a Client during initialization.
|
||||||
|
// Options follow the functional options pattern for configuring client behavior.
|
||||||
type Option func(*Client)
|
type Option func(*Client)
|
||||||
|
|
||||||
|
// WithBaseURL returns an Option that sets a custom API base URL for the client.
|
||||||
|
// Use this to point to a different Sendamatic API endpoint or a testing environment.
|
||||||
|
//
|
||||||
|
// Example:
|
||||||
|
//
|
||||||
|
// client := sendamatic.NewClient("user", "pass",
|
||||||
|
// sendamatic.WithBaseURL("https://custom.api.url"))
|
||||||
func WithBaseURL(baseURL string) Option {
|
func WithBaseURL(baseURL string) Option {
|
||||||
return func(c *Client) {
|
return func(c *Client) {
|
||||||
c.baseURL = baseURL
|
c.baseURL = baseURL
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WithHTTPClient returns an Option that replaces the default HTTP client with a custom one.
|
||||||
|
// This allows full control over HTTP behavior such as transport settings, connection pooling,
|
||||||
|
// and custom middleware.
|
||||||
|
//
|
||||||
|
// Example:
|
||||||
|
//
|
||||||
|
// customClient := &http.Client{
|
||||||
|
// Timeout: 60 * time.Second,
|
||||||
|
// Transport: customTransport,
|
||||||
|
// }
|
||||||
|
// client := sendamatic.NewClient("user", "pass",
|
||||||
|
// sendamatic.WithHTTPClient(customClient))
|
||||||
func WithHTTPClient(client *http.Client) Option {
|
func WithHTTPClient(client *http.Client) Option {
|
||||||
return func(c *Client) {
|
return func(c *Client) {
|
||||||
c.httpClient = client
|
c.httpClient = client
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WithTimeout returns an Option that sets the HTTP client timeout duration.
|
||||||
|
// This determines how long the client will wait for a response before timing out.
|
||||||
|
// The default timeout is 30 seconds.
|
||||||
|
//
|
||||||
|
// Example:
|
||||||
|
//
|
||||||
|
// client := sendamatic.NewClient("user", "pass",
|
||||||
|
// sendamatic.WithTimeout(60*time.Second))
|
||||||
func WithTimeout(timeout time.Duration) Option {
|
func WithTimeout(timeout time.Duration) Option {
|
||||||
return func(c *Client) {
|
return func(c *Client) {
|
||||||
c.httpClient.Timeout = timeout
|
c.httpClient.Timeout = timeout
|
||||||
|
|
|
||||||
22
response.go
22
response.go
|
|
@ -1,17 +1,23 @@
|
||||||
package sendamatic
|
package sendamatic
|
||||||
|
|
||||||
// SendResponse repräsentiert die Antwort auf einen Send-Request
|
// SendResponse represents the response from a send email request.
|
||||||
|
// It contains the overall HTTP status code and per-recipient delivery information
|
||||||
|
// including individual status codes and message IDs.
|
||||||
type SendResponse struct {
|
type SendResponse struct {
|
||||||
StatusCode int
|
StatusCode int
|
||||||
Recipients map[string][2]interface{} // Email -> [StatusCode, MessageID]
|
Recipients map[string][2]interface{} // Email address -> [status code, message ID]
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsSuccess prüft, ob die gesamte Sendung erfolgreich war
|
// IsSuccess returns true if the email send request was successful (HTTP 200).
|
||||||
|
// Note that this checks the overall request status; individual recipients
|
||||||
|
// may still have failed. Use GetStatus to check per-recipient delivery status.
|
||||||
func (r *SendResponse) IsSuccess() bool {
|
func (r *SendResponse) IsSuccess() bool {
|
||||||
return r.StatusCode == 200
|
return r.StatusCode == 200
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetMessageID gibt die Message-ID für einen Empfänger zurück
|
// GetMessageID returns the message ID for a specific recipient email address.
|
||||||
|
// The message ID can be used to track the email in logs or with the email provider.
|
||||||
|
// Returns the message ID and true if found, or empty string and false if not found.
|
||||||
func (r *SendResponse) GetMessageID(email string) (string, bool) {
|
func (r *SendResponse) GetMessageID(email string) (string, bool) {
|
||||||
if info, ok := r.Recipients[email]; ok && len(info) >= 2 {
|
if info, ok := r.Recipients[email]; ok && len(info) >= 2 {
|
||||||
if msgID, ok := info[1].(string); ok {
|
if msgID, ok := info[1].(string); ok {
|
||||||
|
|
@ -21,10 +27,14 @@ func (r *SendResponse) GetMessageID(email string) (string, bool) {
|
||||||
return "", false
|
return "", false
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetStatus gibt den Status-Code für einen Empfänger zurück
|
// GetStatus returns the delivery status code for a specific recipient email address.
|
||||||
|
// The status code indicates whether the email was accepted for delivery to that recipient.
|
||||||
|
// Returns the status code and true if found, or 0 and false if not found.
|
||||||
|
//
|
||||||
|
// Note: The API returns status codes as JSON numbers which are decoded as float64,
|
||||||
|
// so this method performs the necessary type conversion to int.
|
||||||
func (r *SendResponse) GetStatus(email string) (int, bool) {
|
func (r *SendResponse) GetStatus(email string) (int, bool) {
|
||||||
if info, ok := r.Recipients[email]; ok && len(info) >= 1 {
|
if info, ok := r.Recipients[email]; ok && len(info) >= 1 {
|
||||||
// Die API gibt float64 zurück, da JSON numbers als float64 dekodiert werden
|
|
||||||
if status, ok := info[0].(float64); ok {
|
if status, ok := info[0].(float64); ok {
|
||||||
return int(status), true
|
return int(status), true
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue