Sending Email

Hikyaku is a composable email library for Erlang with pluggable adapters. It handles building and delivering emails without tying you to a specific provider.

Adding Hikyaku

Add the dependency to rebar.config:

{deps, [
    nova,
    {kura, "~> 1.0"},
    {hikyaku, "~> 0.1"}
]}.

Add hikyaku to your application dependencies in src/blog.app.src:

{applications,
 [kernel,
  stdlib,
  nova,
  kura,
  hikyaku
 ]},

Creating a mailer

Hikyaku uses a behaviour-based pattern. Define a mailer module that configures the delivery adapter:

-module(blog_mailer).
-behaviour(hikyaku_mailer).
-export([config/0]).

config() ->
    #{adapter => hikyaku_adapter_logger}.

The hikyaku_adapter_logger prints emails to the console — perfect for development. We'll switch to a real adapter for production.

Available adapters

AdapterServiceConfig keys
hikyaku_adapter_smtpAny SMTP serverrelay, port, username, password, tls
hikyaku_adapter_sendgridSendGrid v3 APIapi_key
hikyaku_adapter_mailgunMailgunapi_key, domain
hikyaku_adapter_sesAmazon SES v2access_key, secret_key, region
hikyaku_adapter_loggerConsole outputlevel
hikyaku_adapter_testTest assertionspid

Building an email

Hikyaku uses a builder API — each function takes an email record and returns a new one:

E0 = hikyaku_email:new(),
E1 = hikyaku_email:from(E0, {<<"Blog">>, <<"noreply@myblog.com">>}),
E2 = hikyaku_email:to(E1, {<<"Alice">>, <<"alice@example.com">>}),
E3 = hikyaku_email:subject(E2, <<"Welcome to the Blog!">>),
E4 = hikyaku_email:text_body(E3, <<"Thanks for signing up, Alice.">>),
E5 = hikyaku_email:html_body(E4, <<"<h1>Welcome!</h1><p>Thanks for signing up.</p>">>),

{ok, _} = hikyaku_mailer:deliver(blog_mailer, E5).

Builder functions

FunctionPurpose
hikyaku_email:new/0Create a new email
hikyaku_email:from/2Set the sender ({Name, Address} or Address)
hikyaku_email:to/2Add a recipient
hikyaku_email:cc/2Add a CC recipient
hikyaku_email:bcc/2Add a BCC recipient
hikyaku_email:reply_to/2Set the reply-to address
hikyaku_email:subject/2Set the subject line
hikyaku_email:text_body/2Set the plain text body
hikyaku_email:html_body/2Set the HTML body
hikyaku_email:header/3Add a custom header
hikyaku_email:attachment/2Add an attachment

Creating email helper functions

Organize your emails in a dedicated module:

-module(blog_emails).
-export([welcome/1, comment_notification/2]).

welcome(#{email := Email, username := Username}) ->
    E0 = hikyaku_email:new(),
    E1 = hikyaku_email:from(E0, {<<"Nova Blog">>, <<"noreply@myblog.com">>}),
    E2 = hikyaku_email:to(E1, Email),
    E3 = hikyaku_email:subject(E2, <<"Welcome to Nova Blog!">>),
    E4 = hikyaku_email:text_body(E3,
        <<"Hi ", Username/binary, ",\n\n",
          "Thanks for joining Nova Blog.\n\n",
          "— The Blog Team">>),
    E5 = hikyaku_email:html_body(E4,
        <<"<h1>Welcome, ", Username/binary, "!</h1>",
          "<p>Thanks for joining Nova Blog.</p>">>),
    hikyaku_mailer:deliver(blog_mailer, E5).

comment_notification(Post, Comment) ->
    AuthorEmail = maps:get(email, maps:get(author, Post)),
    CommentAuthor = maps:get(username, maps:get(author, Comment)),
    PostTitle = maps:get(title, Post),

    E0 = hikyaku_email:new(),
    E1 = hikyaku_email:from(E0, {<<"Nova Blog">>, <<"noreply@myblog.com">>}),
    E2 = hikyaku_email:to(E1, AuthorEmail),
    E3 = hikyaku_email:subject(E2,
        <<CommentAuthor/binary, " commented on \"", PostTitle/binary, "\"">>),
    E4 = hikyaku_email:text_body(E3,
        <<CommentAuthor/binary, " left a comment on your post \"",
          PostTitle/binary, "\":\n\n",
          (maps:get(body, Comment))/binary>>),
    hikyaku_mailer:deliver(blog_mailer, E4).

Attachments

Attachment = hikyaku_attachment:from_data(CsvData, <<"export.csv">>),
E0 = hikyaku_email:new(),
E1 = hikyaku_email:from(E0, <<"noreply@myblog.com">>),
E2 = hikyaku_email:to(E1, <<"alice@example.com">>),
E3 = hikyaku_email:subject(E2, <<"Your export is ready">>),
E4 = hikyaku_email:text_body(E3, <<"See attached.">>),
E5 = hikyaku_email:attachment(E4, Attachment),

{ok, _} = hikyaku_mailer:deliver(blog_mailer, E5).

For inline images (e.g. in HTML emails):

LogoAttachment = hikyaku_attachment:from_data(LogoData, <<"logo.png">>),
InlineAttachment = hikyaku_attachment:inline(LogoAttachment, <<"logo">>),

E0 = hikyaku_email:new(),
E1 = hikyaku_email:html_body(E0, <<"<h1>Hello</h1><img src=\"cid:logo\">">>),
E2 = hikyaku_email:attachment(E1, InlineAttachment),
hikyaku_mailer:deliver(blog_mailer, E2).

Production adapter configuration

SendGrid

-module(blog_mailer).
-behaviour(hikyaku_mailer).
-export([config/0]).

config() ->
    #{adapter => hikyaku_adapter_sendgrid,
      api_key => application:get_env(blog, sendgrid_api_key, <<>>)}.

Amazon SES

config() ->
    #{adapter => hikyaku_adapter_ses,
      access_key => application:get_env(blog, aws_access_key, <<>>),
      secret_key => application:get_env(blog, aws_secret_key, <<>>),
      region => <<"us-east-1">>}.

SMTP

config() ->
    #{adapter => hikyaku_adapter_smtp,
      relay => <<"smtp.example.com">>,
      port => 587,
      username => application:get_env(blog, smtp_user, <<>>),
      password => application:get_env(blog, smtp_pass, <<>>),
      tls => always}.

Next, let's build Transactional Email flows — registration confirmation, password reset, and notifications.