Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
Complete MVP implementation of the Cast GHL Conversation Provider bridge: - Go module setup with chi router and mongo-driver dependencies - Config loading with env var validation and defaults - MongoDB token store with upsert, get, update, delete operations - Cast.ph SMS client with 429 retry logic and typed errors - Phone number normalization (E.164 ↔ Philippine local format) - GHL OAuth 2.0 install/callback/refresh flow - GHL webhook handler with ECDSA signature verification (async dispatch) - GHL API client for message status updates and inbound message stubs - Multi-stage Dockerfile, docker-compose with MongoDB, Woodpecker CI pipeline - Unit tests for phone normalization, Cast client, GHL webhook, and OAuth handlers Co-Authored-By: SideKx <sidekx.ai@sds.dev>
2.3 KiB
2.3 KiB
Task 03: Cast API Client
Objective
Build internal/cast/client.go — a typed HTTP client for the Cast.ph SMS API.
Reference
Read CAST_API_REFERENCE.md for exact request/response shapes.
Types (internal/cast/types.go)
type SendRequest struct {
To string `json:"to"`
Message string `json:"message"`
SenderID string `json:"sender_id,omitempty"`
}
type SendResponse struct {
Success bool `json:"success"`
MessageID string `json:"message_id,omitempty"`
Parts int `json:"parts,omitempty"`
Error string `json:"error,omitempty"`
}
Client (internal/cast/client.go)
Client struct
type Client struct {
baseURL string
apiKey string
senderID string // default sender ID, can be empty
httpClient *http.Client
}
func NewClient(baseURL, apiKey, senderID string) *Client
Methods
func (c *Client) SendSMS(ctx context.Context, to, message string) (*SendResponse, error)
- POST to
/api/sms/send - Set headers:
X-API-Key,Content-Type: application/json - Body:
{ "to": to, "message": message, "sender_id": c.senderID }(omit sender_id if empty) - On non-200: return
CastAPIErrorwith status code and error message from body - On 200 with
success: false: returnCastAPIErrorwith the error field - On 200 with
success: true: return the response - Retry on 429: max 3 retries, read
Retry-Afterheader, backoff 1s/2s/4s
Error type
type CastAPIError struct {
StatusCode int
APIError string
}
func (e *CastAPIError) Error() string {
return fmt.Sprintf("cast api error (HTTP %d): %s", e.StatusCode, e.APIError)
}
Key behaviors
- HTTP client timeout: 30 seconds
sender_idomitted from JSON when Client.senderID is empty- Response body always read and closed, even on errors
- Retry on 429 only (not on 5xx — Cast should handle that)
- Log retries with
slog.Warn
Acceptance Criteria
go build ./cmd/server/succeedsSendSMScalls correct URL with correct headersX-API-Keyheader set on every requestsender_idomitted from JSON when emptyCastAPIErrorreturned on non-200 with statusCode + apiErrorCastAPIErrorreturned on 200 withsuccess: false- Retry on 429 with backoff (max 3)
- Context passed to HTTP request
- HTTP client has 30s timeout