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/
|
||||
.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
|
||||
|
||||
import (
|
||||
|
|
@ -11,16 +25,28 @@ import (
|
|||
)
|
||||
|
||||
const (
|
||||
// defaultBaseURL is the default Sendamatic API endpoint.
|
||||
defaultBaseURL = "https://send.api.sendamatic.net"
|
||||
// defaultTimeout is the default HTTP client timeout for API requests.
|
||||
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 {
|
||||
apiKey string
|
||||
baseURL string
|
||||
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 {
|
||||
c := &Client{
|
||||
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 {
|
||||
opt(c)
|
||||
}
|
||||
|
|
@ -38,7 +64,12 @@ func NewClient(userID, password string, opts ...Option) *Client {
|
|||
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) {
|
||||
if err := msg.Validate(); err != nil {
|
||||
return nil, fmt.Errorf("message validation failed: %w", err)
|
||||
|
|
|
|||
|
|
@ -5,7 +5,9 @@ import (
|
|||
"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 {
|
||||
StatusCode int `json:"-"`
|
||||
Message string `json:"error"`
|
||||
|
|
@ -15,6 +17,8 @@ type APIError struct {
|
|||
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 {
|
||||
if e.ValidationErrors != "" {
|
||||
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)
|
||||
}
|
||||
|
||||
// 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 {
|
||||
var apiErr APIError
|
||||
apiErr.StatusCode = statusCode
|
||||
|
|
|
|||
54
message.go
54
message.go
|
|
@ -6,7 +6,9 @@ import (
|
|||
"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 {
|
||||
To []string `json:"to"`
|
||||
CC []string `json:"cc,omitempty"`
|
||||
|
|
@ -19,20 +21,21 @@ type Message struct {
|
|||
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 {
|
||||
Header string `json:"header"`
|
||||
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 {
|
||||
Filename string `json:"filename"`
|
||||
Data string `json:"data"` // Base64-kodiert
|
||||
Data string `json:"data"` // Base64-encoded file content
|
||||
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 {
|
||||
return &Message{
|
||||
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 {
|
||||
m.To = append(m.To, email)
|
||||
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 {
|
||||
m.CC = append(m.CC, email)
|
||||
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 {
|
||||
m.BCC = append(m.BCC, email)
|
||||
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 {
|
||||
m.Sender = email
|
||||
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 {
|
||||
m.Subject = subject
|
||||
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 {
|
||||
m.TextBody = body
|
||||
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 {
|
||||
m.HTMLBody = body
|
||||
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 {
|
||||
m.Headers = append(m.Headers, Header{
|
||||
Header: name,
|
||||
|
|
@ -94,7 +106,9 @@ func (m *Message) AddHeader(name, value string) *Message {
|
|||
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 {
|
||||
m.Attachments = append(m.Attachments, Attachment{
|
||||
Filename: filename,
|
||||
|
|
@ -104,7 +118,9 @@ func (m *Message) AttachFile(filename, mimeType string, data []byte) *Message {
|
|||
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 {
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
|
|
@ -126,7 +142,13 @@ func (m *Message) AttachFileFromPath(path, mimeType string) error {
|
|||
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 {
|
||||
if len(m.To) == 0 {
|
||||
return errors.New("at least one recipient required")
|
||||
|
|
|
|||
29
options.go
29
options.go
|
|
@ -5,20 +5,49 @@ import (
|
|||
"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)
|
||||
|
||||
// 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 {
|
||||
return func(c *Client) {
|
||||
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 {
|
||||
return func(c *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 {
|
||||
return func(c *Client) {
|
||||
c.httpClient.Timeout = timeout
|
||||
|
|
|
|||
22
response.go
22
response.go
|
|
@ -1,17 +1,23 @@
|
|||
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 {
|
||||
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 {
|
||||
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) {
|
||||
if info, ok := r.Recipients[email]; ok && len(info) >= 2 {
|
||||
if msgID, ok := info[1].(string); ok {
|
||||
|
|
@ -21,10 +27,14 @@ func (r *SendResponse) GetMessageID(email string) (string, bool) {
|
|||
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) {
|
||||
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 {
|
||||
return int(status), true
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue