Skip to content

chrisgreg/fyi

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

10 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

FYI

Hex.pm Hex Docs Downloads License

Know what's happening in your app.


In-app events, user feedback, and instant Slack/Telegram notifications for Phoenix.

Stop refreshing your database to see if users are signing up. FYI gives you:

  • πŸ“€ Event tracking β€” Emit events from anywhere in your app with one line of code
  • πŸ“Š Live dashboard β€” Beautiful admin UI with search, filtering, and activity histograms
  • πŸ’¬ Feedback widget β€” Drop-in component to collect user feedback (installs into your codebase)
  • πŸ”” Instant notifications β€” Get pinged in Slack or Telegram when important things happen
  • 🎯 Smart routing β€” Send specific events to specific channels with glob patterns
  • πŸš€ One-command setup β€” mix fyi.install handles migrations, config, and routes

FYI Admin Inbox

Installation

Add fyi to your list of dependencies in mix.exs:

def deps do
  [
    {:fyi, "~> 1.0.0"}
  ]
end

Then run the installer:

mix deps.get
mix fyi.install

This will:

  1. Add FYI.Application to your supervision tree
  2. Create a migration for the fyi_events table
  3. Print instructions to add the /fyi route to your router
  4. Add configuration stubs to your config files

Installer Options

  • --no-ui β€” Skip installing the admin inbox UI
  • --no-persist β€” Skip the database migration (events won't be persisted)
  • --no-feedback β€” Skip installing the feedback component

Configuration

# config/config.exs
config :fyi,
  app_name: "MyApp",
  persist_events: true,
  repo: MyApp.Repo,
  sinks: [
    {FYI.Sink.SlackWebhook, %{url: System.get_env("SLACK_WEBHOOK_URL")}},
    {FYI.Sink.Telegram, %{
      token: System.get_env("TELEGRAM_BOT_TOKEN"),
      chat_id: System.get_env("TELEGRAM_CHAT_ID")
    }}
  ],
  routes: [
    %{match: "waitlist.*", sinks: [:slack]},
    %{match: "purchase.*", sinks: [:slack, :telegram]},
    %{match: "feedback.*", sinks: [:slack]}
  ]

App Name

Set app_name to identify events when multiple apps share the same Slack channel or Telegram chat:

config :fyi, app_name: "MyApp"

Messages will include the app name: [MyApp] *purchase.created* by user_123

Emojis

Add emojis to your notifications in three ways (in priority order):

1. Per-event override:

FYI.emit("error.critical", %{message: "DB down"}, emoji: "🚨")

2. Pattern-based mapping:

config :fyi,
  emojis: %{
    "purchase.*" => "πŸ’°",
    "user.signup" => "πŸ‘‹",
    "feedback.*" => "πŸ’¬",
    "error.*" => "🚨"
  }

3. Default fallback:

config :fyi, emoji: "πŸ“£"

Messages will show as: πŸ’° [MyApp] *purchase.created* by user_123

Routing

Routes use simple glob matching:

  • purchase.* matches purchase.created, purchase.updated, etc.
  • * at the end matches any suffix

If no routes are configured, all events go to all sinks.

Usage

Emit an Event

FYI.emit("purchase.created", %{amount: 4900, currency: "GBP"}, actor: user_id)

FYI.emit("user.signup", %{email: "[email protected]"}, source: "landing_page")

FYI.emit("error.critical", %{message: "DB connection failed"}, emoji: "🚨", tags: %{env: "prod"})

Options:

  • :actor - who triggered the event (user_id, email, etc.)
  • :source - where the event originated (e.g., "api", "web", "worker")
  • :tags - additional metadata map for filtering
  • :emoji - override emoji for this specific event

Emit from Ecto.Multi (Recommended)

Ecto.Multi.new()
|> Ecto.Multi.insert(:purchase, changeset)
|> FYI.Multi.emit("purchase.created", fn %{purchase: p} ->
  %{payload: %{amount: p.amount, currency: p.currency}, actor: p.user_id}
end)
|> Repo.transaction()

This ensures events are only emitted after the transaction commits successfully.

Feedback Component

The installer creates a customizable feedback component in your codebase at lib/your_app_web/components/fyi/feedback_component.ex.

Use it in any LiveView:

import MyAppWeb.FYI.FeedbackComponent

# In your template
<.feedback_button />

Customize as needed:

<.feedback_button
  title="Report an Issue"
  button_label="Report"
  button_icon="πŸ›"
  categories={[{"bug", "Bug"}, {"ux", "UX Problem"}, {"other", "Other"}]}
/>

Since the component lives in your codebase, you can freely modify the Tailwind classes, add fields, or change the behavior.

Skip installing with mix fyi.install --no-feedback.

Admin Inbox

Add the route to your router (the installer prints this):

# In router.ex
scope "/fyi", FYI.Web do
  pipe_through [:browser]
  live "/", InboxLive, :index
  live "/events/:id", InboxLive, :show
end

Visit /fyi to see the event inbox with:

  • Activity histogram with time-based tooltips
  • Real-time event updates (requires PubSub config)
  • Time range filtering (5 minutes to all time)
  • Event type filtering
  • Search by event name or actor
  • Event detail panel with full payload

Real-time Updates

To enable real-time updates in the admin inbox, add your PubSub module:

config :fyi, pubsub: MyApp.PubSub

New events will appear instantly without refreshing the page.

Built-in Sinks

Slack Webhook

{FYI.Sink.SlackWebhook, %{
  url: "https://hooks.slack.com/services/...",
  username: "FYI Bot",      # optional
  icon_emoji: ":bell:"      # optional
}}
How to create a Slack webhook
  1. Go to api.slack.com/apps and click Create New App
  2. Choose From scratch, name it (e.g., "FYI"), and select your workspace
  3. Click Incoming Webhooks in the sidebar, then toggle it On
  4. Click Add New Webhook to Workspace and select the channel
  5. Copy the webhook URL β€” it looks like https://hooks.slack.com/services/T00/B00/xxxx

Telegram Bot

{FYI.Sink.Telegram, %{
  token: "123456:ABC-DEF...",
  chat_id: "-1001234567890",
  parse_mode: "HTML"         # optional, default: "HTML"
}}
How to create a Telegram bot
  1. Message @BotFather on Telegram
  2. Send /newbot and follow the prompts to name your bot
  3. Copy the token (looks like 123456789:ABCdefGHI...)
  4. Add the bot to your group/channel and send a message
  5. Get your chat_id by visiting: https://api.telegram.org/bot<TOKEN>/getUpdates
    • Look for "chat":{"id":-1001234567890} in the response
    • Group IDs are negative numbers

Custom Sinks

Implement the FYI.Sink behaviour:

defmodule MyApp.DiscordSink do
  @behaviour FYI.Sink

  @impl true
  def id, do: :discord

  @impl true
  def init(config) do
    {:ok, %{webhook_url: config.url}}
  end

  @impl true
  def deliver(event, state) do
    # POST to Discord webhook
    case Req.post(state.webhook_url, json: %{content: event.name}) do
      {:ok, %{status: s}} when s in 200..299 -> :ok
      {:ok, resp} -> {:error, resp}
      {:error, err} -> {:error, err}
    end
  end
end

Then add it to your config:

sinks: [
  {MyApp.DiscordSink, %{url: "https://discord.com/api/webhooks/..."}}
]

Design Philosophy

FYI is intentionally simple:

  • ❌ No Oban
  • ❌ No durable queues, retries, or backoff
  • βœ… Fire-and-forget HTTP notifications
  • βœ… Phoenix + Ecto assumed
  • βœ… Failures are logged, never block

Think "Oban Pro install experience", but for events + feedback.

Development

To use FYI locally without publishing to Hex:

# In your app's mix.exs
{:fyi, path: "../fyi"}

License

MIT

About

In-app events & feedback with Slack/Telegram notifications for Phoenix

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages