Elixir feature flags: a step-by-step guide with an Elixir example

By Ben Rometsch on June 29, 2022

Hi! This short tutorial explains how to use Flagsmith in Elixir.

Flagsmith allows dev teams to easily change Feature Flags - be it running live services or development environment configuration - from a convenient web dashboard, or via an API, without the need to redeploy their applications.

I'll cover a simple example use case of toggling e-mail notifications on or off in an application.

Without further ado, let's dive right in!

Elixir feature flag illustration

Preparation

Get your API key

First you'll need to register on https://flagsmith.com/.

Once you have your account and project, go to Development environment > Settings

click "Create Server Side Environment Key"

and save your key

Create mix project

Let's get to the Elixir code!

Start with creating new mix project (with a basic tree):

1mix new notifier --sup

...and open it in your favourite editor.

Installation

It's as simple as adding flagsmith_engine package to your deps in mix.exs:

1  {:flagsmith_engine, "~> 0.1"}

And then running

1mix deps.get

in terminal to actually download the dependency.

You can find the package on hexpm.

Connect to your account

Configuration

The simplest way to connect to your Flagsmith account is to create a standard config file and put your Development Environment SDK key into it.

Let's start with standard config/config.exs file:

1import Config
2
3config :notifier, :configuration, environment_key: "<API KEY>"
4
5import_config "#{Mix.env()}.exs"

There is only one line of config which sets our Flagsmith key to example value. This is just to make the config readable for later use - we'll eventually be setting our API keys per-environment, and don't want to have a real key entered in the default config.exs.

The last line imports the current environment config. For development it would be config/dev.exs, production config/prod.exs.

Let's now create our config/dev.exs file:

1import Config
2
3import_config "dev.secret.exs"

Here we just import our "secret" config file which usually doesn't get committed into the git repository - we don't want our real keys leaked!

Now create the config/dev.secret.exs file with real key:

1import Config
2
3config :flagsmith_engine, :configuration, environment_key: "ser.c8uNwfxpkyU5SxEpoJT9uo"

And replace the key with one you saved in preparation.

Verify configuration

Now to test your configuration, run:

1iex -S mix

This should compile your project and run iex console in development mode.

Once you've landed in the console, try running:

1iex> Flagsmith.Client.get_environment()

If the configuration went well, it should output something like:

1{:ok,
2 %Flagsmith.Schemas.Environment{
3   __configuration__: %Flagsmith.Configuration{
4     api_url: "https://api.flagsmith.com/api/v1",
5     custom_headers: [],
6     default_flag_handler: nil,
7     enable_analytics: false,
8     enable_local_evaluation: false,
9     environment_key: "ser.c8uNwfxpkyU5SxEpoJT9uo",
10     environment_refresh_interval_milliseconds: 60000,
11     request_timeout_milliseconds: 5000,
12     retries: 0
13   },
14   amplitude_config: nil,
15   api_key: "WDWRzdbBs5m9FQK52HxE87",
16   feature_states: [],
17   heap_config: nil,
18   id: 17788,
19   mixpanel_config: nil,
20   project: %Flagsmith.Schemas.Environment.Project{
21     hide_disabled_flags: false,
22     id: 7576,
23     name: "example",
24     organisation: %Flagsmith.Schemas.Environment.Organisation{
25       feature_analytics: false,
26       id: 6741,
27       name: "Me",
28       persist_trait_data: true,
29       stop_serving_flags: false
30     },
31     segments: []
32   },
33   segment_config: nil
34 }}

Well done - now close iex by hitting Ctrl+C twice and let's code!

Set up mailer

Let's start our notification system by setting up a mailer.

I'll use the Bamboo library made by Thoughtbot to send e-mails.

To install the library add the following to deps in mix.exs:

1{:bamboo, "~> 2.2.0"}

And run:

1mix(deps.get)

in terminal.

If you run into the following failure:

1Resolving Hex dependencies...
2
3Failed to use "mime" because
4  bamboo (version 2.2.0) requires ~> 1.4
5  mix.lock specifies 2.0.2
6
7** (Mix) Hex dependency resolution failed, change the version requirements of your dependencies or unlock them (by using mix deps.update or mix deps.unlock). If you are unable to resolve the conflicts you can try overriding with {:dependency, "~> 1.0", override: true}

Add the following entry to deps in mix.exs:

1{:mime, "~> 2.0", override: true}

And try mix deps.get again.

Now let's create a dummy email module for our notification in lib/notifier/email.ex:

1defmodule Notifier.Email do
2  import Bamboo.Email
3
4  def notification do
5    new_email(
6      to: "me@example.com",
7      from: "noreply@notifier.app",
8      subject: "Notification",
9      text_body: "Very important content"
10    )
11  end
12end

A mailer module in lib/notifier/mailer.ex:

1defmodule Notifier.Mailer do
2  use Bamboo.Mailer, otp_app: :notifier
3
4  def send_notification do
5    Notifier.Email.notification()
6    |> deliver_now()
7  end
8end

It contains a simple function to send the notification which we'll use later in our scheduler.

Now let's add development config for Bamboo in config/dev.exs:

1config :notifier, Notifier.Mailer, adapter: Bamboo.LocalAdapter

Now we can start sending emails. Let's create a scheduler to do that for us!

Notification scheduler

To schedule notifications I'll use awesome Quantum library.

To install it add:

1{:quantum, "~> 3.0"}

to deps in mix.exs and run mix deps.get.

Then create scheduler module in lib/notifier/scheduler.ex:

1defmodule Notifier.Scheduler do
2  use Quantum, otp_app: :notifier
3end

To start it, we'll need to add it to the supervision tree in lib/notifier/application.ex.

To do that change

1children = [
2  # Starts a worker by calling: Notifier.Worker.start_link(arg)
3  # {Notifier.Worker, arg}
4]

to

1children = [
2  Notifier.Scheduler
3]

in start function.

Now to send the notification every minute (that often just for testing in development purpose :)) add the following config in config/dev.exs:

1config :notifier, Notifier.Scheduler,
2  jobs: [
3    {"* * * * *", {Notifier.Mailer, :send_notification, []}}
4  ]

To check if everything's working well try running our application:

1iex -S mix

After up to a minute the logs should show something among lines of:

113:55:00.887 [debug] Scheduling job for execution
2
313:55:00.888 [debug] Task for job started on node
4
513:55:00.888 [debug] Execute started for job
6
713:55:00.891 [debug] Sending email with Bamboo.LocalAdapter:
8
9%Bamboo.Email{assigns: %{}, attachments: [], bcc: [], blocked: false, cc: [], from: {nil, "noreply@notifier.app"}, headers: %{}, html_body: nil, private: %{}, subject: "Notification", text_body: "Very important content", to: [nil: "me@example.com"]}
10
1113:55:00.930 [debug] Execution ended for job

-- that means our job has executed and Bamboo sent an email.

Nicely done! Let's get to use Flagsmith to control the notification.

Control the notification from Flagsmith

Add feature in Flagsmith web panel

Let's first head to the Flagsmith web panel. Go to Development > Features

and click "Create your first feature".

Fill in the form and click "Create Feature".

Great! This is your first Flagsmith feature.

Now on Features listing in Development env you can easily control On/Off state of the feature. Let's turn it on for now.

Use feature in mailer

As we already have the feature added in panel we can use it in our code to control the notification.

Open up lib/notifier/mailer.ex and add the following function at the bottom of module:

1  defp send? do
2    Flagsmith.Client.is_feature_enabled("notification")
3  end

And then at the top of module:

1  require Logger

So we can add some debug logging.

Now let's use our newly made private function in send_notification. Change

1  def send_notification do
2    Notifier.Email.notification()
3    |> deliver_now()
4  end
5

to

1  def send_notification do
2    if send?() == true do
3      Notifier.Email.notification()
4      |> deliver_now()
5    else
6      Logger.debug("Not sending notification - feature disabled!")
7    end
8  end

Voila!

Our if statement will check if the send feature is enabled and either send it just as before, or log out [debug] message saying that the feature is disabled.

Let's run iex -S mix and see it in action - after a minute it should output just the same logs as before.

Now without turning off iex head back to the Flagsmith panel and disable the feature.

After a minute you should see a different log:

114:43:00.855 [debug] Scheduling job for execution
2
314:43:00.857 [debug] Task for job started on node
4
514:43:00.857 [debug] Execute started for job
6
714:43:01.286 [debug] Not sending notification - feature disabled!
8
914:43:01.286 [debug] Execution ended for job
10

Perfect - that means our configuration and code work!

Production environment and deployment

Let's add some production configuration to our app and deploy it.

Configuration

First, let's create a production environment config in config/prod.exs:

1import Config
2
3config :notifier, Notifier.Scheduler,
4  jobs: [
5    {"* * * * *", {Notifier.Mailer, :send_notification, []}}
6  ]
7
8import_config "prod.secret.exs"

We need to configure the notification scheduler and import secret configuration. In secret we'll take care of the Flagsmith and Mailer config.

Let's head to Flagsmith panel, go to Production > Settings, create a Server-Side Environment Key and put the key into secret/prod.secret.exs:

1import Config
2
3config :flagsmith_engine, :configuration, environment_key: "ser.ETKNCjTGwbNW3Ze6Ekpgyv"

(Again this is just an example key.)

Sending emails

We'll need some kind of external service integration for sending our notification emails.

Let's use MailGun as our Mailer, Bamboo, ships with adapter for it. If you want to use other adapter, feel free to choose one from Bamboo's adapter list and skip the next section.

Set up MainGun

Register for MailGun, go to user panel and save the "Sending domain" address. Then head to API keys and save the Private API key.

Use your domain and private key in secret/prod.secret.exs like that:

1config :notifier, Notifier.Mailer,
2  adapter: Bamboo.MailgunAdapter,
3  domain: "sandboxf4ab7d84d8c840fea1c562f13e768d2a.mailgun.org",
4  api_key: "8de57ec0e7d9a63a05f318f26fbae772-4f207195-9c321c2e"

Testing

To test the prod configuration, run the mix project in production mode:

1MIX_ENV=prod iex -S mix

Remember to set the Flagsmith feature in the Production environment to enabled and change to: value in lib/notifier/email.ex to your real email address!

After a minute your logs should say something about the email being sent and an email with Very important content should arrive to your mailbox (be sure to also check spam).

Well done! Now as the test went successful let's change our Scheduler to run once a day at midnight to not spam our mailbox. In config/prod.exs change Scheduler config to:

1config :notifier, Notifier.Scheduler,
2  jobs: [
3    {"@daily", {Notifier.Mailer, :send_notification, []}}
4  ]

Deployment configuration

Let's now take care about our deployment configuration.

Normally we don't want to store our secrets in the git repository. Instead we'll make our program read their values from environment variables.

To do that, create config/runtime.exs file:

1import Config
2
3config :flagsmith_engine, :configuration, environment_key: System.get_env("FLAGSMITH_KEY")
4
5config :notifier, Notifier.Mailer,
6  adapter: Bamboo.MailgunAdapter,
7  domain: System.get_env("MAILGUN_DOMAIN"),
8  api_key: System.get_env("MAILGUN_KEY")

We read from three environment variables - FLAGSMITH_KEY, MAILGUN_DOMAIN, MAILGUN_KEY - which we'll soon create in our production environment.

Deploying to Gigalixir

Let's use Gigalixir for our deployment.

Register for the service. Then go to user panel and create a new app.

Click on the app name, then Configuration tab and add all 3 environment variables.

Next install the Gigalixir CLI according to official guide and run gigalixir login to log into your account.

If you didn't create a git repository before, run git init ..

Then run gigalixir git:remote <your-app-name> in your repository's directory. This will add Gigalixir's remote to it so that we can push the code to Gigalixir for deployment.

To see your Gigalixir apps names (and other details) run gigalixir apps.

Now let's create elixir_buildpack.config file to specify our runtime versions:

1elixir_version=1.13.4
2erlang_version=25.0.1

If you didn't add any code to the repo, simply run git add . and git commit -m "My commit" to commit the code to repo. Otherwise simply commit the buildpack file before pushing the code to Gigalixir remote.

Now to actually perform the deployment, run git push gigalixir. After couple of minutes you should be able to see your running application in gigalixir ps.