Sending Emails in Phoenix Using Swoosh

A comprehensive guide to implementing transactional email functionality in Phoenix applications with advanced production patterns including async delivery with Oban and HTML email styling

Introduction to Swoosh for Phoenix

Swoosh is the de facto standard library for sending emails in Elixir and Phoenix applications. It provides a clean, composable API for building and delivering emails across multiple providers, with support for SendGrid, Mailgun, Postmark, Amazon SES, and SMTP adapters. The library has gained widespread adoption in the Elixir ecosystem due to its flexibility and excellent documentation.

The adapter-based architecture allows developers to switch email providers without changing their application code, making it easy to optimize for cost, deliverability, or regional requirements. This modular approach means your email infrastructure can evolve independently from your application logic, providing flexibility as your needs change over time.

Swoosh integrates seamlessly with Phoenix through the Phoenix.Swoosh library, which provides view and layout support for rendering HTML emails using the same template patterns used for web pages. This integration means developers can reuse their existing Phoenix knowledge to create professional-looking email templates, reducing the learning curve and maintenance burden. For teams building AI-powered automation workflows, reliable email infrastructure is essential for notification systems and user communication.

What is Swoosh?

Swoosh is Elixir's premier email library, designed with the same composable, functional principles that make Elixir powerful. Rather than forcing you into a monolithic email configuration, Swoosh lets you build emails piece by piece using a fluent interface that chains together different components. The library's architecture separates email composition from delivery, allowing the same email building blocks to work with any supported adapter.

The library's design philosophy emphasizes simplicity and extensibility. You start with a basic email structure and progressively add recipients, content, attachments, and other features as needed. This approach makes code easier to read and maintain while providing fine-grained control over every aspect of your emails. The composable design also facilitates testing, as each component can be verified independently.

Why Use Swoosh for Email in Phoenix?

The combination of Swoosh and Phoenix creates a powerful email system that leverages Elixir's strengths in concurrency and reliability. Multi-adapter support means you can start with one provider and switch to another as your needs evolve, without rewriting your email code. The Phoenix.Swoosh integration brings template rendering directly into your email workflow, enabling consistent branding across your application and emails.

Production reliability is built into Swoosh's design, with comprehensive error handling, connection management, and telemetry support. The library handles the complexities of different email providers behind a consistent API, so you can focus on your application's email logic rather than provider-specific implementation details. Combined with Elixir's supervision trees, your email system gains fault tolerance and easy recovery from failures.

Prerequisites

Before implementing Swoosh in your Phoenix application, ensure you have a working Phoenix project with Elixir and Erlang installed. Familiarity with Elixir behaviors and GenServers will help you understand how Swoosh's Mailer module integrates with your application. You'll also need access to an email provider account (SendGrid, Mailgun, Postmark, or similar) for production use, though Swoosh's Test adapter allows full development and testing without external credentials.

Why Swoosh for Phoenix?

Key capabilities that make Swoosh the go-to choice for email in Elixir applications

Multi-Adapter Support

Switch between SendGrid, Mailgun, Postmark, Amazon SES, and more without changing application code. Each adapter provides consistent API access to provider-specific features.

Phoenix Integration

Phoenix.Swoosh provides view and layout support for rendering HTML emails using Phoenix templates. Reuse your existing template patterns for professional email designs.

Async Processing

Seamless integration with Oban for reliable background email delivery. Prevent email sending from blocking user requests while ensuring messages are eventually delivered.

HTML Email Support

Premailex integration for CSS inlining ensures emails render consistently across all email clients, from Gmail to Outlook to Apple Mail.

Setting Up Swoosh in Your Phoenix Project

Installation and configuration patterns for production-ready email functionality

Installation and Configuration
1# Add to mix.exs2def deps do3 [4 {:swoosh, "~> 1.20"},5 {:swoosh_adapters, "~> 1.20"},6 {:phoenix_swoosh, "~> 1.0"}7 ]8end9 10# config/config.exs11config :sample, Sample.Mailer,12 adapter: Swoosh.Adapters.SendGrid,13 api_key: System.get_env("SENDGRID_API_KEY")14 15# lib/sample/mailer.ex16defmodule Sample.Mailer do17 use Swoosh.Mailer, otp_app: :sample18end

Configuring Email Adapters

Swoosh supports numerous email adapters, each with provider-specific configuration requirements. The adapter-based architecture means you can switch providers by changing configuration alone, without modifying your email composition code. Choose adapters based on your priorities: deliverability rates, pricing, regional compliance, or feature requirements.

SendGrid

SendGrid is one of the most popular choices for transactional email, offering robust APIs and strong deliverability. Configure your SendGrid adapter by setting your API key through an environment variable and specifying the SendGrid adapter. For production use, ensure your API key is stored securely and never committed to version control. SendGrid supports template-based sending through their API, which integrates well with Phoenix.Swoosh layouts.

Mailgun

Mailgun provides excellent deliverability and a straightforward API for sending emails. Configuration requires your domain verification and API key, with regional endpoints available for EU-based sending. Mailgun's webhook system can feed delivery events back to your application for tracking and analytics, making it valuable for applications requiring detailed email monitoring.

Postmark

Postmark specializes in transactional email with exceptional speed and deliverability. Their template API allows pre-defined templates that can be filled with dynamic data, reducing rendering overhead for high-volume sending. Server-specific tokens provide granular access control for different environments, making Postmark particularly suitable for applications with strict security requirements.

Amazon SES

Amazon SES offers the lowest cost at high volumes but requires more setup and configuration than managed services. SES uses SMTP credentials for authentication, which can be generated through the AWS console. For production applications, consider using the REST API rather than SMTP for better error handling and performance. SES integrates well with AWS Simple Notification Service for delivery notifications.

SMTP

Direct SMTP configuration provides maximum flexibility when you already have SMTP infrastructure or need to use on-premises email servers. Configure SSL/TLS settings, authentication credentials, and connection pooling based on your server requirements. SMTP is suitable for applications with existing email infrastructure or those requiring complete control over email routing.

Composing Emails with Swoosh

Building and sending emails using the Swoosh API

Basic Email Composition
1alias Swoosh.Email2 3# Build a simple email4Email.new()5|> from("[email protected]")6|> to("[email protected]")7|> subject("Welcome to Our Platform")8|> text_body("Thank you for signing up!")9|> html_body("<h1>Welcome!</h1><p>Thanks for signing up.</p>")10|> Mailer.deliver()11 12# With attachments13Email.new()14|> to({"John", "[email protected]"})15|> from({"MyApp", "[email protected]"})16|> subject("Your Invoice")17|> text_body("Please find your invoice attached.")18|> attach("/path/to/invoice.pdf")19|> Mailer.deliver()

Basic Email Structure

Swoosh's composable API lets you build emails using a fluent, chainable interface. Start with Email.new() to create a blank email struct, then progressively add components using the pipe operator. Each function in the chain returns a modified email, allowing you to read the code sequentially and understand exactly what the email contains. This pattern produces readable, maintainable code that clearly expresses email composition logic.

Adding Recipients, Subject, and Body

Recipients can be added as simple email strings or as tuples containing both name and email address. The to() function handles single recipients, while to() called multiple times or with a list adds multiple recipients. Subject lines are set with subject(), which accepts plain text strings. Email bodies come in two flavors: plain text with text_body() and HTML with html_body(). Most applications provide both versions to ensure readability across all email clients and preferences.

Attachments and Inline Images

The attach() function adds file attachments to emails, accepting paths to files or content directly. For inline images like logos in email signatures, use embed() to include images directly in the HTML body using content IDs. This approach ensures images display correctly even when recipients have image blocking enabled. Swoosh automatically handles content type detection for common file types, though you can specify types explicitly when needed.

Phoenix.Swoosh Integration

Leveraging Phoenix templates for professional email designs

The Phoenix.Swoosh library bridges the gap between Phoenix's template rendering system and Swoosh's email composition. By creating dedicated email views and layouts, developers can maintain consistency between web pages and email templates while leveraging Phoenix's template engine for email rendering. This integration means your design system, components, and styling can be shared between your web application and email communications. Our web development services team specializes in building Phoenix applications with production-ready email infrastructure.

Email layouts provide a base template that wraps individual email templates, similar to how web layouts work in Phoenix. This pattern is essential for maintaining brand consistency across all outgoing emails and reduces duplication by centralizing header, footer, and styling code. The layout approach also makes updating common email elements as simple as editing a single file.

Setting Up Email Views

Create an EmailHTML module in your web context that uses Phoenix.HTML and embeds your email templates. The embed_templates macro automatically compiles email templates into functions you can call from your email composition code. Set up a dedicated template directory structure following Phoenix conventions to keep email templates organized and separate from page templates.

Creating Email Templates

Email templates use standard Phoenix template syntax with access to assigns passed from your email module. Keep templates focused on presentation logic while complex business rules stay in your email composition module. Use pattern matching in function heads to create different template variations for the same email type based on context or user preferences.

Using Layouts for Consistent Styling

Email layouts wrap your individual email templates with common header, footer, and styling elements. The Phoenix.Swoosh layout system uses render_layout to combine layouts with templates, ensuring consistent branding across all communications. Include CSS reset styles and email-specific styling in your layout to handle the unique rendering constraints of email clients.

Email Template Module
1# lib/my_app_web/email_html.ex2defmodule MyAppWeb.EmailHTML do3 use MyAppWeb, :html4 5 # Embed email templates6 embed_templates "emails/*.html"7 embed_templates "emails/*.text", suffix: "_text"8 9 # Use in your email module10 def welcome_email(user) do11 html_body = render("welcome.html", user: user)12 13 Swoosh.Email.new()14 |> to({user.name, user.email})15 |> from({"MyApp", "[email protected]"})16 |> subject("Welcome to MyApp!")17 |> html_body(html_body)18 end19end20 21# lib/my_app_web/templates/email/welcome.html.heex22<h1>Welcome to MyApp, <%= @user.name %>!</h1>23<p>Thanks for joining our community.</p>24<p><%= link("Get Started", to: "https://myapp.com") %></p>

Advanced HTML Email Development

Creating professional HTML emails with Premailex CSS inlining

HTML emails present unique challenges due to the limited CSS support in email clients. Unlike web browsers, email clients have inconsistent support for modern CSS features, requiring developers to use table-based layouts, inline styles, and workarounds for features taken for granted in web development. Premailex addresses these challenges by automatically inlining CSS styles, ensuring that emails render consistently across different email clients from Gmail to Outlook to Apple Mail.

The combination of Phoenix templates, Swoosh for delivery, and Premailex for CSS processing creates a powerful workflow for building professional HTML emails. Developers can use familiar Phoenix template patterns while producing emails that pass even strict email client compatibility tests. This approach maintains developer productivity while meeting the technical requirements of email HTML.

Introduction to Premailex

Premailex is an Elixir library that processes HTML and inlines CSS styles for email compatibility. The library parses HTML documents, extracts stylesheets, and applies styles inline to elements based on CSS selectors. Beyond basic inlining, Premailex can convert HTML to plain text fallbacks and help validate email templates before sending.

CSS Inlining for Email Clients

The inlining process preserves semantic HTML structure while adding style attributes to elements. Premailex handles complex CSS selectors by matching rules to corresponding elements, ensuring that your carefully crafted styles actually affect email rendering. Media queries are preserved in a style block at the email head, allowing responsive designs to work on mobile clients that support them.

Creating Responsive Email Templates

Mobile-first email design requires careful attention to layout flexibility and image handling. Use percentage-based widths, fluid tables, and conditional comments for Outlook compatibility. Provide proper alt text for images, include explicit dimensions, and use responsive techniques that work within email client constraints. Fallback fonts ensure readable text even when custom web fonts fail to load.

Premailex CSS Inlining
1def premail(email) do2 # Inline CSS in HTML body3 html =4 email.html_body5 |> Premailex.to_inline_css()6 7 # Generate text fallback from HTML8 text =9 email.html_body10 |> Premailex.to_text()11 12 email13 |> html_body(html)14 |> text_body(text)15end16 17# Use in email composition18def welcome_email(user) do19 Swoosh.Email.new()20 |> to({user.name, user.email})21 |> from({"MyApp", "[email protected]"})22 |> subject("Welcome!")23 |> html_body(render("welcome.html", user: user))24 |> premail() # Apply CSS inlining25 |> Mailer.deliver()26end

Asynchronous Email Delivery with Oban

Building robust background email processing for production systems

Benefits of Async Email Processing

Responsive UX

Email sending doesn't block request handling, improving user experience and reducing response times.

Retry Logic

Oban's built-in retry mechanisms handle transient failures automatically with configurable backoff.

Job Persistence

Emails are persisted in the database and won't be lost if the application restarts.

Delivery Tracking

Track email status through job states and identify delivery issues proactively.

For production applications, sending emails synchronously can impact response times and user experience. When an email is sent during a web request, the user waits for the email provider API call to complete before receiving their response. Oban provides robust background job processing that integrates naturally with Swoosh, allowing your application to queue emails and respond immediately while delivery happens separately.

By queuing emails as background jobs, applications maintain responsive request handling while ensuring reliable email delivery. This decoupling also isolates your application from transient email provider issues, as Oban's retry mechanisms will reschedule failed jobs automatically. The background processing approach is essential for any production email system handling significant volume.

Why Async Email Processing?

Synchronous email sending creates several problems for production applications. API latency from email providers directly affects user-facing response times. Provider outages can cause request failures that impact users immediately. Without job persistence, unsent emails during application restarts are simply lost. Async processing solves all these issues by separating email queuing from delivery execution.

Creating Email Worker Modules

Oban workers for email implement the Oban.Worker behavior with a perform function that reconstructs and delivers emails. Configure workers with appropriate queue names to isolate email processing from other background jobs. The worker's job is to take stored email data and execute the delivery, handling success and failure cases appropriately.

Serialization and Deserialization Patterns

Swoosh.Email structs cannot be stored directly in Oban's job table, requiring conversion to JSON-serializable maps. The serialization pattern converts email data to maps with simple types, storing recipient information, subject, and body content. Deserialization reconstructs Swoosh.Email structs from stored maps, handling the conversion of tuple-based contact representations.

Error Handling and Retries

Oban's retry mechanism uses exponential backoff for transient failures, configurable per worker. Permanent failures eventually move to dead letter handling, allowing investigation and manual intervention. Classify errors appropriately to distinguish retriable failures from permanent rejections. Set up alerting for emails that consistently fail to ensure timely response to provider issues.

Oban Email Worker
1# lib/my_app/workers/send_email.ex2defmodule MyApp.Workers.SendEmail do3 use Oban.Worker, queue: :mailer4 5 alias MyApp.Mailer6 7 @impl Oban.Worker8 def perform(%Oban.Job{args: %{"email" => email_args}}) do9 with email <- Mailer.from_map(email_args),10 {:ok, _metadata} <- Mailer.deliver(email) do11 :ok12 end13 end14end15 16# Queueing emails from your notifier17defmodule MyApp.Accounts.UserNotifier do18 alias MyApp.Mailer19 alias MyApp.Workers.SendEmail20 21 def welcome_email(user) do22 email = build_welcome_email(user)23 24 email25 |> Mailer.to_map()26 |> SendEmail.new()27 |> Oban.insert()28 end29end
Email Serialization Pattern
1# Email serialization for Oban job storage2defmodule MyApp.Mailer do3 use Swoosh.Mailer, otp_app: :my_app4 5 # Convert Swoosh.Email to JSON-serializable map6 def to_map(%Swoosh.Email{} = email) do7 %{8 "to" => contact_to_map(email.to),9 "from" => contact_to_map(email.from),10 "subject" => email.subject,11 "text_body" => email.text_body,12 "html_body" => email.html_body13 }14 end15 16 # Convert map back to Swoosh.Email struct17 def from_map(args) do18 Swoosh.Email.new(19 to: map_to_contact(args["to"]),20 from: map_to_contact(args["from"]),21 subject: args["subject"],22 text_body: args["text_body"],23 html_body: args["html_body"]24 )25 end26 27 defp contact_to_map({name, email}), do: %{"name" => name, "email" => email}28 defp contact_to_map(info) when is_list(info), do: Enum.map(info, &contact_to_map/1)29 defp map_to_contact(%{"name" => name, "email" => email}), do: {name, email}30 defp map_to_contact(info) when is_list(info), do: Enum.map(info, &map_to_contact/1)31end

Testing Email Functionality

Strategies for reliable email testing in development and production

Swoosh includes a test adapter that captures sent emails without actually delivering them. This adapter is essential for automated testing, allowing assertions on email content, recipients, and attachments. The development mailbox feature provides a web interface for reviewing sent emails during development, speeding up template iteration and debugging. Comprehensive testing ensures email functionality remains correct as your application evolves.

Writing tests for email functionality protects against regressions when modifying template code or email composition logic. Tests verify that the right emails go to the right recipients with the correct content. For applications where email delivery is critical, consider integration tests that verify the full flow from user action through email sending.

Using Swoosh.Adapters.Test

The Test adapter intercepts all email deliveries and stores them in memory for test assertions. Configure the adapter in your test environment configuration, then use Swoosh's assertion helpers to verify emails were sent correctly. The adapter supports all email features including attachments, inline images, and multi-recipient scenarios.

Development Preview with Mailbox

For development and QA, local email adapters display sent emails in a web interface. This approach allows visual review of email templates without delivering to real addresses. Configure a local adapter that stores emails and provides a mailbox UI endpoint for reviewing sent messages. This workflow accelerates template development and QA review.

Writing Unit Tests for Email Content

Unit tests for emails should verify content accuracy, recipient handling, and template rendering. Use pattern matching in assertions to verify specific email attributes without being overly brittle. Test both success and error paths, including handling of edge cases like empty recipients or malformed content. Keep tests fast by using the Test adapter rather than external provider calls.

Testing with Swoosh.Adapters.Test
1# config/test.exs2config :my_app, MyApp.Mailer,3 adapter: Swoosh.Adapters.Test4 5# Test assertions6import Swoosh.Email, only: [assert_email_sent: 1]7 8test "welcome_email sends correct email" do9 user = %{name: "John", email: "[email protected]"}10 11 assert_email_sent to: [{"John", "[email protected]"}],12 subject: "Welcome!" do13 MyApp.Accounts.UserNotifier.welcome_email(user)14 end15end16 17# Or retrieve all sent emails18test "verifies email content" do19 user = %{name: "Jane", email: "[email protected]"}20 MyApp.Accounts.UserNotifier.welcome_email(user)21 22 assert [%{subject: "Welcome!", to: to, html_body: html}] = Swoosh.Adapters.Test.pop_all()23 assert to == [{"Jane", "[email protected]"}]24 assert html =~ "Welcome"25end

Production Best Practices

Configuration, monitoring, and optimization for production email systems

Production Checklist

Environment Configuration

Use environment variables for API keys. Configure separate adapters for dev/staging/production to prevent accidental sends.

Telemetry Integration

Swoosh emits Telemetry events. Capture these for monitoring delivery success rates, timing, and error tracking.

Error Handling

Implement proper error handling for delivery failures with alerting for persistent issues that require attention.

Performance

Use connection pooling and consider pre-warming connections to reduce first-email latency and improve throughput.

Environment Configuration

Production email systems require careful attention to configuration management. Store API keys and credentials in environment variables, never in source code. Configure different adapters for development, staging, and production environments to prevent accidental delivery to real addresses during testing. Implement safety guards that block email delivery to real addresses in non-production environments.

Monitoring with Telemetry

Swoosh emits Telemetry events for email delivery attempts, successes, and failures. Configure telemetry handlers to capture these events and forward metrics to your monitoring system. Track delivery success rates, timing distributions, and error frequencies to identify issues proactively. Set up alerts for degradation in delivery rates or increased failure rates.

Error Handling Strategies

Email delivery can fail for many reasons: invalid addresses, provider outages, rate limiting, or content issues. Implement appropriate error handling for each failure mode, distinguishing retriable transient failures from permanent rejections. Use circuit breaker patterns to prevent overwhelming providers during outages. Alert on persistent failures that require investigation or intervention.

Performance Optimization

Email sending performance affects user experience and throughput. Use connection pooling to reuse connections to email providers. Consider pre-warming connections during application startup to eliminate first-email latency. For high-volume applications, batch similar emails where possible. Pre-compile frequently used templates to reduce rendering overhead.

Conclusion

Swoosh provides a robust foundation for email functionality in Phoenix applications. By combining Swoosh with Phoenix.Swoosh for templates, Premailex for CSS inlining, and Oban for async delivery, you can build a production-grade email system that delivers reliably across all email clients.

The adapter-based architecture allows flexibility in choosing email providers based on cost, deliverability, and feature requirements. Comprehensive testing with the Test adapter ensures email content remains correct as your application evolves. For production systems, background processing with Oban provides the reliability and observability needed for critical email workflows.

When building AI-powered automation workflows that include email notifications, the combination of Phoenix, Swoosh, and Oban creates a reliable communication layer. Our team has extensive experience implementing these patterns for clients who need robust, scalable email infrastructure integrated with their AI automation systems.

Sources

Frequently Asked Questions

Ready to Implement Email Functionality?

Let Digital Thrive help you build robust email systems for your Phoenix applications with Swoosh integration, async delivery, and comprehensive testing strategies.