A community based topic aggregation platform built on atproto
1package telnyx 2 3import ( 4 "bytes" 5 "context" 6 "encoding/json" 7 "fmt" 8 "io" 9 "net/http" 10 "time" 11) 12 13const ( 14 TelnyxAPIBaseURL = "https://api.telnyx.com/v2" 15 SendMessagePath = "/messages" 16) 17 18// Client handles communication with Telnyx API 19type Client struct { 20 apiKey string 21 messagingProfileID string 22 fromNumber string 23 httpClient *http.Client 24} 25 26// NewClient creates a new Telnyx client 27func NewClient(apiKey, messagingProfileID, fromNumber string) *Client { 28 return &Client{ 29 apiKey: apiKey, 30 messagingProfileID: messagingProfileID, 31 fromNumber: fromNumber, 32 httpClient: &http.Client{ 33 Timeout: 10 * time.Second, 34 }, 35 } 36} 37 38// SendOTP sends an OTP code via SMS 39func (c *Client) SendOTP(ctx context.Context, phoneNumber, code string) error { 40 message := fmt.Sprintf("Your Coves verification code is: %s\n\nThis code expires in 10 minutes.", code) 41 42 req := &SendMessageRequest{ 43 From: c.fromNumber, 44 To: phoneNumber, 45 Text: message, 46 MessagingProfileID: c.messagingProfileID, 47 } 48 49 reqBody, err := json.Marshal(req) 50 if err != nil { 51 return fmt.Errorf("failed to marshal request: %w", err) 52 } 53 54 httpReq, err := http.NewRequestWithContext(ctx, "POST", TelnyxAPIBaseURL+SendMessagePath, bytes.NewReader(reqBody)) 55 if err != nil { 56 return fmt.Errorf("failed to create request: %w", err) 57 } 58 59 httpReq.Header.Set("Authorization", "Bearer "+c.apiKey) 60 httpReq.Header.Set("Content-Type", "application/json") 61 62 resp, err := c.httpClient.Do(httpReq) 63 if err != nil { 64 return fmt.Errorf("failed to send request: %w", err) 65 } 66 defer resp.Body.Close() 67 68 body, _ := io.ReadAll(resp.Body) 69 70 if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusCreated { 71 var telnyxErr TelnyxErrorResponse 72 if err := json.Unmarshal(body, &telnyxErr); err == nil && len(telnyxErr.Errors) > 0 { 73 return fmt.Errorf("telnyx API error: %s (code: %s)", telnyxErr.Errors[0].Detail, telnyxErr.Errors[0].Code) 74 } 75 return fmt.Errorf("telnyx API error: status %d, body: %s", resp.StatusCode, string(body)) 76 } 77 78 var msgResp SendMessageResponse 79 if err := json.Unmarshal(body, &msgResp); err != nil { 80 return fmt.Errorf("failed to parse response: %w", err) 81 } 82 83 // Check if message was accepted 84 if msgResp.Data.ID == "" { 85 return fmt.Errorf("message not accepted by Telnyx") 86 } 87 88 return nil 89} 90 91// SendMessageRequest represents a Telnyx send message request 92type SendMessageRequest struct { 93 From string `json:"from"` 94 To string `json:"to"` 95 Text string `json:"text"` 96 MessagingProfileID string `json:"messaging_profile_id"` 97} 98 99// SendMessageResponse represents a Telnyx send message response 100type SendMessageResponse struct { 101 Data MessageData `json:"data"` 102} 103 104// MessageData represents message data in Telnyx response 105type MessageData struct { 106 ID string `json:"id"` 107 Status string `json:"status"` 108} 109 110// TelnyxErrorResponse represents an error response from Telnyx 111type TelnyxErrorResponse struct { 112 Errors []TelnyxError `json:"errors"` 113} 114 115// TelnyxError represents a single error from Telnyx 116type TelnyxError struct { 117 Code string `json:"code"` 118 Detail string `json:"detail"` 119 Title string `json:"title"` 120}