Refactor Delivery Structures and Enhance Documentation

- Updated the DeliveryResult and ChannelResult structures to include new fields such as RequestID, SentAt, and improved content handling.
- Revised EmailTarget and WebhookTarget structures to clarify their configurations, including the addition of Template and Method fields.
- Enhanced the DeliveryPreferences structure to support Email, Webhook, and Process configurations, improving the flexibility of delivery options.
- Updated DESIGN.md and TECHNICAL.md to reflect these changes, ensuring comprehensive documentation of the new delivery architecture.
- Marked the completion of related tasks in TODO.md, confirming the integration of new features and structures.
This commit is contained in:
Max 2026-01-18 17:14:30 +08:00
parent 8d88042489
commit ad7e0dbf85
8 changed files with 393 additions and 97 deletions

View file

@ -619,22 +619,23 @@ type DeliveryAgentOutput struct {
```go
// DeliveryResult - returned by Delivery Center
type DeliveryResult struct {
RequestID string `json:"request_id"` // Delivery request ID
Content *DeliveryContent `json:"content"` // What was delivered
Success bool `json:"success"` // All channels succeeded
Results []ChannelResult `json:"results"` // Per-channel results
Error string `json:"error,omitempty"`
RequestID string `json:"request_id"` // Delivery request ID
Content *DeliveryContent `json:"content"` // Agent-generated content
Results []ChannelResult `json:"results,omitempty"` // Results per channel
Success bool `json:"success"` // Overall success
Error string `json:"error,omitempty"` // Error if failed
SentAt *time.Time `json:"sent_at,omitempty"` // When delivery completed
}
// ChannelResult - result for a single delivery target
type ChannelResult struct {
Type DeliveryType `json:"type"` // email | webhook | process | notify
Target string `json:"target,omitempty"` // Target identifier
Success bool `json:"success"`
Recipients []string `json:"recipients,omitempty"` // For email
Type DeliveryType `json:"type"` // email | webhook | process
Target string `json:"target"` // Target identifier (email, URL, process name)
Success bool `json:"success"` // Whether delivery succeeded
Recipients []string `json:"recipients,omitempty"` // Who received (for email)
Details interface{} `json:"details,omitempty"` // Channel-specific response
Error string `json:"error,omitempty"`
SentAt *time.Time `json:"sent_at,omitempty"`
Error string `json:"error,omitempty"` // Error message if failed
SentAt *time.Time `json:"sent_at,omitempty"` // When this target was delivered
}
```
@ -857,9 +858,10 @@ type EmailPreference struct {
}
type EmailTarget struct {
To []string `json:"to"`
CC []string `json:"cc,omitempty"`
SubjectTemplate string `json:"subject_template,omitempty"`
To []string `json:"to"` // Recipient addresses
CC []string `json:"cc,omitempty"` // CC addresses
Template string `json:"template,omitempty"` // Email template ID
Subject string `json:"subject,omitempty"` // Subject template
}
type WebhookPreference struct {
@ -868,8 +870,10 @@ type WebhookPreference struct {
}
type WebhookTarget struct {
URL string `json:"url"`
Headers map[string]string `json:"headers,omitempty"`
URL string `json:"url"` // Webhook URL
Method string `json:"method,omitempty"` // HTTP method (default: POST)
Headers map[string]string `json:"headers,omitempty"` // Custom headers
Secret string `json:"secret,omitempty"` // Signing secret
}
type ProcessPreference struct {
@ -878,8 +882,8 @@ type ProcessPreference struct {
}
type ProcessTarget struct {
Name string `json:"name"` // Process name, e.g., "orders.UpdateStatus"
Args []any `json:"args,omitempty"`
Process string `json:"process"` // Yao Process name, e.g., "orders.UpdateStatus"
Args []any `json:"args,omitempty"` // Additional arguments
}
// ExecutorMode - executor mode enum

View file

@ -1447,9 +1447,10 @@ type EmailPreference struct {
}
type EmailTarget struct {
To []string `json:"to"`
CC []string `json:"cc,omitempty"`
SubjectTemplate string `json:"subject_template,omitempty"` // Optional, default: content.Summary
To []string `json:"to"` // Recipient addresses
CC []string `json:"cc,omitempty"` // CC addresses
Template string `json:"template,omitempty"` // Email template ID
Subject string `json:"subject,omitempty"` // Subject template (default: content.Summary)
}
// WebhookPreference - multiple webhook targets
@ -1459,8 +1460,10 @@ type WebhookPreference struct {
}
type WebhookTarget struct {
URL string `json:"url"`
Headers map[string]string `json:"headers,omitempty"`
URL string `json:"url"` // Webhook URL
Method string `json:"method,omitempty"` // HTTP method (default: POST)
Headers map[string]string `json:"headers,omitempty"` // Custom headers
Secret string `json:"secret,omitempty"` // Signing secret
}
// ProcessPreference - multiple Yao Process targets
@ -1470,28 +1473,29 @@ type ProcessPreference struct {
}
type ProcessTarget struct {
Name string `json:"name"` // Process name, e.g., "orders.UpdateStatus"
Args []any `json:"args,omitempty"` // Additional args (DeliveryContent passed as first arg)
Process string `json:"process"` // Yao Process name, e.g., "orders.UpdateStatus"
Args []any `json:"args,omitempty"` // Additional args (DeliveryContent passed as first arg)
}
// DeliveryResult - P4 delivery output (returned by Delivery Center)
type DeliveryResult struct {
RequestID string `json:"request_id"` // Delivery request ID
Content *DeliveryContent `json:"content,omitempty"` // What was delivered
Success bool `json:"success"` // All channels succeeded
Results []ChannelResult `json:"results,omitempty"` // Per-channel results
Error string `json:"error,omitempty"` // Overall error if any
RequestID string `json:"request_id"` // Delivery request ID
Content *DeliveryContent `json:"content"` // Agent-generated content
Results []ChannelResult `json:"results,omitempty"` // Results per channel
Success bool `json:"success"` // Overall success
Error string `json:"error,omitempty"` // Error if failed
SentAt *time.Time `json:"sent_at,omitempty"` // When delivery completed
}
// ChannelResult - result for a single delivery target
type ChannelResult struct {
Type DeliveryType `json:"type"` // email | webhook | process | notify
Target string `json:"target,omitempty"` // Target identifier (email, URL, process name)
Success bool `json:"success"`
Type DeliveryType `json:"type"` // email | webhook | process
Target string `json:"target"` // Target identifier (email, URL, process name)
Success bool `json:"success"` // Whether delivery succeeded
Recipients []string `json:"recipients,omitempty"` // Who received (for email)
Details interface{} `json:"details,omitempty"` // Channel-specific response
Error string `json:"error,omitempty"`
SentAt *time.Time `json:"sent_at,omitempty"`
Error string `json:"error,omitempty"` // Error message if failed
SentAt *time.Time `json:"sent_at,omitempty"` // When this target was delivered
}
// LearningEntry - knowledge to save
@ -2315,16 +2319,16 @@ func (dc *DeliveryCenter) callProcess(ctx context.Context, content *DeliveryCont
// DeliveryContent as first arg, then additional args
args := append([]interface{}{content}, target.Args...)
proc := process.Of(target.Name, args...)
proc := process.Of(target.Process, args...)
result, err := proc.Execute()
now := time.Now()
return ChannelResult{
Type: DeliveryProcess,
Target: target.Name,
Target: target.Process,
Success: err == nil,
Details: map[string]interface{}{
"process": target.Name,
"process": target.Process,
"result": result,
},
Error: errStr(err),

View file

@ -954,24 +954,24 @@ Supported channels:
- [x] SMS - No attachment (text only)
- [x] WhatsApp - TBD
### 10.3 Type Updates (Prerequisite)
### 10.3 Type Updates (Prerequisite)
- [ ] Update `types/enums.go` - Update `DeliveryType` enum
- [ ] Remove `DeliveryFile`
- [ ] Add `DeliveryProcess`
- [ ] Update `types/robot.go` - Delivery types for new architecture
- [ ] `DeliveryResult` - update to new structure (RequestID, Content, Results[])
- [ ] Add `DeliveryContent` struct
- [ ] Add `DeliveryAttachment` struct
- [ ] Add `DeliveryRequest` struct
- [ ] Add `DeliveryContext` struct
- [ ] Add `DeliveryPreferences` struct (with Email, Webhook, Process)
- [ ] Add `EmailPreference`, `EmailTarget` structs
- [ ] Add `WebhookPreference`, `WebhookTarget` structs
- [ ] Add `ProcessPreference`, `ProcessTarget` structs
- [ ] Add `ChannelResult` struct (with Target field)
- [ ] Update `types/enums_test.go` - Update DeliveryType tests
- [ ] Update `types/robot_test.go` - Update delivery result tests
- [x] Update `types/enums.go` - Update `DeliveryType` enum
- [x] Remove `DeliveryFile`
- [x] Add `DeliveryProcess`
- [x] Update `types/robot.go` - Delivery types for new architecture
- [x] `DeliveryResult` - update to new structure (RequestID, Content, Results[])
- [x] Add `DeliveryContent` struct
- [x] Add `DeliveryAttachment` struct
- [x] Add `DeliveryRequest` struct
- [x] Add `DeliveryContext` struct
- [x] Add `DeliveryPreferences` struct (with Email, Webhook, Process)
- [x] Add `EmailPreference`, `EmailTarget` structs
- [x] Add `WebhookPreference`, `WebhookTarget` structs
- [x] Add `ProcessPreference`, `ProcessTarget` structs
- [x] Add `ChannelResult` struct (with Target field)
- [x] Update `types/enums_test.go` - Update DeliveryType tests
- [x] Update `types/robot_test.go` - Update delivery result tests
### 10.4 Delivery Agent Setup

View file

@ -7,17 +7,17 @@ import (
// Config - robot_config in __yao.member
type Config struct {
Triggers *Triggers `json:"triggers,omitempty"`
Clock *Clock `json:"clock,omitempty"`
Identity *Identity `json:"identity"`
Quota *Quota `json:"quota,omitempty"`
KB *KB `json:"kb,omitempty"` // shared knowledge base (same as assistant)
DB *DB `json:"db,omitempty"` // shared database (same as assistant)
Learn *Learn `json:"learn,omitempty"` // learning config for private KB
Resources *Resources `json:"resources,omitempty"`
Delivery *Delivery `json:"delivery,omitempty"`
Events []Event `json:"events,omitempty"`
Executor *ExecutorConfig `json:"executor,omitempty"` // executor mode settings
Triggers *Triggers `json:"triggers,omitempty"`
Clock *Clock `json:"clock,omitempty"`
Identity *Identity `json:"identity"`
Quota *Quota `json:"quota,omitempty"`
KB *KB `json:"kb,omitempty"` // shared knowledge base (same as assistant)
DB *DB `json:"db,omitempty"` // shared database (same as assistant)
Learn *Learn `json:"learn,omitempty"` // learning config for private KB
Resources *Resources `json:"resources,omitempty"`
Delivery *DeliveryPreferences `json:"delivery,omitempty"` // delivery preferences (see robot.go)
Events []Event `json:"events,omitempty"`
Executor *ExecutorConfig `json:"executor,omitempty"` // executor mode settings
}
// ExecutorConfig - executor settings
@ -224,12 +224,6 @@ type MCPConfig struct {
Tools []string `json:"tools,omitempty"` // empty = all
}
// Delivery - output delivery
type Delivery struct {
Type DeliveryType `json:"type"`
Opts map[string]interface{} `json:"opts,omitempty"`
}
// Event - event trigger config
type Event struct {
Type EventSource `json:"type"` // webhook | database

View file

@ -111,10 +111,10 @@ type DeliveryType string
// DeliveryType constants define the output delivery types
const (
DeliveryEmail DeliveryType = "email"
DeliveryFile DeliveryType = "file"
DeliveryWebhook DeliveryType = "webhook"
DeliveryNotify DeliveryType = "notify"
DeliveryEmail DeliveryType = "email" // Send via yao/messenger
DeliveryWebhook DeliveryType = "webhook" // POST to external URL
DeliveryProcess DeliveryType = "process" // Call Yao Process
DeliveryNotify DeliveryType = "notify" // In-app notification (future, auto by subscriptions)
)
// DedupResult - deduplication result

View file

@ -83,8 +83,8 @@ func TestPriorityEnum(t *testing.T) {
func TestDeliveryTypeEnum(t *testing.T) {
assert.Equal(t, types.DeliveryType("email"), types.DeliveryEmail)
assert.Equal(t, types.DeliveryType("file"), types.DeliveryFile)
assert.Equal(t, types.DeliveryType("webhook"), types.DeliveryWebhook)
assert.Equal(t, types.DeliveryType("process"), types.DeliveryProcess)
assert.Equal(t, types.DeliveryType("notify"), types.DeliveryNotify)
}

View file

@ -262,15 +262,101 @@ type ValidationResult struct {
ReplyContent string `json:"reply_content,omitempty"` // content for next turn (if NeedReply)
}
// DeliveryResult - P4 delivery output
// DeliveryResult - P4 delivery output (new architecture)
type DeliveryResult struct {
Type DeliveryType `json:"type"`
Success bool `json:"success"`
Recipients []string `json:"recipients,omitempty"` // who received
Content string `json:"content,omitempty"` // formatted content that was delivered
Details interface{} `json:"details,omitempty"` // channel-specific response
Error string `json:"error,omitempty"`
SentAt *time.Time `json:"sent_at,omitempty"`
RequestID string `json:"request_id"` // Delivery request ID
Content *DeliveryContent `json:"content"` // Agent-generated content
Results []ChannelResult `json:"results,omitempty"` // Results per channel
Success bool `json:"success"` // Overall success
Error string `json:"error,omitempty"` // Error if failed
SentAt *time.Time `json:"sent_at,omitempty"` // When delivery completed
}
// DeliveryContent - Content generated by Delivery Agent (only content, no channels)
type DeliveryContent struct {
Summary string `json:"summary"` // Brief 1-2 sentence summary
Body string `json:"body"` // Full markdown report
Attachments []DeliveryAttachment `json:"attachments,omitempty"` // Output artifacts from P3
}
// DeliveryAttachment - Task output attachment with metadata
type DeliveryAttachment struct {
Title string `json:"title"` // Human-readable title
Description string `json:"description,omitempty"` // What this artifact is
TaskID string `json:"task_id,omitempty"` // Which task produced this
File string `json:"file"` // Wrapper: __<uploader>://<fileID>
}
// DeliveryRequest - pushed to Delivery Center (no channels - center decides based on preferences)
type DeliveryRequest struct {
Content *DeliveryContent `json:"content"` // Agent-generated content
Context *DeliveryContext `json:"context"` // Tracking info
}
// DeliveryContext - tracking and audit info
type DeliveryContext struct {
MemberID string `json:"member_id"` // Robot member ID (globally unique)
ExecutionID string `json:"execution_id"` // Execution ID
TriggerType TriggerType `json:"trigger_type"` // clock | human | event
TeamID string `json:"team_id"` // Team ID
}
// DeliveryPreferences - Robot/User delivery preferences (from Config)
type DeliveryPreferences struct {
Email *EmailPreference `json:"email,omitempty"` // Email delivery settings
Webhook *WebhookPreference `json:"webhook,omitempty"` // Webhook delivery settings
Process *ProcessPreference `json:"process,omitempty"` // Process delivery settings
}
// EmailPreference - Email delivery configuration
type EmailPreference struct {
Enabled bool `json:"enabled"` // Whether email delivery is enabled
Targets []EmailTarget `json:"targets,omitempty"` // Multiple email targets
}
// EmailTarget - Single email target
type EmailTarget struct {
To []string `json:"to"` // Recipient addresses
CC []string `json:"cc,omitempty"` // CC addresses
Template string `json:"template,omitempty"` // Email template ID
Subject string `json:"subject,omitempty"` // Subject template
}
// WebhookPreference - Webhook delivery configuration
type WebhookPreference struct {
Enabled bool `json:"enabled"` // Whether webhook delivery is enabled
Targets []WebhookTarget `json:"targets,omitempty"` // Multiple webhook targets
}
// WebhookTarget - Single webhook target
type WebhookTarget struct {
URL string `json:"url"` // Webhook URL
Method string `json:"method,omitempty"` // HTTP method (default: POST)
Headers map[string]string `json:"headers,omitempty"` // Custom headers
Secret string `json:"secret,omitempty"` // Signing secret
}
// ProcessPreference - Process delivery configuration
type ProcessPreference struct {
Enabled bool `json:"enabled"` // Whether process delivery is enabled
Targets []ProcessTarget `json:"targets,omitempty"` // Multiple process targets
}
// ProcessTarget - Single process target
type ProcessTarget struct {
Process string `json:"process"` // Yao Process name
Args []any `json:"args,omitempty"` // Process arguments
}
// ChannelResult - Result of delivery to a single channel target
type ChannelResult struct {
Type DeliveryType `json:"type"` // email | webhook | process
Target string `json:"target"` // Target identifier (email, URL, process name)
Success bool `json:"success"` // Whether delivery succeeded
Recipients []string `json:"recipients,omitempty"` // Who received (for email)
Details interface{} `json:"details,omitempty"` // Channel-specific response
Error string `json:"error,omitempty"` // Error message if failed
SentAt *time.Time `json:"sent_at,omitempty"` // When this target was delivered
}
// LearningEntry - knowledge to save

View file

@ -536,25 +536,233 @@ func TestValidationResultMultiTurnFields(t *testing.T) {
func TestDeliveryResultStructure(t *testing.T) {
sentAt := time.Now()
delivery := &types.DeliveryResult{
Type: types.DeliveryEmail,
Success: true,
Recipients: []string{"user@example.com", "manager@example.com"},
Content: "# Weekly Report\n\nSales increased by 20%...",
Details: map[string]interface{}{
"message_id": "msg-12345",
"subject": "Daily Report",
RequestID: "req-12345",
Content: &types.DeliveryContent{
Summary: "Weekly sales report completed",
Body: "# Weekly Report\n\nSales increased by 20%...",
Attachments: []types.DeliveryAttachment{
{
Title: "Sales Report",
Description: "Detailed sales analysis",
TaskID: "task-1",
File: "__s3://report-12345.pdf",
},
},
},
SentAt: &sentAt,
Results: []types.ChannelResult{
{
Type: types.DeliveryEmail,
Target: "user@example.com",
Success: true,
Details: map[string]interface{}{
"message_id": "msg-12345",
},
},
{
Type: types.DeliveryWebhook,
Target: "https://webhook.example.com/notify",
Success: true,
},
},
Success: true,
SentAt: &sentAt,
}
assert.Equal(t, types.DeliveryEmail, delivery.Type)
assert.Equal(t, "req-12345", delivery.RequestID)
assert.True(t, delivery.Success)
assert.Len(t, delivery.Recipients, 2)
assert.Contains(t, delivery.Content, "Weekly Report")
assert.NotNil(t, delivery.Details)
assert.NotNil(t, delivery.Content)
assert.Equal(t, "Weekly sales report completed", delivery.Content.Summary)
assert.Contains(t, delivery.Content.Body, "Weekly Report")
assert.Len(t, delivery.Content.Attachments, 1)
assert.Equal(t, "__s3://report-12345.pdf", delivery.Content.Attachments[0].File)
assert.Len(t, delivery.Results, 2)
assert.Equal(t, types.DeliveryEmail, delivery.Results[0].Type)
assert.NotNil(t, delivery.SentAt)
}
func TestDeliveryContentStructure(t *testing.T) {
content := &types.DeliveryContent{
Summary: "Task execution completed successfully",
Body: "# Execution Report\n\n## Summary\n- 3 tasks completed\n- 1 task failed",
Attachments: []types.DeliveryAttachment{
{
Title: "Analysis Results",
Description: "JSON data from analysis task",
TaskID: "task-analysis",
File: "__local://files/analysis-result.json",
},
{
Title: "Generated Chart",
TaskID: "task-chart",
File: "__s3://charts/sales-chart.png",
},
},
}
assert.NotEmpty(t, content.Summary)
assert.Contains(t, content.Body, "Execution Report")
assert.Len(t, content.Attachments, 2)
assert.Equal(t, "Analysis Results", content.Attachments[0].Title)
assert.Equal(t, "task-analysis", content.Attachments[0].TaskID)
}
func TestDeliveryAttachmentStructure(t *testing.T) {
attachment := &types.DeliveryAttachment{
Title: "Sales Report PDF",
Description: "Monthly sales analysis report",
TaskID: "task-report",
File: "__s3://reports/sales-2024-01.pdf",
}
assert.Equal(t, "Sales Report PDF", attachment.Title)
assert.Equal(t, "Monthly sales analysis report", attachment.Description)
assert.Equal(t, "task-report", attachment.TaskID)
assert.Contains(t, attachment.File, "__s3://")
}
func TestDeliveryRequestStructure(t *testing.T) {
request := &types.DeliveryRequest{
Content: &types.DeliveryContent{
Summary: "Report ready",
Body: "# Report\n\nDetails...",
},
Context: &types.DeliveryContext{
MemberID: "member-123",
ExecutionID: "exec-456",
TriggerType: types.TriggerClock,
TeamID: "team-789",
},
}
assert.NotNil(t, request.Content)
assert.NotNil(t, request.Context)
assert.Equal(t, "member-123", request.Context.MemberID)
assert.Equal(t, "exec-456", request.Context.ExecutionID)
assert.Equal(t, types.TriggerClock, request.Context.TriggerType)
}
func TestDeliveryPreferencesStructure(t *testing.T) {
prefs := &types.DeliveryPreferences{
Email: &types.EmailPreference{
Enabled: true,
Targets: []types.EmailTarget{
{
To: []string{"team@example.com"},
CC: []string{"manager@example.com"},
Template: "weekly-report",
Subject: "Weekly Report - {{.Date}}",
},
{
To: []string{"backup@example.com"},
},
},
},
Webhook: &types.WebhookPreference{
Enabled: true,
Targets: []types.WebhookTarget{
{
URL: "https://api.example.com/webhook",
Method: "POST",
Headers: map[string]string{
"X-API-Key": "secret-key",
},
Secret: "signing-secret",
},
},
},
Process: &types.ProcessPreference{
Enabled: true,
Targets: []types.ProcessTarget{
{
Process: "scripts.notify.slack",
Args: []any{"#general", "Report ready"},
},
},
},
}
// Email
assert.True(t, prefs.Email.Enabled)
assert.Len(t, prefs.Email.Targets, 2)
assert.Equal(t, "weekly-report", prefs.Email.Targets[0].Template)
assert.Len(t, prefs.Email.Targets[0].To, 1)
assert.Len(t, prefs.Email.Targets[0].CC, 1)
// Webhook
assert.True(t, prefs.Webhook.Enabled)
assert.Len(t, prefs.Webhook.Targets, 1)
assert.Equal(t, "https://api.example.com/webhook", prefs.Webhook.Targets[0].URL)
assert.Equal(t, "POST", prefs.Webhook.Targets[0].Method)
// Process
assert.True(t, prefs.Process.Enabled)
assert.Len(t, prefs.Process.Targets, 1)
assert.Equal(t, "scripts.notify.slack", prefs.Process.Targets[0].Process)
assert.Len(t, prefs.Process.Targets[0].Args, 2)
}
func TestChannelResultStructure(t *testing.T) {
t.Run("email result with recipients", func(t *testing.T) {
sentAt := time.Now()
result := &types.ChannelResult{
Type: types.DeliveryEmail,
Target: "user@example.com",
Success: true,
Recipients: []string{"user@example.com", "manager@example.com"},
Details: map[string]interface{}{
"message_id": "msg-123",
},
SentAt: &sentAt,
}
assert.Equal(t, types.DeliveryEmail, result.Type)
assert.Equal(t, "user@example.com", result.Target)
assert.True(t, result.Success)
assert.Len(t, result.Recipients, 2)
assert.NotNil(t, result.SentAt)
})
t.Run("webhook result", func(t *testing.T) {
sentAt := time.Now()
result := &types.ChannelResult{
Type: types.DeliveryWebhook,
Target: "https://api.example.com/webhook",
Success: true,
Details: map[string]interface{}{
"status_code": 200,
"response": "OK",
},
SentAt: &sentAt,
}
assert.Equal(t, types.DeliveryWebhook, result.Type)
assert.True(t, result.Success)
assert.NotNil(t, result.SentAt)
})
t.Run("process result", func(t *testing.T) {
result := &types.ChannelResult{
Type: types.DeliveryProcess,
Target: "scripts.notify.slack",
Success: true,
Details: map[string]interface{}{
"output": "Message sent",
},
}
assert.Equal(t, types.DeliveryProcess, result.Type)
assert.Equal(t, "scripts.notify.slack", result.Target)
})
t.Run("failed result", func(t *testing.T) {
result := &types.ChannelResult{
Type: types.DeliveryWebhook,
Target: "https://api.example.com/webhook",
Success: false,
Error: "Connection refused",
}
assert.False(t, result.Success)
assert.Equal(t, "Connection refused", result.Error)
})
}
func TestDeliveryTargetStructure(t *testing.T) {
delivery := &types.DeliveryTarget{
Type: types.DeliveryEmail,