Packages

A Gleam library for sending emails via SMTP, inspired by the Rust mail-send crate

Current section

Files

Jump to
lumenmail src lumenmail_network_ffi.erl
Raw

src/lumenmail_network_ffi.erl

%% Minimal FFI for Erlang gen_tcp and ssl modules.
%% This module provides thin wrappers around Erlang's networking functions.
-module(lumenmail_network_ffi).
-export([
tcp_connect/4,
ssl_connect/4,
ensure_ssl_started/0,
send/3,
recv/3,
close/2
]).
%% @doc Establish a TCP connection.
tcp_connect(Host, Port, _Options, Timeout) ->
HostStr = binary_to_list(Host),
Options = [binary, {active, false}, {packet, line}, {reuseaddr, true}],
case gen_tcp:connect(HostStr, Port, Options, Timeout) of
{ok, Socket} -> {ok, Socket};
{error, Reason} -> {error, format_reason(Reason)}
end.
%% @doc Upgrade TCP socket to SSL/TLS.
ssl_connect(Socket, Host, AllowInvalidCerts, Timeout) ->
HostStr = binary_to_list(Host),
BaseOptions = [binary, {active, false}, {packet, line}],
Options = case AllowInvalidCerts of
true ->
[{verify, verify_none} | BaseOptions];
false ->
[{verify, verify_peer},
{server_name_indication, HostStr},
{cacerts, public_key:cacerts_get()}
| BaseOptions]
end,
case ssl:connect(Socket, Options, Timeout) of
{ok, SslSocket} -> {ok, SslSocket};
{error, Reason} -> {error, format_reason(Reason)}
end.
%% @doc Ensure SSL application is started.
ensure_ssl_started() ->
application:ensure_all_started(ssl),
nil.
%% @doc Send data over socket.
send(Socket, IsTls, Data) ->
Result = case IsTls of
true -> ssl:send(Socket, Data);
false -> gen_tcp:send(Socket, Data)
end,
case Result of
ok -> {ok, nil};
{error, Reason} -> {error, format_reason(Reason)}
end.
%% @doc Receive a line from socket.
recv(Socket, IsTls, Timeout) ->
Result = case IsTls of
true -> ssl:recv(Socket, 0, Timeout);
false -> gen_tcp:recv(Socket, 0, Timeout)
end,
case Result of
{ok, Data} -> {ok, Data};
{error, closed} -> {error, <<"closed">>};
{error, timeout} -> {error, <<"timeout">>};
{error, Reason} -> {error, format_reason(Reason)}
end.
%% @doc Close socket.
close(Socket, IsTls) ->
case IsTls of
true -> ssl:close(Socket);
false -> gen_tcp:close(Socket)
end,
nil.
%% Internal: format error reason to binary string.
format_reason(Reason) when is_atom(Reason) ->
atom_to_binary(Reason, utf8);
format_reason(Reason) when is_list(Reason) ->
list_to_binary(Reason);
format_reason(Reason) when is_binary(Reason) ->
Reason;
format_reason(Reason) ->
iolist_to_binary(io_lib:format("~p", [Reason])).