Initial release
This commit is contained in:
commit
70d1d6bce8
9 changed files with 576 additions and 0 deletions
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
.idea/
|
||||||
|
.vscode/
|
||||||
21
LICENSE
Normal file
21
LICENSE
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2025 Oliver Jakoubek, Beautiful Machines
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
225
README.md
Normal file
225
README.md
Normal file
|
|
@ -0,0 +1,225 @@
|
||||||
|
# Sendamatic Go Client
|
||||||
|
|
||||||
|
A Go client library for the [Sendamatic](https://www.sendamatic.net) email delivery API.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- Simple and idiomatic Go API
|
||||||
|
- Context support for timeouts and cancellation
|
||||||
|
- Fluent message builder interface
|
||||||
|
- Support for HTML and plain text emails
|
||||||
|
- File attachments with automatic base64 encoding
|
||||||
|
- Custom headers
|
||||||
|
- Multiple recipients (To, CC, BCC)
|
||||||
|
- Comprehensive error handling
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
```bash
|
||||||
|
go get code.beautifulmachines.dev/jakoubek/sendamatic
|
||||||
|
```
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"code.beautifulmachines.dev/jakoubek/sendamatic"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// Create client
|
||||||
|
client := sendamatic.NewClient("your-user-id", "your-password")
|
||||||
|
|
||||||
|
// Build message
|
||||||
|
msg := sendamatic.NewMessage().
|
||||||
|
SetSender("sender@example.com").
|
||||||
|
AddTo("recipient@example.com").
|
||||||
|
SetSubject("Hello from Sendamatic").
|
||||||
|
SetTextBody("This is a test message.").
|
||||||
|
SetHTMLBody("<h1>Hello!</h1><p>This is a test message.</p>")
|
||||||
|
|
||||||
|
// Send email
|
||||||
|
resp, err := client.Send(context.Background(), msg)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("Email sent successfully: %d", resp.StatusCode)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage Examples
|
||||||
|
|
||||||
|
### Basic Email
|
||||||
|
```go
|
||||||
|
msg := sendamatic.NewMessage().
|
||||||
|
SetSender("sender@example.com").
|
||||||
|
AddTo("recipient@example.com").
|
||||||
|
SetSubject("Hello World").
|
||||||
|
SetTextBody("This is a plain text email.")
|
||||||
|
|
||||||
|
resp, err := client.Send(context.Background(), msg)
|
||||||
|
```
|
||||||
|
|
||||||
|
### HTML Email with Multiple Recipients
|
||||||
|
```go
|
||||||
|
msg := sendamatic.NewMessage().
|
||||||
|
SetSender("newsletter@example.com").
|
||||||
|
AddTo("user1@example.com").
|
||||||
|
AddTo("user2@example.com").
|
||||||
|
AddCC("manager@example.com").
|
||||||
|
AddBCC("archive@example.com").
|
||||||
|
SetSubject("Monthly Newsletter").
|
||||||
|
SetHTMLBody("<h1>Newsletter</h1><p>Your monthly update...</p>").
|
||||||
|
SetTextBody("Newsletter - Your monthly update...")
|
||||||
|
```
|
||||||
|
|
||||||
|
### Email with Attachments
|
||||||
|
```go
|
||||||
|
// From file path
|
||||||
|
msg := sendamatic.NewMessage().
|
||||||
|
SetSender("sender@example.com").
|
||||||
|
AddTo("recipient@example.com").
|
||||||
|
SetSubject("Invoice").
|
||||||
|
SetTextBody("Please find your invoice attached.")
|
||||||
|
|
||||||
|
err := msg.AttachFileFromPath("./invoice.pdf", "application/pdf")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Or from byte slice
|
||||||
|
pdfData := []byte{...}
|
||||||
|
msg.AttachFile("invoice.pdf", "application/pdf", pdfData)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Custom Headers
|
||||||
|
```go
|
||||||
|
msg := sendamatic.NewMessage().
|
||||||
|
SetSender("sender@example.com").
|
||||||
|
AddTo("recipient@example.com").
|
||||||
|
SetSubject("Custom Headers").
|
||||||
|
SetTextBody("Email with custom headers").
|
||||||
|
AddHeader("Reply-To", "support@example.com").
|
||||||
|
AddHeader("X-Priority", "1")
|
||||||
|
```
|
||||||
|
|
||||||
|
### With Timeout
|
||||||
|
```go
|
||||||
|
client := sendamatic.NewClient(
|
||||||
|
"user-id",
|
||||||
|
"password",
|
||||||
|
sendamatic.WithTimeout(45*time.Second),
|
||||||
|
)
|
||||||
|
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
resp, err := client.Send(ctx, msg)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Custom HTTP Client
|
||||||
|
```go
|
||||||
|
httpClient := &http.Client{
|
||||||
|
Timeout: 60 * time.Second,
|
||||||
|
Transport: &http.Transport{
|
||||||
|
MaxIdleConns: 10,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
client := sendamatic.NewClient(
|
||||||
|
"user-id",
|
||||||
|
"password",
|
||||||
|
sendamatic.WithHTTPClient(httpClient),
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Configuration Options
|
||||||
|
|
||||||
|
The client supports various configuration options via the functional options pattern:
|
||||||
|
```go
|
||||||
|
client := sendamatic.NewClient(
|
||||||
|
"user-id",
|
||||||
|
"password",
|
||||||
|
sendamatic.WithBaseURL("https://custom.api.url"),
|
||||||
|
sendamatic.WithTimeout(60*time.Second),
|
||||||
|
sendamatic.WithHTTPClient(customHTTPClient),
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Response Handling
|
||||||
|
|
||||||
|
The `SendResponse` provides methods to check the delivery status:
|
||||||
|
```go
|
||||||
|
resp, err := client.Send(ctx, msg)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check overall success
|
||||||
|
if resp.IsSuccess() {
|
||||||
|
log.Println("Email sent successfully")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check individual recipient status
|
||||||
|
for email := range resp.Recipients {
|
||||||
|
if status, ok := resp.GetStatus(email); ok {
|
||||||
|
log.Printf("Recipient %s: status %d", email, status)
|
||||||
|
}
|
||||||
|
if msgID, ok := resp.GetMessageID(email); ok {
|
||||||
|
log.Printf("Message ID: %s", msgID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Error Handling
|
||||||
|
|
||||||
|
The library provides typed errors for better error handling:
|
||||||
|
```go
|
||||||
|
resp, err := client.Send(ctx, msg)
|
||||||
|
if err != nil {
|
||||||
|
var apiErr *sendamatic.APIError
|
||||||
|
if errors.As(err, &apiErr) {
|
||||||
|
log.Printf("API error (status %d): %s", apiErr.StatusCode, apiErr.Message)
|
||||||
|
if apiErr.ValidationErrors != "" {
|
||||||
|
log.Printf("Validation: %s", apiErr.ValidationErrors)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log.Printf("Other error: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
- Go 1.22 or higher
|
||||||
|
- Valid Sendamatic account with API credentials
|
||||||
|
|
||||||
|
## API Credentials
|
||||||
|
|
||||||
|
Your API credentials consist of:
|
||||||
|
- **User ID**: Your Mail Credential User ID
|
||||||
|
- **Password**: Your Mail Credential Password
|
||||||
|
|
||||||
|
Find these in your Sendamatic dashboard under Mail Credentials.
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
|
||||||
|
For detailed API documentation, visit:
|
||||||
|
- [Sendamatic API Documentation](https://docs.sendamatic.net/api/send/)
|
||||||
|
- [Sendamatic Website](https://www.sendamatic.net)
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
MIT License - see [LICENSE](LICENSE) file for details.
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
Contributions are welcome! Please feel free to submit issues or pull requests.
|
||||||
|
|
||||||
|
## Author
|
||||||
|
|
||||||
|
Oliver Jakoubek ([info@jakoubek.net](mailto:info@jakoubek.net))
|
||||||
83
client.go
Normal file
83
client.go
Normal file
|
|
@ -0,0 +1,83 @@
|
||||||
|
package sendamatic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
defaultBaseURL = "https://send.api.sendamatic.net"
|
||||||
|
defaultTimeout = 30 * time.Second
|
||||||
|
)
|
||||||
|
|
||||||
|
type Client struct {
|
||||||
|
apiKey string
|
||||||
|
baseURL string
|
||||||
|
httpClient *http.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewClient(userID, password string, opts ...Option) *Client {
|
||||||
|
c := &Client{
|
||||||
|
apiKey: fmt.Sprintf("%s-%s", userID, password),
|
||||||
|
baseURL: defaultBaseURL,
|
||||||
|
httpClient: &http.Client{
|
||||||
|
Timeout: defaultTimeout,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Optionen anwenden
|
||||||
|
for _, opt := range opts {
|
||||||
|
opt(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send versendet eine E-Mail über die Sendamatic API
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
payload, err := json.Marshal(msg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to marshal message: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
req, err := http.NewRequestWithContext(ctx, http.MethodPost, c.baseURL+"/send", bytes.NewReader(payload))
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create request: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
req.Header.Set("x-api-key", c.apiKey)
|
||||||
|
|
||||||
|
resp, err := c.httpClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("request failed: %w", err)
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
body, err := io.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to read response: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fehlerbehandlung für 4xx und 5xx
|
||||||
|
if resp.StatusCode >= 400 {
|
||||||
|
return nil, parseErrorResponse(resp.StatusCode, body)
|
||||||
|
}
|
||||||
|
|
||||||
|
var sendResp SendResponse
|
||||||
|
if err := json.Unmarshal(body, &sendResp.Recipients); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to unmarshal response: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
sendResp.StatusCode = resp.StatusCode
|
||||||
|
return &sendResp, nil
|
||||||
|
}
|
||||||
36
errors.go
Normal file
36
errors.go
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
package sendamatic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
// APIError repräsentiert einen API-Fehler
|
||||||
|
type APIError struct {
|
||||||
|
StatusCode int `json:"-"`
|
||||||
|
Message string `json:"error"`
|
||||||
|
ValidationErrors string `json:"validation_errors,omitempty"`
|
||||||
|
JSONPath string `json:"json_path,omitempty"`
|
||||||
|
Sender string `json:"sender,omitempty"`
|
||||||
|
SMTPCode int `json:"smtp_code,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *APIError) Error() string {
|
||||||
|
if e.ValidationErrors != "" {
|
||||||
|
return fmt.Sprintf("sendamatic api error (status %d): %s (path: %s)",
|
||||||
|
e.StatusCode, e.ValidationErrors, e.JSONPath)
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("sendamatic api error (status %d): %s", e.StatusCode, e.Message)
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseErrorResponse(statusCode int, body []byte) error {
|
||||||
|
var apiErr APIError
|
||||||
|
apiErr.StatusCode = statusCode
|
||||||
|
|
||||||
|
if err := json.Unmarshal(body, &apiErr); err != nil {
|
||||||
|
// Fallback, falls JSON nicht parsebar ist
|
||||||
|
apiErr.Message = string(body)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &apiErr
|
||||||
|
}
|
||||||
3
go.mod
Normal file
3
go.mod
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
module code.beautifulmachines.dev/jakoubek/sendamatic
|
||||||
|
|
||||||
|
go 1.25.4
|
||||||
147
message.go
Normal file
147
message.go
Normal file
|
|
@ -0,0 +1,147 @@
|
||||||
|
package sendamatic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/base64"
|
||||||
|
"errors"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Message repräsentiert eine E-Mail-Nachricht
|
||||||
|
type Message struct {
|
||||||
|
To []string `json:"to"`
|
||||||
|
CC []string `json:"cc,omitempty"`
|
||||||
|
BCC []string `json:"bcc,omitempty"`
|
||||||
|
Sender string `json:"sender"`
|
||||||
|
Subject string `json:"subject"`
|
||||||
|
TextBody string `json:"text_body,omitempty"`
|
||||||
|
HTMLBody string `json:"html_body,omitempty"`
|
||||||
|
Headers []Header `json:"headers,omitempty"`
|
||||||
|
Attachments []Attachment `json:"attachments,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Header repräsentiert einen benutzerdefinierten E-Mail-Header
|
||||||
|
type Header struct {
|
||||||
|
Header string `json:"header"`
|
||||||
|
Value string `json:"value"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attachment repräsentiert einen E-Mail-Anhang
|
||||||
|
type Attachment struct {
|
||||||
|
Filename string `json:"filename"`
|
||||||
|
Data string `json:"data"` // Base64-kodiert
|
||||||
|
MimeType string `json:"mimetype"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMessage erstellt eine neue Message
|
||||||
|
func NewMessage() *Message {
|
||||||
|
return &Message{
|
||||||
|
To: []string{},
|
||||||
|
CC: []string{},
|
||||||
|
BCC: []string{},
|
||||||
|
Headers: []Header{},
|
||||||
|
Attachments: []Attachment{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddTo fügt einen Empfänger hinzu
|
||||||
|
func (m *Message) AddTo(email string) *Message {
|
||||||
|
m.To = append(m.To, email)
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddCC fügt einen CC-Empfänger hinzu
|
||||||
|
func (m *Message) AddCC(email string) *Message {
|
||||||
|
m.CC = append(m.CC, email)
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddBCC fügt einen BCC-Empfänger hinzu
|
||||||
|
func (m *Message) AddBCC(email string) *Message {
|
||||||
|
m.BCC = append(m.BCC, email)
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetSender setzt den Absender
|
||||||
|
func (m *Message) SetSender(email string) *Message {
|
||||||
|
m.Sender = email
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetSubject setzt den Betreff
|
||||||
|
func (m *Message) SetSubject(subject string) *Message {
|
||||||
|
m.Subject = subject
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetTextBody setzt den Text-Körper
|
||||||
|
func (m *Message) SetTextBody(body string) *Message {
|
||||||
|
m.TextBody = body
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetHTMLBody setzt den HTML-Körper
|
||||||
|
func (m *Message) SetHTMLBody(body string) *Message {
|
||||||
|
m.HTMLBody = body
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddHeader fügt einen benutzerdefinierten Header hinzu
|
||||||
|
func (m *Message) AddHeader(name, value string) *Message {
|
||||||
|
m.Headers = append(m.Headers, Header{
|
||||||
|
Header: name,
|
||||||
|
Value: value,
|
||||||
|
})
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
// AttachFile fügt eine Datei als Anhang hinzu
|
||||||
|
func (m *Message) AttachFile(filename, mimeType string, data []byte) *Message {
|
||||||
|
m.Attachments = append(m.Attachments, Attachment{
|
||||||
|
Filename: filename,
|
||||||
|
Data: base64.StdEncoding.EncodeToString(data),
|
||||||
|
MimeType: mimeType,
|
||||||
|
})
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
// AttachFileFromPath lädt eine Datei vom Dateisystem und fügt sie als Anhang hinzu
|
||||||
|
func (m *Message) AttachFileFromPath(path, mimeType string) error {
|
||||||
|
data, err := os.ReadFile(path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extrahiere Dateinamen aus Pfad
|
||||||
|
filename := path
|
||||||
|
if idx := len(path) - 1; idx >= 0 {
|
||||||
|
for i := idx; i >= 0; i-- {
|
||||||
|
if path[i] == '/' || path[i] == '\\' {
|
||||||
|
filename = path[i+1:]
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
m.AttachFile(filename, mimeType, data)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate prüft, ob die Message gültig ist
|
||||||
|
func (m *Message) Validate() error {
|
||||||
|
if len(m.To) == 0 {
|
||||||
|
return errors.New("at least one recipient required")
|
||||||
|
}
|
||||||
|
if len(m.To) > 255 {
|
||||||
|
return errors.New("maximum 255 recipients allowed")
|
||||||
|
}
|
||||||
|
if m.Sender == "" {
|
||||||
|
return errors.New("sender is required")
|
||||||
|
}
|
||||||
|
if m.Subject == "" {
|
||||||
|
return errors.New("subject is required")
|
||||||
|
}
|
||||||
|
if m.TextBody == "" && m.HTMLBody == "" {
|
||||||
|
return errors.New("either text_body or html_body is required")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
26
options.go
Normal file
26
options.go
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
package sendamatic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Option func(*Client)
|
||||||
|
|
||||||
|
func WithBaseURL(baseURL string) Option {
|
||||||
|
return func(c *Client) {
|
||||||
|
c.baseURL = baseURL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithHTTPClient(client *http.Client) Option {
|
||||||
|
return func(c *Client) {
|
||||||
|
c.httpClient = client
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithTimeout(timeout time.Duration) Option {
|
||||||
|
return func(c *Client) {
|
||||||
|
c.httpClient.Timeout = timeout
|
||||||
|
}
|
||||||
|
}
|
||||||
33
response.go
Normal file
33
response.go
Normal file
|
|
@ -0,0 +1,33 @@
|
||||||
|
package sendamatic
|
||||||
|
|
||||||
|
// SendResponse repräsentiert die Antwort auf einen Send-Request
|
||||||
|
type SendResponse struct {
|
||||||
|
StatusCode int
|
||||||
|
Recipients map[string][2]interface{} // Email -> [StatusCode, MessageID]
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsSuccess prüft, ob die gesamte Sendung erfolgreich war
|
||||||
|
func (r *SendResponse) IsSuccess() bool {
|
||||||
|
return r.StatusCode == 200
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetMessageID gibt die Message-ID für einen Empfänger zurück
|
||||||
|
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 {
|
||||||
|
return msgID, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetStatus gibt den Status-Code für einen Empfänger zurück
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue