This document gives a high‑level overview of how the Mailgun Logger codebase is structured and how HTTP requests flow through it. It is written for developers who are new to Elixir and Phoenix but familiar with typical MVC / web application concepts.
- Goal: Periodically pull email event data from Mailgun and store it in PostgreSQL, then provide a small web UI to browse/search those events and some basic statistics.
- Tech stack:
- Elixir / OTP application
- Phoenix 1.8 for the web layer (router, controllers, views/templates, components)
- Ecto for database access (PostgreSQL)
- Quantum scheduler for background jobs (in non‑dev/test environments)
- ExAws for optional raw message storage in S3
At runtime, the application is an OTP supervision tree:
MailgunLogger.Application(top‑level OTP application)MailgunLogger.Repo– database connection poolMailgunLoggerWeb.Endpoint– HTTP server entrypoint (Plug/Cowboy)MailgunLogger.Scheduler– background scheduler (only in non‑dev/test)
lib/mailgun_logger/…– domain / business logic (contexts, schemas, scheduler, mailer).lib/mailgun_logger_web/…– web interface (endpoint, router, plugs, controllers, views, templates, UI components).priv/repo/…– database migrations and seeds.priv/static/…– compiled frontend assets (JS/CSS/images).config/*.exs– environment‑specific configuration (HTTP port, HTTPS certs, DB connection, etc.).
You can think of lib/mailgun_logger as “model & services” and lib/mailgun_logger_web as “MVC web layer” in more traditional terms.
- Defined in
lib/mailgun_logger/application.ex. - Implements
start/2, which:- Optionally validates configuration for S3 raw message storage.
- Starts a supervision tree with:
MailgunLogger.PubSub– Phoenix PubSub instance.MailgunLogger.Repo– Ecto repo for Postgres.MailgunLoggerWeb.Endpoint– HTTP endpoint.MailgunLogger.Scheduler– only when:envis not:devor:test.
- Implements
config_change/3so the endpoint can be reconfigured on upgrades.
- Standard Ecto repo module (see
lib/mailgun_logger/repo.ex). - Handles DB connections, queries, and transactions for all contexts.
Location: lib/mailgun_logger_web/endpoint.ex
- This is the top‑level HTTP interface:
-
Starts the WebSocket endpoint for LiveView at
/live. -
Serves static assets from
priv/staticat/. -
In dev, enables live code reloading.
-
Sets up standard plugs:
Plug.Parsersfor URL‑encoded, multipart, and JSON request bodies.Plug.MethodOverride/Plug.Head.Plug.Sessionfor signed cookie sessions.
-
Finally delegates all remaining requests to
MailgunLoggerWeb.Router:plug(MailgunLoggerWeb.Router)
-
You can think of the endpoint as “Express + middleware setup” in Node terms.
Location: lib/mailgun_logger_web/router.ex
- Declares pipelines and routes.
- Pipelines are reusable chains of plugs (middleware) applied to groups of routes.
:browser- Accept
html. - Fetch session and LiveView flash.
- Set the default layout (
MailgunLoggerWeb.LayoutView, :app). - CSRF protection and secure browser headers.
Plug.Loggerfor request logging.
- Accept
:ping- Minimal pipeline for health checks:
- Accept
html. - Secure browser headers.
- Accept
- Minimal pipeline for health checks:
:authMailgunLoggerWeb.Plugs.SetupCheck– ensures the app has been initially configured (root user/account).MailgunLoggerWeb.Plugs.Auth– enforces authentication and loads current user.
:redirect_memberMailgunLoggerWeb.Plugs.RedirectMember– redirects non‑admin users away from admin‑only routes.
-
/pingand/health- Pipelines:
:ping - Controller:
PingController.ping/2 - Used for uptime/health checks.
- Pipelines:
-
/login- Pipeline:
:browser AuthController:new– login form.create– authenticate and start a session.
- Pipeline:
-
/password-reset/...- Pipeline:
:browser PasswordResetController:- Request a reset email, show success/error pages, handle reset token flows.
- Pipeline:
-
/setup- Pipeline:
:browser SetupController:- Initial installation flow to create the first/root account and user.
- Pipeline:
-
Authenticated user scope (
/)- Pipelines:
:browser,:auth AuthController.logout/2EventController– listing and viewing events, viewing stored raw message HTML.ProfileController– edit/update the current user’s profile.PageController– dashboard, trigger a manual fetch run, graphs, non‑affiliation page.
- Pipelines:
-
Admin scope (
/with extra plug)- Pipelines:
:browser,:auth,:redirect_member UserController– manage users (CRUD minusshow).AccountController– manage Mailgun accounts/configurations.PageController.stats/2– higher‑level stats page.
- Pipelines:
Router summary: It maps URL paths to controller actions and decides which authentication/authorization plugs run before a given controller.
Location: lib/mailgun_logger_web/plugs/*.ex
-
SetupCheck- Runs on authenticated routes.
- Checks if initial setup (root user/account) is complete.
- If not, redirects the user to the
/setupflow.
-
Auth- Handles authentication:
- Looks at session/cookies.
- Redirects to
/loginif the user is not authenticated.
- Typically assigns
current_userinto the connection for controllers/views.
- Handles authentication:
-
RedirectMember- Authorization plug for admin‑only routes.
- If the current user is only a “member” (no admin/superuser role), they are redirected away from admin sections.
Plugs are conceptually similar to middleware in other web frameworks: they receive the request/response and can transform or short‑circuit it.
Phoenix uses controllers + views + templates in a pattern that should feel familiar to MVC developers:
- Controller: orchestrates a request: fetches data from contexts, decides which template to render or where to redirect.
- View: presentation helpers / rendering logic for a given resource/domain.
- Template (
.heex): HTML with embedded Elixir (server‑rendered).
All controllers live under lib/mailgun_logger_web/controllers/.
-
PageController- General pages:
- Dashboard / index.
- Triggering a manual Mailgun fetch run.
- Graphs/statistics view.
- Non‑affiliation info page.
- General pages:
-
EventController- Works with the
MailgunLogger.Eventscontext. - Actions:
index– list events with filters and pagination via Flop.- Loads all accounts via
Accounts.list_accounts/0. - Calls
Events.search_events/2and passes results + metadata to the:indextemplate.
- Loads all accounts via
show– show a single event, preloading its associated account.stored_message– if an event has a stored raw message (in S3), fetch and display it as HTML in a stripped‑down layout.
- Works with the
-
AccountController- Manages Mailgun accounts/config (domain, API key, etc.).
- Uses the
MailgunLogger.Accountscontext.
-
UserController- Admin‑side user management (create/edit/delete users).
- Uses the
MailgunLogger.Userscontext.
-
ProfileController- Lets the current authenticated user edit their own profile.
-
AuthController- Session management / login/logout.
-
PasswordResetController- Handles password reset request and token‑based reset flows.
- Uses email sending (via
MailgunLogger.Mailer/ Bamboo) for reset emails.
-
SetupController- First‑time setup for the app: create initial root user and account.
-
PingController- Simple health check endpoint used for
/pingand/health.
- Simple health check endpoint used for
- Views live in
lib/mailgun_logger_web/views/*_view.ex. - Templates live in
lib/mailgun_logger_web/templates/**.html.heex. - Layouts:
LayoutViewand templates undertemplates/layout/(e.g.app.html.heex,setup.html.heex).- The router’s
:browserpipeline wiresapp.html.heexas default layout.
For example, the events UI is composed of:
- Controller:
MailgunLoggerWeb.EventController - View:
MailgunLoggerWeb.EventView - Templates:
templates/event/index.html.heex,templates/event/show.html.heex,templates/event/stored_message.html.heex
lib/mailgun_logger_web/components/core_components.ex- Reusable UI components (buttons, forms, tables, etc.), written for Phoenix 1.8.
lib/mailgun_logger_web/components/flop.ex- Components/helpers for pagination and filtering using Flop.
lib/mailgun_logger_web/helpers/view_helpers.ex- Miscellaneous presentation helpers used from templates.
The non‑web logic lives in lib/mailgun_logger/ and is split into contexts (service‑style modules) and schemas (Ecto models).
Some important schemas (in lib/mailgun_logger/**.ex):
-
MailgunLogger.Event- Represents a single Mailgun event (accepted, delivered, failed, clicked, opened, stored, …).
- Stores metadata such as timestamp, recipient, subject, message ID, log level, and the raw event payload.
-
MailgunLogger.Account- Represents a Mailgun account configuration (domain, API key, etc.).
-
MailgunLogger.User- Represents an application user (email, password hash, roles, etc.).
-
MailgunLogger.RoleandMailgunLogger.UserRole- Basic role/permission modeling (admin, superuser, member).
Contexts encapsulate queries and domain logic; controllers call contexts rather than working with Repo directly.
-
MailgunLogger.Events- Searching and listing events with filtering/pagination (via Flop).
- Loading a single event and its “linked” events (sharing the same message ID).
- Persisting raw events fetched from Mailgun (
save_events/2):- Transforms raw JSON from Mailgun into schema attributes.
- Uses
Ecto.Multito batch‑insert withon_conflict: :nothingto avoid duplicates.
- Computing statistics over the last N hours (
get_stats/1) for graphing. - Managing the
has_stored_messageflag when raw messages are stored externally.
-
MailgunLogger.Accounts- Listing, creating, updating, and deleting accounts.
-
MailgunLogger.Users- User CRUD and authentication‑related logic (password hashing via Argon2, etc.).
-
MailgunLogger.Roles- Helpers around roles and authorizations.
-
MailgunLogger.Emails- High‑level API for sending emails (e.g. password reset email content).
-
MailgunLogger.Mailer- Bamboo mailer module used by
Emailsto actually send messages.
- Bamboo mailer module used by
-
MailgunLogger.Scheduler- Quantum scheduler module that defines periodic jobs:
- Regularly calling the Mailgun API.
- Using contexts to persist new events.
- Quantum scheduler module that defines periodic jobs:
-
MailgunLogger.Seeder- Helpers for database seeding (e.g. creating default roles/admin user).
-
Mailgun API
- Accessed via modules under
lib/mailgun/(separate fromMailgunLogger.*namespaces). - Responsible for:
- Fetching events from the Mailgun Events API.
- Optionally fetching raw message contents for stored messages.
- Accessed via modules under
-
PostgreSQL
- Used via
MailgunLogger.Repoand Ecto schemas. - Migrations define tables and indexes (see
priv/repo/migrations).
- Used via
-
AWS S3 (optional)
- If
store_messagesis enabled and ExAws is configured:- Raw message bodies are stored in S3 rather than the DB.
- The events table keeps metadata, and UI can load the stored raw message on demand.
- If
Below is a simplified sequence for a typical authenticated web request, e.g. “view events list”:
- TCP/HTTP request comes in on the configured port (dev: HTTPS on port 7070).
MailgunLoggerWeb.Endpointreceives it and runs global plugs:- Static asset check (for
/assets/...). - Body parsing, method override, session handling, etc.
- Static asset check (for
- The request is forwarded to
MailgunLoggerWeb.Router. - The router matches the path to a route, e.g.
GET /events:- Applies the
:browserand:authpipelines:- Session & flash.
- CSRF protection.
- Loads current user (Auth plug).
- Optional authorization via
:redirect_memberfor admin‑only routes.
- Applies the
- The matched controller action runs, e.g.
EventController.index/2:- Calls the appropriate context functions (
Events.search_events/2,Accounts.list_accounts/0). - Decides which template to render (or where to redirect).
- Calls the appropriate context functions (
- The controller calls
render/3:- Phoenix chooses the corresponding view (
EventView) and template (templates/event/index.html.heex). - View helpers and components are used to build the final HTML.
- Phoenix chooses the corresponding view (
- The rendered HTML is sent back via the endpoint to the client.
Other flows (login, password reset, admin management) follow the same pattern but talk to different contexts and render different templates.
If you are new to Elixir/Phoenix and want to understand more:
- Start with the router:
lib/mailgun_logger_web/router.ex- This tells you which URLs exist and which controllers handle them.
- For any controller action, follow its call into a context in
lib/mailgun_logger/. - Look at the view and
.heextemplate with the same name to see what gets rendered. - Check
config/dev.exsfor how the dev server is configured (HTTPS certs, port 7070, etc.).
With that path (router → controller → context → view/template), you can usually trace any request or feature in this application.