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}