Snippets

Tuomas Hietanen Send text messages with F# (FSharp)

Created by Tuomas Hietanen
[<RequireQualifiedAccess>]
module TextMagic
//https://www.textmagic.com/docs/api/send-sms/
// #r "System.Text.Json"
// #r "FSharp.Data"
open System
open System.IO
open System.Net
open System.Text.Json

let private smsClientUsername = "username"
let private smsClientApiKey = "key"

type SendMessageResponse = FSharp.Data.JsonProvider<"""[
    {
        "id": "49576009",
        "href": "/api/v2/messages/49576009",
        "type": "session",
        "sessionId": "34436600",
        "bulkId": "357122",
        "messageId": "49576009",
        "scheduleId": "313"
    },
    {
        "id": "49576009",
        "href": "/api/v2/messages/49576009",
        "type": "session"
    }
    ]""", SampleIsList = true>

let makePostRequestWithHeaders (reqType: PostRequestTypes) (url : string) (requestBody : string) (headers) =
    let req = WebRequest.CreateHttp url
    headers |> Seq.iter(fun (h:string,k:string) ->
        if not (String.IsNullOrEmpty h) then
            if h.ToLower() = "user-agent" then
                req.UserAgent <- k
            else
                req.Headers.Add(h,k)
    )
    req.CookieContainer <- new CookieContainer()
    req.Method <- "POST"
    let timeout = 8000
    req.Timeout <- timeout
    req.ProtocolVersion <- HttpVersion.Version10
    let postBytes = requestBody |> System.Text.Encoding.ASCII.GetBytes
    req.ContentLength <- postBytes.LongLength
    req.ContentType <- "application/json"
    let asynccall =
        async {
            let! res =
                async{
                    let! reqStream = req.GetRequestStreamAsync() |> Async.AwaitTask
                    do! reqStream.WriteAsync(postBytes, 0, postBytes.Length) |> Async.AwaitIAsyncResult |> Async.Ignore
                    reqStream.Close()
                    let! res =
                        async { // Async methods are not using req.Timeout
                            let! child = Async.StartChild(req.AsyncGetResponse(), timeout)
                            return! child
                        }
                    use stream = res.GetResponseStream()
                    use reader = new StreamReader(stream)
                    let! rdata = reader.ReadToEndAsync() |> Async.AwaitTask
                    return rdata
                } |> Async.Catch
            match res with
            | Choice1Of2 x -> return x, None
            | Choice2Of2 e ->
                match e with
                | :? WebException as wex when not(isNull wex.Response) ->
                    use stream = wex.Response.GetResponseStream()
                    use reader = new StreamReader(stream)
                    let err = reader.ReadToEnd()
                    return err, Some e
                | :? TimeoutException as e ->
                    return failwith(e.ToString())
                | _ ->
                    return failwith(e.ToString())
        }
    asynccall

let sendMessage (number:string, text:string) =
    task {
        if number.Contains "," || number.Length > 20 then
            return Result.Error "No batch sending supported."
        else
        // The specs tells to URL-encode, but in real life if you do it, it breaks the text.
        // So the specs has to be talking about something else than ApplicationJson,
        // but changing the type also breaks the system.
        //``text`` = HttpUtility.UrlEncode text
        let json = JsonSerializer.Serialize({|
            ``text`` = text
            ``phones`` = number
        |})

        match! makePostRequestWithHeaders ApplicationJson ("https://rest.textmagic.com/api/v2/messages") json [ "X-TM-Key", smsClientApiKey; "X-TM-Username", smsClientUsername ] with
        | responseJson, None ->
            let response = SendMessageResponse.Parse responseJson
            return Result.Ok response
        | _ , Some error ->
            return Result.Error (sprintf "Could not send SMS: %A" error)
    }

Comments (0)

HTTPS SSH

You can clone a snippet to your computer for local editing. Learn more.