Created
February 7, 2023 09:57
-
-
Save arjan/cc6989c71a71697beb728d68c54a0b93 to your computer and use it in GitHub Desktop.
Example on how to receive email in Elixir
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
defmodule MyApp.ReceiveServer do | |
@behaviour :gen_smtp_server_session | |
require Logger | |
# how many simultaneous SMTP sessions | |
@max_sessions 20 | |
# max email message size | |
@maxsize 200 * 1024 * 1024 | |
def child_spec(_opts) do | |
%{ | |
id: __MODULE__, | |
start: {__MODULE__, :start_link, []} | |
} | |
end | |
def start_link() do | |
args = [port: 2525, address: {0, 0, 0, 0}] | |
Logger.info("Accepting e-mail on port #{args[:port]}") | |
:gen_smtp_server.start({:local, __MODULE__}, __MODULE__, args) | |
end | |
defstruct [:hostname, :from, :peername, :options, :helo, :bns_entry] | |
alias __MODULE__, as: State | |
@impl true | |
def init(_hostname, session_count, _peername, _options) when session_count > @max_sessions do | |
{:stop, :normal, '421 server too busy to accept mail right now'} | |
end | |
def init(_hostname, _session_count, peername, _options) when not is_tuple(peername) do | |
{:stop, :normal, '421 invalid peername'} | |
end | |
def init(hostname, _session_count, peername, options) do | |
banner = 'ESMTP example 1.0' | |
{:ok, banner, %State{hostname: hostname, peername: peername, options: options}} | |
end | |
@impl true | |
# credo:disable-for-next-line | |
def handle_HELO(hostname, state) do | |
{:ok, @maxsize, %State{state | helo: hostname}} | |
end | |
@impl true | |
# credo:disable-for-next-line | |
def handle_EHLO(hostname, extensions, state) do | |
extensions = [{'SIZE', '#{@maxsize}'} | :lists.keydelete('SIZE', 1, extensions)] | |
{:ok, extensions, %State{state | helo: hostname}} | |
end | |
@impl true | |
# credo:disable-for-next-line | |
def handle_STARTTLS(state), do: state | |
@impl true | |
# credo:disable-for-next-line | |
def handle_MAIL(from, state) do | |
state = %State{state | from: from} | |
# this function comes from from z_stdlib | |
case :z_email_dnsbl.status(state.peername) do | |
{:ok, {:blocked, service}} -> | |
{:error, '451 peer blocked by DNSBL service #{inspect(service)}', state} | |
{:ok, _} -> | |
{:ok, state} | |
end | |
end | |
@impl true | |
# credo:disable-for-next-line | |
def handle_MAIL_extension(_from, state) do | |
{:ok, state} | |
end | |
@impl true | |
# credo:disable-for-next-line | |
def handle_RCPT(to, state) do | |
# TODO check if we accept mail for this address (in `to`) | |
case do_we_accept_this_addres?(to) do | |
false -> | |
{:error, '550 No such recipient', state} | |
true -> | |
{:ok, state} | |
end | |
end | |
@impl true | |
# credo:disable-for-next-line | |
def handle_RCPT_extension(_from, state) do | |
{:ok, state} | |
end | |
@impl true | |
# credo:disable-for-next-line | |
def handle_DATA(_from, _to, data, state) do | |
with {:ok, parsed_mime} <- decode_mime(data) do | |
# TODO do something with `parsed_mime` here | |
msg_id = process_message() | |
{:ok, msg_id, %State{}} | |
else | |
{:error, message} -> | |
{:error, '552 #{message}', state} | |
end | |
end | |
@impl true | |
# credo:disable-for-next-line | |
def handle_RSET(_state) do | |
%State{} | |
end | |
@impl true | |
# credo:disable-for-next-line | |
def handle_VRFY(_address, state) do | |
{:error, '252 VRFY disabled by policy, just send some mail', state} | |
end | |
@impl true | |
def handle_other(verb, _args, state) do | |
{'500 error: verb not recognized: ' ++ verb, state} | |
end | |
@impl true | |
def code_change(_oldvsn, state, _extra), do: {:ok, state} | |
@impl true | |
def terminate(reason, state), do: {:ok, reason, state} | |
defp decode_mime(mime) do | |
mime = mime |> String.replace("\r", "") |> String.replace("\n", "\r\n") | |
try do | |
{:ok, :mimemail.decode(mime)} | |
catch | |
:error, r -> | |
{:error, "MIME decoding error: " <> String.replace(to_string(r), "_", " ")} | |
end | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment