Skip to main content

src/internal@protocol.erl

-module(internal@protocol).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/internal/protocol.gleam").
-export([start_session/2]).
-export_type([sasl_mechanism/0, extensions/0, argument_error/0, protocol_error/0, action/0, protocol_config/0, receiver/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(" Implementation of https://www.rfc-editor.org/rfc/rfc5321.html\n").

-type sasl_mechanism() :: cram_md5 | login | plain | x_oauth2.

-type extensions() :: {extensions,
        integer(),
        internal@encoder@encoding:encoding_mode(),
        internal@encoder@encoding:encoding_mode(),
        boolean(),
        list(sasl_mechanism())}.

-type argument_error() :: {encoding_error,
        internal@encoder@encoding:encoder_error()} |
    {invalid_address, binary()} |
    no_from_mailbox_specified |
    no_recipients_specified |
    {data_size_error, integer(), integer()} |
    {data_render_error, internal@renderer@internet_message:render_error()}.

-type protocol_error() :: {invalid_response, binary()} |
    {unexpected_response_code, integer(), integer()} |
    {invalid_request_error, argument_error()}.

-type action() :: {send,
        binary(),
        fun(() -> {ok, action()} | {error, protocol_error()})} |
    {'receive', fun((binary()) -> {ok, action()} | {error, protocol_error()})} |
    {upgrade, fun(() -> {ok, action()} | {error, protocol_error()})} |
    done.

-type protocol_config() :: {protocol_config,
        binary(),
        gleam@option:option({binary(), binary()})}.

-type receiver() :: {receiver,
        integer(),
        fun((list(binary())) -> {ok, list(binary())} | {error, protocol_error()}),
        fun((protocol_error()) -> {ok, action()} | {error, protocol_error()})}.

-file("src/internal/protocol.gleam", 577).
-spec 'receive'(integer()) -> receiver().
'receive'(Expected_code) ->
    {receiver,
        Expected_code,
        fun(Field@0) -> {ok, Field@0} end,
        fun(Field@0) -> {error, Field@0} end}.

-file("src/internal/protocol.gleam", 628).
-spec parse_response(binary()) -> {ok, {boolean(), integer(), binary()}} |
    {error, protocol_error()}.
parse_response(Response) ->
    _pipe@2 = begin
        gleam@result:'try'(
            gleam_stdlib:parse_int(gleam@string:slice(Response, 0, 3)),
            fun(Response_code) ->
                gleam@result:map(case gleam@string:slice(Response, 3, 1) of
                        <<" "/utf8>> ->
                            {ok, false};

                        <<""/utf8>> ->
                            {ok, false};

                        <<"-"/utf8>> ->
                            {ok, true};

                        _ ->
                            {error, nil}
                    end, fun(Need_more) ->
                        {Need_more,
                            Response_code,
                            begin
                                _pipe = Response,
                                _pipe@1 = gleam@string:drop_start(_pipe, 4),
                                gleam_stdlib:string_remove_suffix(
                                    _pipe@1,
                                    <<"\r\n"/utf8>>
                                )
                            end}
                    end)
            end
        )
    end,
    gleam@result:replace_error(_pipe@2, {invalid_response, Response}).

-file("src/internal/protocol.gleam", 581).
-spec do_receive(
    integer(),
    {ok, list(binary())} | {error, protocol_error()},
    fun((list(binary())) -> {ok, list(binary())} | {error, protocol_error()}),
    fun((protocol_error()) -> {ok, action()} | {error, protocol_error()}),
    fun((list(binary())) -> {ok, action()} | {error, protocol_error()})
) -> {ok, action()} | {error, protocol_error()}.
do_receive(Expected_code, Accumulator, Mapper, Recover, Next) ->
    Callback = fun(Response) -> case {parse_response(Response), Accumulator} of
            {{ok, {true, _, _}}, {error, _} = Error} ->
                do_receive(Expected_code, Error, Mapper, Recover, Next);

            {{ok, {false, _, _}}, {error, Error@1}} ->
                Recover(Error@1);

            {{ok, {true, Response_code, _}}, {ok, _}} when Response_code =/= Expected_code ->
                do_receive(
                    Expected_code,
                    {error,
                        {unexpected_response_code, Expected_code, Response_code}},
                    Mapper,
                    Recover,
                    Next
                );

            {{ok, {false, Response_code@1, _}}, {ok, _}} when Response_code@1 =/= Expected_code ->
                Recover(
                    {unexpected_response_code, Expected_code, Response_code@1}
                );

            {{ok, {true, _, Message}}, {ok, Messages}} ->
                do_receive(
                    Expected_code,
                    {ok, [Message | Messages]},
                    Mapper,
                    Recover,
                    Next
                );

            {{ok, {false, _, Message@1}}, {ok, Messages@1}} ->
                _pipe = lists:reverse([Message@1 | Messages@1]),
                _pipe@1 = Mapper(_pipe),
                gleam@result:'try'(_pipe@1, Next);

            {{error, Error@2}, _} ->
                Recover(Error@2)
        end end,
    {ok, {'receive', Callback}}.

-file("src/internal/protocol.gleam", 531).
-spec ignore(receiver(), fun(() -> {ok, action()} | {error, protocol_error()})) -> {ok,
        action()} |
    {error, protocol_error()}.
ignore(Receiver, Next) ->
    do_receive(
        erlang:element(2, Receiver),
        {ok, []},
        erlang:element(3, Receiver),
        erlang:element(4, Receiver),
        fun(_) -> Next() end
    ).

-file("src/internal/protocol.gleam", 524).
-spec send(binary(), fun(() -> {ok, action()} | {error, protocol_error()})) -> {ok,
        action()} |
    {error, protocol_error()}.
send(Command, Next) ->
    {ok, {send, <<Command/binary, "\r\n"/utf8>>, Next}}.

-file("src/internal/protocol.gleam", 512).
-spec quit() -> {ok, action()} | {error, protocol_error()}.
quit() ->
    send(
        <<"QUIT"/utf8>>,
        fun() -> ignore('receive'(221), fun() -> {ok, done} end) end
    ).

-file("src/internal/protocol.gleam", 495).
-spec do_mail_data(
    list(binary()),
    fun(() -> {ok, action()} | {error, protocol_error()})
) -> {ok, action()} | {error, protocol_error()}.
do_mail_data(Data, Next) ->
    case Data of
        [Data@1 | Rest_data] ->
            Data@2 = case gleam_stdlib:string_starts_with(Data@1, <<"."/utf8>>) of
                true ->
                    <<"."/utf8, Data@1/binary>>;

                false ->
                    Data@1
            end,
            send(Data@2, fun() -> do_mail_data(Rest_data, Next) end);

        [] ->
            Next()
    end.

-file("src/internal/protocol.gleam", 464).
-spec mail_data(
    sendr@message:message(),
    extensions(),
    fun(() -> {ok, action()} | {error, protocol_error()})
) -> {ok, action()} | {error, protocol_error()}.
mail_data(Message, Extensions, Next) ->
    Data = begin
        _pipe = Message,
        _pipe@1 = internal@renderer@internet_message:encode(
            _pipe,
            erlang:element(4, Extensions)
        ),
        _pipe@2 = gleam@result:map_error(
            _pipe@1,
            fun(Field@0) -> {data_render_error, Field@0} end
        ),
        _pipe@3 = gleam@result:'try'(
            _pipe@2,
            fun(Body) ->
                Body_size = gleam@list:fold(
                    Body,
                    0,
                    fun(Accumulator, Line) ->
                        Accumulator + erlang:byte_size(Line)
                    end
                ),
                case erlang:element(2, Extensions) of
                    Size when (Size =:= 0) orelse (Body_size =< Size) ->
                        {ok, Body};

                    Size@1 ->
                        {error, {data_size_error, Body_size, Size@1}}
                end
            end
        ),
        gleam@result:map_error(
            _pipe@3,
            fun(Field@0) -> {invalid_request_error, Field@0} end
        )
    end,
    gleam@result:'try'(
        Data,
        fun(Data@1) ->
            send(
                <<"DATA"/utf8>>,
                fun() ->
                    ignore(
                        'receive'(354),
                        fun() ->
                            do_mail_data(
                                Data@1,
                                fun() ->
                                    send(
                                        <<"."/utf8>>,
                                        fun() ->
                                            ignore(
                                                'receive'(250),
                                                fun() -> Next() end
                                            )
                                        end
                                    )
                                end
                            )
                        end
                    )
                end
            )
        end
    ).

-file("src/internal/protocol.gleam", 450).
-spec do_mail_recipients(
    list(binary()),
    fun(() -> {ok, action()} | {error, protocol_error()})
) -> {ok, action()} | {error, protocol_error()}.
do_mail_recipients(Addresses, Next) ->
    case Addresses of
        [Address | Rest_addresses] ->
            send(
                <<<<"RCPT TO:<"/utf8, Address/binary>>/binary, ">"/utf8>>,
                fun() ->
                    ignore(
                        'receive'(250),
                        fun() -> do_mail_recipients(Rest_addresses, Next) end
                    )
                end
            );

        [] ->
            Next()
    end.

-file("src/internal/protocol.gleam", 664).
-spec is_ascii_string(binary()) -> boolean().
is_ascii_string(String) ->
    _pipe = String,
    _pipe@1 = gleam@string:split(_pipe, <<""/utf8>>),
    gleam@list:all(
        _pipe@1,
        fun(Char) ->
            (gleam@string:compare(Char, <<"\x{1f}"/utf8>>) =:= gt) andalso (gleam@string:compare(
                Char,
                <<"\x{7f}"/utf8>>
            )
            =:= lt)
        end
    ).

-file("src/internal/protocol.gleam", 647).
-spec validate_mailbox(sendr@message@mailbox:mailbox(), extensions()) -> {ok,
        sendr@message@mailbox:mailbox()} |
    {error, argument_error()}.
validate_mailbox(Mailbox, Extensions) ->
    case gleam@string:split_once(erlang:element(3, Mailbox), <<"@"/utf8>>) of
        {ok, {Local, _}} ->
            case (erlang:element(3, Extensions) =:= utf8) orelse is_ascii_string(
                Local
            ) of
                true ->
                    {ok, Mailbox};

                false ->
                    {error, {invalid_address, erlang:element(3, Mailbox)}}
            end;

        {error, _} ->
            {error, {invalid_address, erlang:element(3, Mailbox)}}
    end.

-file("src/internal/protocol.gleam", 362).
-spec recipients(sendr@message:message()) -> list(sendr@message@mailbox:mailbox()).
recipients(Message) ->
    _pipe = [erlang:element(4, Message),
        erlang:element(5, Message),
        erlang:element(6, Message)],
    _pipe@1 = gleam@option:values(_pipe),
    lists:append(_pipe@1).

-file("src/internal/protocol.gleam", 418).
-spec mail_recipients(
    sendr@message:message(),
    extensions(),
    fun(() -> {ok, action()} | {error, protocol_error()})
) -> {ok, action()} | {error, protocol_error()}.
mail_recipients(Message, Extensions, Next) ->
    Addresses = begin
        _pipe = Message,
        _pipe@1 = recipients(_pipe),
        _pipe@6 = gleam@list:map(_pipe@1, fun(Mailbox) -> _pipe@2 = Mailbox,
                _pipe@3 = validate_mailbox(_pipe@2, Extensions),
                _pipe@5 = gleam@result:'try'(
                    _pipe@3,
                    fun(Mailbox@1) ->
                        _pipe@4 = internal@encoder@idna:encode_email_address(
                            erlang:element(3, Mailbox@1),
                            erlang:element(3, Extensions)
                        ),
                        gleam@result:map_error(
                            _pipe@4,
                            fun(Field@0) -> {encoding_error, Field@0} end
                        )
                    end
                ),
                gleam@result:map_error(
                    _pipe@5,
                    fun(Field@0) -> {invalid_request_error, Field@0} end
                ) end),
        _pipe@7 = gleam@result:all(_pipe@6),
        gleam@result:'try'(_pipe@7, fun(Recipients) -> case Recipients of
                    [] ->
                        {error,
                            {invalid_request_error, no_recipients_specified}};

                    _ ->
                        {ok, Recipients}
                end end)
    end,
    gleam@result:'try'(
        Addresses,
        fun(Addresses@1) -> do_mail_recipients(Addresses@1, Next) end
    ).

-file("src/internal/protocol.gleam", 379).
-spec mail_from(
    sendr@message:message(),
    extensions(),
    fun(() -> {ok, action()} | {error, protocol_error()})
) -> {ok, action()} | {error, protocol_error()}.
mail_from(Message, Extensions, Next) ->
    Smtp_utf8 = case erlang:element(3, Extensions) of
        ascii ->
            none;

        utf8 ->
            {some, <<"SMTPUTF8"/utf8>>}
    end,
    Body_8bitmime = case erlang:element(4, Extensions) of
        ascii ->
            none;

        utf8 ->
            {some, <<"BODY=8BITMIME"/utf8>>}
    end,
    Address@1 = begin
        _pipe = erlang:element(2, Message),
        _pipe@1 = gleam@option:to_result(_pipe, no_from_mailbox_specified),
        _pipe@2 = gleam@result:'try'(
            _pipe@1,
            fun(_capture) -> validate_mailbox(_capture, Extensions) end
        ),
        _pipe@7 = gleam@result:'try'(
            _pipe@2,
            fun(Mailbox) ->
                _pipe@3 = internal@encoder@idna:encode_email_address(
                    erlang:element(3, Mailbox),
                    erlang:element(3, Extensions)
                ),
                _pipe@6 = gleam@result:map(
                    _pipe@3,
                    fun(Address) ->
                        _pipe@4 = [{some,
                                <<<<"<"/utf8, Address/binary>>/binary,
                                    ">"/utf8>>},
                            Smtp_utf8,
                            Body_8bitmime],
                        _pipe@5 = gleam@option:values(_pipe@4),
                        gleam@string:join(_pipe@5, <<" "/utf8>>)
                    end
                ),
                gleam@result:map_error(
                    _pipe@6,
                    fun(Field@0) -> {encoding_error, Field@0} end
                )
            end
        ),
        gleam@result:map_error(
            _pipe@7,
            fun(Field@0) -> {invalid_request_error, Field@0} end
        )
    end,
    gleam@result:'try'(
        Address@1,
        fun(Address@2) ->
            send(
                <<"MAIL FROM:"/utf8, Address@2/binary>>,
                fun() -> ignore('receive'(250), fun() -> Next() end) end
            )
        end
    ).

-file("src/internal/protocol.gleam", 368).
-spec mail_transaction(
    extensions(),
    sendr@message:message(),
    fun(() -> {ok, action()} | {error, protocol_error()})
) -> {ok, action()} | {error, protocol_error()}.
mail_transaction(Extensions, Message, Next) ->
    mail_from(
        Message,
        Extensions,
        fun() ->
            mail_recipients(
                Message,
                Extensions,
                fun() -> mail_data(Message, Extensions, fun() -> Next() end) end
            )
        end
    ).

-file("src/internal/protocol.gleam", 285).
-spec authenticate_xoauth2(
    {binary(), binary()},
    fun(() -> {ok, action()} | {error, protocol_error()})
) -> {ok, action()} | {error, protocol_error()}.
authenticate_xoauth2(Credentials, Next) ->
    {Username, Token} = Credentials,
    Encoded = gleam_stdlib:base64_encode(
        <<"user="/utf8,
            Username/binary,
            1,
            "auth=Bearer "/utf8,
            Token/binary,
            1,
            1>>,
        true
    ),
    send(
        <<"AUTH XOAUTH2 "/utf8, Encoded/binary>>,
        fun() -> ignore('receive'(235), fun() -> Next() end) end
    ).

-file("src/internal/protocol.gleam", 272).
-spec authenticate_plain(
    {binary(), binary()},
    fun(() -> {ok, action()} | {error, protocol_error()})
) -> {ok, action()} | {error, protocol_error()}.
authenticate_plain(Credentials, Next) ->
    {Username, Password} = Credentials,
    Encoded = gleam_stdlib:base64_encode(
        <<0, Username/binary, 0, Password/binary>>,
        true
    ),
    send(
        <<"AUTH PLAIN "/utf8, Encoded/binary>>,
        fun() -> ignore('receive'(235), fun() -> Next() end) end
    ).

-file("src/internal/protocol.gleam", 544).
-spec check(
    receiver(),
    list(binary()),
    fun(() -> {ok, action()} | {error, protocol_error()})
) -> {ok, action()} | {error, protocol_error()}.
check(Receiver, Valid_responses, Next) ->
    Mapper = fun(Response) ->
        case gleam@list:all(
            Response,
            fun(_capture) -> gleam@list:contains(Valid_responses, _capture) end
        ) of
            false ->
                {error,
                    {invalid_response,
                        gleam@string:join(Response, <<"\n"/utf8>>)}};

            true ->
                {ok, Response}
        end
    end,
    do_receive(
        erlang:element(2, Receiver),
        {ok, []},
        Mapper,
        erlang:element(4, Receiver),
        fun(_) -> Next() end
    ).

-file("src/internal/protocol.gleam", 255).
-spec authenticate_login(
    {binary(), binary()},
    fun(() -> {ok, action()} | {error, protocol_error()})
) -> {ok, action()} | {error, protocol_error()}.
authenticate_login(Credentials, Next) ->
    {Username, Password} = Credentials,
    Encoded_username = gleam_stdlib:base64_encode(<<Username/binary>>, true),
    Encoded_password = gleam_stdlib:base64_encode(<<Password/binary>>, true),
    send(
        <<"AUTH LOGIN"/utf8>>,
        fun() ->
            check(
                'receive'(334),
                [<<"VXNlcm5hbWU6"/utf8>>, <<"dXNlcm5hbWU6"/utf8>>],
                fun() ->
                    send(
                        Encoded_username,
                        fun() ->
                            check(
                                'receive'(334),
                                [<<"UGFzc3dvcmQ6"/utf8>>,
                                    <<"cGFzc3dvcmQ6"/utf8>>],
                                fun() ->
                                    send(
                                        Encoded_password,
                                        fun() ->
                                            ignore(
                                                'receive'(235),
                                                fun() -> Next() end
                                            )
                                        end
                                    )
                                end
                            )
                        end
                    )
                end
            )
        end
    ).

-file("src/internal/protocol.gleam", 561).
-spec map(
    receiver(),
    fun((list(binary())) -> {ok, list(binary())} | {error, protocol_error()}),
    fun((list(binary())) -> {ok, action()} | {error, protocol_error()})
) -> {ok, action()} | {error, protocol_error()}.
map(Receiver, Mapper, Next) ->
    do_receive(
        erlang:element(2, Receiver),
        {ok, []},
        Mapper,
        erlang:element(4, Receiver),
        Next
    ).

-file("src/internal/protocol.gleam", 226).
-spec authenticate_cram_md5(
    {binary(), binary()},
    fun(() -> {ok, action()} | {error, protocol_error()})
) -> {ok, action()} | {error, protocol_error()}.
authenticate_cram_md5(Credentials, Next) ->
    {Username, Password} = Credentials,
    Encode_credentials = fun(Challenge) ->
        Challenge@1 = gleam@string:join(Challenge, <<""/utf8>>),
        _pipe = Challenge@1,
        _pipe@1 = gleam@bit_array:base64_decode(_pipe),
        _pipe@2 = gleam@result:map(
            _pipe@1,
            fun(_capture) ->
                gleam_crypto_ffi:hmac(_capture, md5, <<Password/binary>>)
            end
        ),
        _pipe@3 = gleam@result:map(_pipe@2, fun gleam_stdlib:base16_encode/1),
        _pipe@4 = gleam@result:map(_pipe@3, fun string:lowercase/1),
        _pipe@5 = gleam@result:map(
            _pipe@4,
            fun(_capture@1) ->
                gleam@string:append(<<Username/binary, " "/utf8>>, _capture@1)
            end
        ),
        _pipe@6 = gleam@result:map(
            _pipe@5,
            fun(Response) ->
                gleam_stdlib:base64_encode(<<Response/binary>>, true)
            end
        ),
        _pipe@7 = gleam@result:map(
            _pipe@6,
            fun(Encoded_credentials) -> [Encoded_credentials] end
        ),
        gleam@result:replace_error(_pipe@7, {invalid_response, Challenge@1})
    end,
    send(
        <<"AUTH CRAM-MD5"/utf8>>,
        fun() ->
            map(
                'receive'(334),
                Encode_credentials,
                fun(Encoded_credentials@1) ->
                    send(
                        gleam@string:join(Encoded_credentials@1, <<""/utf8>>),
                        fun() -> ignore('receive'(235), fun() -> Next() end) end
                    )
                end
            )
        end
    ).

-file("src/internal/protocol.gleam", 212).
-spec authenticate(
    extensions(),
    gleam@option:option({binary(), binary()}),
    fun(() -> {ok, action()} | {error, protocol_error()})
) -> {ok, action()} | {error, protocol_error()}.
authenticate(Extensions, Credentials, Next) ->
    case {Credentials, erlang:element(6, Extensions)} of
        {none, _} ->
            Next();

        {{some, _}, []} ->
            Next();

        {{some, Credentials@1}, [cram_md5 | _]} ->
            authenticate_cram_md5(Credentials@1, Next);

        {{some, Credentials@2}, [login | _]} ->
            authenticate_login(Credentials@2, Next);

        {{some, Credentials@3}, [plain | _]} ->
            authenticate_plain(Credentials@3, Next);

        {{some, Credentials@4}, [x_oauth2 | _]} ->
            authenticate_xoauth2(Credentials@4, Next)
    end.

-file("src/internal/protocol.gleam", 518).
-spec upgrade(fun(() -> {ok, action()} | {error, protocol_error()})) -> {ok,
        action()} |
    {error, protocol_error()}.
upgrade(Next) ->
    {ok, {upgrade, Next}}.

-file("src/internal/protocol.gleam", 194).
-spec start_tls(
    extensions(),
    fun((fun((extensions()) -> {ok, action()} | {error, protocol_error()})) -> {ok,
            action()} |
        {error, protocol_error()}),
    fun((extensions()) -> {ok, action()} | {error, protocol_error()})
) -> {ok, action()} | {error, protocol_error()}.
start_tls(Extensions, Initiate_client, Next) ->
    case erlang:element(5, Extensions) of
        false ->
            Next(Extensions);

        true ->
            send(
                <<"STARTTLS"/utf8>>,
                fun() ->
                    ignore(
                        'receive'(220),
                        fun() ->
                            upgrade(
                                fun() ->
                                    Initiate_client(
                                        fun(Extensions@1) ->
                                            Next(Extensions@1)
                                        end
                                    )
                                end
                            )
                        end
                    )
                end
            )
    end.

-file("src/internal/protocol.gleam", 159).
-spec initiate_server(fun(() -> {ok, action()} | {error, protocol_error()})) -> {ok,
        action()} |
    {error, protocol_error()}.
initiate_server(Next) ->
    ignore('receive'(220), fun() -> Next() end).

-file("src/internal/protocol.gleam", 569).
-spec recover(
    receiver(),
    fun((protocol_error()) -> {ok, action()} | {error, protocol_error()}),
    fun((list(binary())) -> {ok, action()} | {error, protocol_error()})
) -> {ok, action()} | {error, protocol_error()}.
recover(Receiver, Recover, Next) ->
    do_receive(
        erlang:element(2, Receiver),
        {ok, []},
        erlang:element(3, Receiver),
        Recover,
        Next
    ).

-file("src/internal/protocol.gleam", 301).
-spec parse_extensions(list(binary()), boolean(), boolean()) -> extensions().
parse_extensions(Extensions, Has_smtputf8, Has_8bitmime) ->
    _pipe = Extensions,
    _pipe@1 = gleam@list:drop(_pipe, 1),
    gleam@list:fold(
        _pipe@1,
        {extensions, 0, ascii, ascii, false, []},
        fun(Extensions@1, Line) -> case string:uppercase(Line) of
                <<"8BITMIME"/utf8>> when Has_8bitmime ->
                    {extensions,
                        erlang:element(2, Extensions@1),
                        erlang:element(3, Extensions@1),
                        utf8,
                        erlang:element(5, Extensions@1),
                        erlang:element(6, Extensions@1)};

                <<"AUTH "/utf8, Mechanisms/binary>> ->
                    _pipe@2 = Mechanisms,
                    _pipe@3 = gleam@string:split(_pipe@2, <<" "/utf8>>),
                    gleam@list:fold(
                        _pipe@3,
                        Extensions@1,
                        fun(Extensions@2, Mechanism) -> case Mechanism of
                                <<"CRAM-MD5"/utf8>> ->
                                    {extensions,
                                        erlang:element(2, Extensions@2),
                                        erlang:element(3, Extensions@2),
                                        erlang:element(4, Extensions@2),
                                        erlang:element(5, Extensions@2),
                                        lists:append(
                                            erlang:element(6, Extensions@2),
                                            [cram_md5]
                                        )};

                                <<"LOGIN"/utf8>> ->
                                    {extensions,
                                        erlang:element(2, Extensions@2),
                                        erlang:element(3, Extensions@2),
                                        erlang:element(4, Extensions@2),
                                        erlang:element(5, Extensions@2),
                                        lists:append(
                                            erlang:element(6, Extensions@2),
                                            [login]
                                        )};

                                <<"PLAIN"/utf8>> ->
                                    {extensions,
                                        erlang:element(2, Extensions@2),
                                        erlang:element(3, Extensions@2),
                                        erlang:element(4, Extensions@2),
                                        erlang:element(5, Extensions@2),
                                        lists:append(
                                            erlang:element(6, Extensions@2),
                                            [plain]
                                        )};

                                <<"XOAUTH2"/utf8>> ->
                                    {extensions,
                                        erlang:element(2, Extensions@2),
                                        erlang:element(3, Extensions@2),
                                        erlang:element(4, Extensions@2),
                                        erlang:element(5, Extensions@2),
                                        lists:append(
                                            erlang:element(6, Extensions@2),
                                            [x_oauth2]
                                        )};

                                _ ->
                                    Extensions@2
                            end end
                    );

                <<"SIZE "/utf8, Size/binary>> ->
                    {extensions,
                        gleam@result:unwrap(gleam_stdlib:parse_int(Size), 0),
                        erlang:element(3, Extensions@1),
                        erlang:element(4, Extensions@1),
                        erlang:element(5, Extensions@1),
                        erlang:element(6, Extensions@1)};

                <<"SMTPUTF8"/utf8>> when Has_smtputf8 ->
                    {extensions,
                        erlang:element(2, Extensions@1),
                        utf8,
                        erlang:element(4, Extensions@1),
                        erlang:element(5, Extensions@1),
                        erlang:element(6, Extensions@1)};

                <<"STARTTLS"/utf8>> ->
                    {extensions,
                        erlang:element(2, Extensions@1),
                        erlang:element(3, Extensions@1),
                        erlang:element(4, Extensions@1),
                        true,
                        erlang:element(6, Extensions@1)};

                _ ->
                    Extensions@1
            end end
    ).

-file("src/internal/protocol.gleam", 166).
-spec initiate_client(
    binary(),
    boolean(),
    boolean(),
    fun((extensions()) -> {ok, action()} | {error, protocol_error()})
) -> {ok, action()} | {error, protocol_error()}.
initiate_client(Helo_host, Has_smtputf8, Has_8bitmime, Next) ->
    Next@1 = fun(Extensions) -> _pipe = Extensions,
        _pipe@1 = parse_extensions(_pipe, Has_smtputf8, Has_8bitmime),
        Next(_pipe@1) end,
    send(
        <<"EHLO "/utf8, Helo_host/binary>>,
        fun() -> recover('receive'(250), fun(Error) -> case Error of
                        {unexpected_response_code, _, 500} ->
                            send(
                                <<"HELO "/utf8, Helo_host/binary>>,
                                fun() ->
                                    ignore(
                                        'receive'(250),
                                        fun() -> Next@1([]) end
                                    )
                                end
                            );

                        {unexpected_response_code, _, _} = Error@1 ->
                            {error, Error@1};

                        {invalid_response, _} = Error@1 ->
                            {error, Error@1};

                        {invalid_request_error, _} = Error@1 ->
                            {error, Error@1}
                    end end, fun(Extensions@1) -> Next@1(Extensions@1) end) end
    ).

-file("src/internal/protocol.gleam", 153).
-spec encode_domain(binary()) -> {ok, binary()} | {error, protocol_error()}.
encode_domain(Domain) ->
    _pipe = Domain,
    _pipe@1 = internal@encoder@idna:encode_domain(_pipe, ascii),
    gleam@result:map_error(
        _pipe@1,
        fun(Error) -> {invalid_request_error, {encoding_error, Error}} end
    ).

-file("src/internal/protocol.gleam", 143).
-spec has_8bitmime_body(sendr@message:message()) -> {ok, boolean()} |
    {error, protocol_error()}.
has_8bitmime_body(Message) ->
    _pipe = gleam@option:values(
        [gleam@option:map(
                erlang:element(3, erlang:element(9, Message)),
                fun is_ascii_string/1
            ),
            gleam@option:map(
                erlang:element(2, erlang:element(9, Message)),
                fun is_ascii_string/1
            )]
    ),
    _pipe@1 = gleam@list:any(_pipe, fun gleam@function:identity/1),
    _pipe@2 = gleam@bool:negate(_pipe@1),
    {ok, _pipe@2}.

-file("src/internal/protocol.gleam", 127).
-spec has_smtputf8(sendr@message:message()) -> {ok, boolean()} |
    {error, protocol_error()}.
has_smtputf8(Message) ->
    _pipe = [gleam@option:unwrap(
            erlang:element(2, Message),
            {mailbox, <<""/utf8>>, <<""/utf8>>}
        ) |
        recipients(Message)],
    _pipe@3 = gleam@list:map(
        _pipe,
        fun(Mailbox) ->
            _pipe@1 = gleam@string:split_once(
                erlang:element(3, Mailbox),
                <<"@"/utf8>>
            ),
            _pipe@2 = gleam@result:map(
                _pipe@1,
                fun(Address) ->
                    {Local, _} = Address,
                    not is_ascii_string(Local)
                end
            ),
            gleam@result:replace_error(
                _pipe@2,
                {invalid_request_error,
                    {invalid_address, erlang:element(3, Mailbox)}}
            )
        end
    ),
    _pipe@4 = gleam@result:all(_pipe@3),
    gleam@result:map(
        _pipe@4,
        fun(_capture) ->
            gleam@list:any(_capture, fun gleam@function:identity/1)
        end
    ).

-file("src/internal/protocol.gleam", 119).
-spec has_from_and_recipients(sendr@message:message()) -> {ok, nil} |
    {error, protocol_error()}.
has_from_and_recipients(Message) ->
    case {erlang:element(2, Message), recipients(Message)} of
        {none, _} ->
            {error, {invalid_request_error, no_from_mailbox_specified}};

        {_, []} ->
            {error, {invalid_request_error, no_recipients_specified}};

        {_, _} ->
            {ok, nil}
    end.

-file("src/internal/protocol.gleam", 98).
?DOC(
    " Start an SMTP protocol session for sending a message.\n"
    "\n"
    " Executes the full SMTP session: server greeting, EHLO/HELO, STARTTLS,\n"
    " authentication (if configured), and mail transaction (MAIL FROM, RCPT TO, DATA).\n"
    "\n"
    " - `message`: The `sendr/message.Message` to deliver.\n"
    " - `config`: The `ProtocolConfig` for the session.\n"
    "\n"
    " Returns an `Action` representing the first action in the protocol sequence.\n"
).
-spec start_session(sendr@message:message(), protocol_config()) -> {ok,
        action()} |
    {error, protocol_error()}.
start_session(Message, Config) ->
    gleam@result:'try'(
        has_from_and_recipients(Message),
        fun(_) ->
            gleam@result:'try'(
                has_smtputf8(Message),
                fun(Has_smtputf8) ->
                    gleam@result:'try'(
                        has_8bitmime_body(Message),
                        fun(Has_8bitmime) ->
                            gleam@result:'try'(
                                encode_domain(erlang:element(2, Config)),
                                fun(Encoded_helo_host) ->
                                    begin
                                        Initiate_client = fun(Next) ->
                                            initiate_client(
                                                Encoded_helo_host,
                                                Has_smtputf8,
                                                Has_8bitmime,
                                                Next
                                            )
                                        end,
                                        initiate_server(
                                            fun() ->
                                                Initiate_client(
                                                    fun(Extensions) ->
                                                        start_tls(
                                                            Extensions,
                                                            Initiate_client,
                                                            fun(Extensions@1) ->
                                                                authenticate(
                                                                    Extensions@1,
                                                                    erlang:element(
                                                                        3,
                                                                        Config
                                                                    ),
                                                                    fun() ->
                                                                        mail_transaction(
                                                                            Extensions@1,
                                                                            Message,
                                                                            fun(
                                                                                
                                                                            ) ->
                                                                                quit(
                                                                                    
                                                                                )
                                                                            end
                                                                        )
                                                                    end
                                                                )
                                                            end
                                                        )
                                                    end
                                                )
                                            end
                                        )
                                    end
                                end
                            )
                        end
                    )
                end
            )
        end
    ).