-module(sendr_lettermint). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "src/sendr_lettermint.gleam"). -export([config/1, set_route/2, request/2, response/1]). -export_type([lettermint_error/0, lettermint_config/0]). -if(?OTP_RELEASE >= 27). -define(MODULEDOC(Str), -moduledoc(Str)). -define(DOC(Str), -doc(Str)). -else. -define(MODULEDOC(Str), -compile([])). -define(DOC(Str), -compile([])). -endif. ?MODULEDOC( " sendr_lettermint — Lettermint email backend for sendr.\n" "\n" " Builds HTTP `Request` values for the Lettermint send API and parses\n" " `Response` values returned by it.\n" ). -type lettermint_error() :: {invalid_uri, binary()} | {invalid_response, integer(), gleam@json:decode_error()} | {api_error, integer(), gleam@option:option(binary()), binary()}. -opaque lettermint_config() :: {lettermint_config, binary(), binary()}. -file("src/sendr_lettermint.gleam", 45). ?DOC(" Create a new `LettermintConfig` with the given API token.\n"). -spec config(binary()) -> lettermint_config(). config(Token) -> {lettermint_config, Token, <<""/utf8>>}. -file("src/sendr_lettermint.gleam", 52). ?DOC( " Set the route for this config.\n" "\n" " Routes control deliverability settings in your Lettermint account.\n" ). -spec set_route(lettermint_config(), binary()) -> lettermint_config(). set_route(Config, Route) -> {lettermint_config, erlang:element(2, Config), Route}. -file("src/sendr_lettermint.gleam", 296). -spec add(list({binary(), gleam@json:json()}), binary(), gleam@json:json()) -> list({binary(), gleam@json:json()}). add(Entries, Key, Value) -> lists:append(Entries, [{Key, Value}]). -file("src/sendr_lettermint.gleam", 304). -spec add_if_some( list({binary(), gleam@json:json()}), binary(), gleam@option:option(gleam@json:json()) ) -> list({binary(), gleam@json:json()}). add_if_some(Entries, Key, Value) -> case Value of {some, Value@1} -> add(Entries, Key, Value@1); none -> Entries end. -file("src/sendr_lettermint.gleam", 271). -spec attachment_to_json(sendr@message@attachment:attachment()) -> gleam@json:json(). attachment_to_json(Attachment) -> Filename@1 = case erlang:element(3, Attachment) of <<""/utf8>> -> erlang:element(4, Attachment); Filename -> Filename end, Content = gleam_stdlib:base64_encode(erlang:element(5, Attachment), true), Content_type = case erlang:element(2, Attachment) of <<""/utf8>> -> <<"application/octet-stream"/utf8>>; Ct -> Ct end, gleam@json:object( begin _pipe = [{<<"filename"/utf8>>, gleam@json:string(Filename@1)}, {<<"content"/utf8>>, gleam@json:string(Content)}, {<<"content_type"/utf8>>, gleam@json:string(Content_type)}], add_if_some(_pipe, <<"content_id"/utf8>>, case Attachment of {inlined_attachment, _, _, _, _, Cid} when Cid =/= <<""/utf8>> -> {some, gleam@json:string(Cid)}; _ -> none end) end ). -file("src/sendr_lettermint.gleam", 264). -spec mailbox_to_json(sendr@message@mailbox:mailbox()) -> gleam@json:json(). mailbox_to_json(Mailbox) -> case Mailbox of {mailbox, <<""/utf8>>, Address} -> gleam@json:string(Address); {mailbox, Name, Address@1} -> gleam@json:string( <<<<<>/binary, Address@1/binary>>/binary, ">"/utf8>> ) end. -file("src/sendr_lettermint.gleam", 232). -spec build_body(sendr@message:message(), lettermint_config()) -> gleam@json:json(). build_body(Message, Config) -> _pipe = [], _pipe@1 = add_if_some( _pipe, <<"route"/utf8>>, case erlang:element(3, Config) of <<""/utf8>> -> none; Route -> {some, gleam@json:string(Route)} end ), _pipe@2 = add_if_some( _pipe@1, <<"from"/utf8>>, gleam@option:map(erlang:element(2, Message), fun mailbox_to_json/1) ), _pipe@3 = add_if_some( _pipe@2, <<"reply_to"/utf8>>, gleam@option:map( erlang:element(3, Message), fun(Mailboxes) -> gleam@json:array(Mailboxes, fun mailbox_to_json/1) end ) ), _pipe@4 = add_if_some( _pipe@3, <<"to"/utf8>>, gleam@option:map( erlang:element(4, Message), fun(_capture) -> gleam@json:array(_capture, fun mailbox_to_json/1) end ) ), _pipe@5 = add_if_some( _pipe@4, <<"cc"/utf8>>, gleam@option:map( erlang:element(5, Message), fun(_capture@1) -> gleam@json:array(_capture@1, fun mailbox_to_json/1) end ) ), _pipe@6 = add_if_some( _pipe@5, <<"bcc"/utf8>>, gleam@option:map( erlang:element(6, Message), fun(_capture@2) -> gleam@json:array(_capture@2, fun mailbox_to_json/1) end ) ), _pipe@7 = add_if_some( _pipe@6, <<"subject"/utf8>>, gleam@option:map(erlang:element(7, Message), fun gleam@json:string/1) ), _pipe@8 = add_if_some( _pipe@7, <<"text"/utf8>>, gleam@option:map( erlang:element(2, erlang:element(9, Message)), fun gleam@json:string/1 ) ), _pipe@9 = add_if_some( _pipe@8, <<"html"/utf8>>, gleam@option:map( erlang:element(3, erlang:element(9, Message)), fun gleam@json:string/1 ) ), _pipe@10 = add( _pipe@9, <<"attachments"/utf8>>, gleam@json:array(erlang:element(8, Message), fun attachment_to_json/1) ), gleam@json:object(_pipe@10). -file("src/sendr_lettermint.gleam", 219). -spec validate_body(sendr@message:message()) -> {ok, nil} | {error, sendr:sendr_error(any())}. validate_body(Message) -> Text_length = begin _pipe = erlang:element(2, erlang:element(9, Message)), gleam@option:map(_pipe, fun string:length/1) end, Html_length = begin _pipe@1 = erlang:element(3, erlang:element(9, Message)), gleam@option:map(_pipe@1, fun string:length/1) end, case {Text_length, Html_length} of {none, none} -> {error, {invalid_body, no_body}}; {{some, 0}, {some, 0}} -> {error, {invalid_body, no_body}}; {{some, Length}, _} when Length < 3 -> {error, {invalid_body, {text_to_short, 3, Length}}}; {_, {some, Length@1}} when Length@1 < 3 -> {error, {invalid_body, {html_to_short, 3, Length@1}}}; {_, _} -> {ok, nil} end. -file("src/sendr_lettermint.gleam", 205). -spec validate_attachments(sendr@message:message()) -> {ok, nil} | {error, sendr:sendr_error(any())}. validate_attachments(Message) -> gleam@list:try_each( erlang:element(8, Message), fun(Attachment) -> case {erlang:element(3, Attachment), erlang:element(4, Attachment)} of {<<""/utf8>>, <<""/utf8>>} -> {error, {invalid_attachment, required_filename_missing, Attachment}}; {_, _} -> case Attachment of {inlined_attachment, _, _, _, _, <<""/utf8>>} -> {error, {invalid_attachment, required_content_id_missing, Attachment}}; _ -> {ok, nil} end end end ). -file("src/sendr_lettermint.gleam", 194). -spec validate_subject(sendr@message:message()) -> {ok, nil} | {error, sendr:sendr_error(any())}. validate_subject(Message) -> Subject_length = begin _pipe = erlang:element(7, Message), gleam@option:map(_pipe, fun string:length/1) end, case Subject_length of none -> {error, {required_field_missing, subject}}; {some, 0} -> {error, {required_field_missing, subject}}; {some, Length} when Length > 998 -> {error, {field_exceeds_maximum_length, subject, 998, Length}}; _ -> {ok, nil} end. -file("src/sendr_lettermint.gleam", 184). -spec validate_mailbox(sendr@message@mailbox:mailbox(), sendr:field()) -> {ok, nil} | {error, sendr:sendr_error(any())}. validate_mailbox(Mailbox, Field) -> case gleam@string:split_once(erlang:element(3, Mailbox), <<"@"/utf8>>) of {ok, {Local, Domain}} when (Local =/= <<""/utf8>>) andalso (Domain =/= <<""/utf8>>) -> {ok, nil}; _ -> {error, {invalid_mailbox, Field, Mailbox}} end. -file("src/sendr_lettermint.gleam", 155). -spec validate_recipients(sendr@message:message()) -> {ok, nil} | {error, sendr:sendr_error(any())}. validate_recipients(Message) -> Recipients = begin _pipe = [erlang:element(4, Message), erlang:element(5, Message), erlang:element(6, Message)], _pipe@1 = gleam@option:values(_pipe), lists:append(_pipe@1) end, Validate = fun(Mailboxes, Field) -> _pipe@2 = Mailboxes, _pipe@3 = gleam@option:unwrap(_pipe@2, []), gleam@list:try_each( _pipe@3, fun(_capture) -> validate_mailbox(_capture, Field) end ) end, case Recipients of [] -> {error, no_recipients}; _ -> _pipe@4 = Validate(erlang:element(6, Message), bcc), _pipe@5 = gleam@result:'or'( _pipe@4, Validate(erlang:element(5, Message), cc) ), gleam@result:'or'( _pipe@5, begin _pipe@6 = Validate(erlang:element(4, Message), to), gleam@result:'try'( _pipe@6, fun(_) -> case gleam@option:unwrap( erlang:element(4, Message), [] ) of [] -> {error, {required_field_missing, to}}; _ -> {ok, nil} end end ) end ) end. -file("src/sendr_lettermint.gleam", 146). -spec validate_reply_to(sendr@message:message()) -> {ok, nil} | {error, sendr:sendr_error(any())}. validate_reply_to(Message) -> case erlang:element(3, Message) of none -> {ok, nil}; {some, []} -> {ok, nil}; {some, Mailboxes} -> _pipe = gleam@list:try_each( Mailboxes, fun(_capture) -> validate_mailbox(_capture, reply_to) end ), gleam@result:replace(_pipe, nil) end. -file("src/sendr_lettermint.gleam", 139). -spec validate_from(sendr@message:message()) -> {ok, nil} | {error, sendr:sendr_error(any())}. validate_from(Message) -> case erlang:element(2, Message) of none -> {error, {required_field_missing, from}}; {some, Mailbox} -> validate_mailbox(Mailbox, from) end. -file("src/sendr_lettermint.gleam", 63). ?DOC( " Build an HTTP `Request` for the Lettermint send API from a sendr `Message`.\n" "\n" " Validates the message (from, reply-to, recipients, subject, attachments)\n" " and returns `Error(SendrError(LettermintError))` on validation failure.\n" ). -spec request(sendr@message:message(), lettermint_config()) -> {ok, gleam@http@request:request(binary())} | {error, sendr:sendr_error(lettermint_error())}. request(Message, Config) -> gleam@result:'try'( validate_from(Message), fun(_) -> gleam@result:'try'( validate_reply_to(Message), fun(_) -> gleam@result:'try'( validate_recipients(Message), fun(_) -> gleam@result:'try'( validate_subject(Message), fun(_) -> gleam@result:'try'( validate_attachments(Message), fun(_) -> gleam@result:'try'( validate_body(Message), fun(_) -> Body = build_body( Message, Config ), _pipe = <<"https://api.lettermint.co/v1/send"/utf8>>, _pipe@1 = gleam_stdlib:uri_parse( _pipe ), _pipe@2 = gleam@result:'try'( _pipe@1, fun gleam@http@request:from_uri/1 ), _pipe@3 = gleam@result:replace_error( _pipe@2, {invalid_uri, <<"https://api.lettermint.co/v1/send"/utf8>>} ), _pipe@8 = gleam@result:map( _pipe@3, fun(Req) -> _pipe@4 = Req, _pipe@5 = gleam@http@request:set_method( _pipe@4, post ), _pipe@6 = gleam@http@request:set_header( _pipe@5, <<"content-type"/utf8>>, <<"application/json"/utf8>> ), _pipe@7 = gleam@http@request:set_header( _pipe@6, <<"x-lettermint-token"/utf8>>, erlang:element( 2, Config ) ), gleam@http@request:set_body( _pipe@7, gleam@json:to_string( Body ) ) end ), gleam@result:map_error( _pipe@8, fun(Field@0) -> {backend_error, Field@0} end ) end ) end ) end ) end ) end ) end ). -file("src/sendr_lettermint.gleam", 95). ?DOC( " Parse a `Response` from the Lettermint send API.\n" "\n" " On HTTP 202 the response body is decoded and the `message_id` value\n" " is returned. On HTTP 422 the `detail` array is decoded into\n" " `ApiErrorDetail` entries. All other statuses are treated as generic\n" " API errors.\n" ). -spec response(gleam@http@response:response(binary())) -> {ok, binary()} | {error, sendr:sendr_error(lettermint_error())}. response(Response) -> case erlang:element(2, Response) of 202 = Status -> Success_decoder = begin gleam@dynamic@decode:field( <<"message_id"/utf8>>, {decoder, fun gleam@dynamic@decode:decode_string/1}, fun(Id) -> gleam@dynamic@decode:success(Id) end ) end, _pipe = gleam@json:parse( erlang:element(4, Response), Success_decoder ), gleam@result:map_error( _pipe, fun(Error) -> {backend_error, {invalid_response, Status, Error}} end ); 422 = Status@1 -> Error_decoder = begin gleam@dynamic@decode:field( <<"error"/utf8>>, begin gleam@dynamic@decode:field( <<"code"/utf8>>, {decoder, fun gleam@dynamic@decode:decode_string/1}, fun(Code) -> gleam@dynamic@decode:field( <<"message"/utf8>>, {decoder, fun gleam@dynamic@decode:decode_string/1}, fun(Message) -> gleam@dynamic@decode:success( {api_error, Status@1, {some, Code}, Message} ) end ) end ) end, fun(Error@1) -> gleam@dynamic@decode:success(Error@1) end ) end, case gleam@json:parse(erlang:element(4, Response), Error_decoder) of {ok, Api_error} -> {error, {backend_error, Api_error}}; {error, Error@2} -> {error, {backend_error, {invalid_response, Status@1, Error@2}}} end; Status@2 -> Error_decoder@1 = begin gleam@dynamic@decode:field( <<"message"/utf8>>, {decoder, fun gleam@dynamic@decode:decode_string/1}, fun(Message@1) -> gleam@dynamic@decode:success( {api_error, Status@2, none, Message@1} ) end ) end, case gleam@json:parse(erlang:element(4, Response), Error_decoder@1) of {ok, Api_error@1} -> {error, {backend_error, Api_error@1}}; {error, Error@3} -> {error, {backend_error, {invalid_response, Status@2, Error@3}}} end end.