# Poodle Elixir SDK
[](https://hex.pm/packages/poodle)
[](https://github.com/usepoodle/poodle-elixir/actions)
[](./LICENSE)
Elixir SDK for the Poodle email sending API.
## Features
- 🚀 Simple and intuitive API
- 📧 HTML and plain text email support
- 🔒 Comprehensive error handling
- ⚡ Asynchronous email sending
- 🛡️ Built-in input validation
- 📊 Rate limiting support
- 🔧 Configurable via environment variables
- 🎯 Pattern-matchable error tuples
- 📖 Complete documentation and examples
- ✅ Elixir 1.14+ support
## Installation
Add `poodle` to your list of dependencies in `mix.exs`:
```elixir
def deps do
[
{:poodle, "~> 1.0"}
]
end
```
Then run:
```bash
mix deps.get
```
## Quick Start
### 1. Set your API key
```bash
export POODLE_API_KEY=your_api_key_here
```
### 2. Send your first email
```elixir
# Send an HTML email
{:ok, response} = Poodle.send_html(
"sender@yourdomain.com",
"recipient@example.com",
"Hello from Poodle!",
"<h1>Welcome!</h1><p>This is a test email.</p>"
)
IO.puts("Email sent! Message: #{response.message}")
```
## Configuration
The SDK can be configured via environment variables or application config:
### Environment Variables
```bash
export POODLE_API_KEY=your_api_key
export POODLE_BASE_URL=https://api.usepoodle.com
export POODLE_TIMEOUT=30000
export POODLE_DEBUG=false
```
### Application Config
```elixir
# config/config.exs
config :poodle,
api_key: "your_api_key",
base_url: "https://api.usepoodle.com",
timeout: 30_000,
debug: false
```
## Usage Examples
### Basic Email Sending
```elixir
# HTML email
{:ok, response} = Poodle.send_html(
"sender@example.com",
"recipient@example.com",
"Welcome!",
"<h1>Hello World!</h1><p>Welcome to our service!</p>"
)
# Plain text email
{:ok, response} = Poodle.send_text(
"sender@example.com",
"recipient@example.com",
"Welcome!",
"Hello World! Welcome to our service!"
)
# Both HTML and text
{:ok, response} = Poodle.send(
"sender@example.com",
"recipient@example.com",
"Welcome!",
html: "<h1>Hello World!</h1>",
text: "Hello World!"
)
```
### Using Email Structs
```elixir
# Create an email struct
{:ok, email} = Poodle.Email.new(
"sender@example.com",
"recipient@example.com",
"Welcome!",
html: "<h1>Hello World!</h1>",
text: "Hello World!"
)
# Send the email
{:ok, response} = Poodle.send_email(email)
```
### Asynchronous Sending
```elixir
# Send email asynchronously
task = Poodle.send_async(
"sender@example.com",
"recipient@example.com",
"Welcome!",
html: "<h1>Hello World!</h1>"
)
# Wait for result
{:ok, response} = Task.await(task)
```
### Error Handling
```elixir
case Poodle.send_html(from, to, subject, html) do
{:ok, response} ->
IO.puts("Email sent! Message: #{response.message}")
# Check rate limit info
if response.rate_limit do
IO.puts("Rate limit remaining: #{response.rate_limit.remaining}")
end
{:error, %Poodle.Error{type: :unauthorized}} ->
IO.puts("Invalid API key")
{:error, %Poodle.Error{type: :rate_limit_exceeded, retry_after: retry_after}} ->
IO.puts("Rate limited. Retry after #{retry_after} seconds")
{:error, %Poodle.Error{type: :validation_error, message: message}} ->
IO.puts("Validation error: #{message}")
{:error, %Poodle.Error{type: :payment_required}} ->
IO.puts("Subscription expired or limit reached")
{:error, error} ->
IO.puts("Error: #{error.message}")
end
```
## API Reference
### Main Functions
- `Poodle.send/4` - Send email with HTML and/or text content
- `Poodle.send_html/5` - Send HTML email
- `Poodle.send_text/5` - Send plain text email
- `Poodle.send_email/3` - Send email using Email struct
- `Poodle.send_async/4` - Send email asynchronously
- `Poodle.validate_config/1` - Validate configuration
### Data Structures
#### Email Struct
```elixir
%Poodle.Email{
from: "sender@example.com",
to: "recipient@example.com",
subject: "Email Subject",
html: "<h1>HTML content</h1>", # optional
text: "Plain text content" # optional
}
```
#### Response Struct
```elixir
%Poodle.Response{
success: true,
message: "Email queued for sending",
rate_limit: %Poodle.RateLimit{
limit: 2,
remaining: 1,
reset: 1640995200
}
}
```
#### Error Struct
```elixir
%Poodle.Error{
type: :rate_limit_exceeded,
message: "Rate limit exceeded",
status_code: 429,
retry_after: 30,
details: %{...}
}
```
## Error Types
The SDK provides specific error types for different scenarios:
- `:validation_error` - Invalid input data
- `:unauthorized` - Invalid or missing API key
- `:forbidden` - Account suspended
- `:payment_required` - Subscription issues
- `:rate_limit_exceeded` - Rate limit exceeded
- `:server_error` - Server-side errors
- `:network_error` - Network connectivity issues
- `:timeout` - Request timeout
- `:dns_error` - DNS resolution failed
- `:ssl_error` - SSL/TLS errors
## Phoenix Integration
### In a Phoenix Controller
```elixir
defmodule MyAppWeb.EmailController do
use MyAppWeb, :controller
def send_welcome(conn, %{"email" => email, "name" => name}) do
case Poodle.send_html(
"welcome@myapp.com",
email,
"Welcome, #{name}!",
render_welcome_email(name)
) do
{:ok, _response} ->
json(conn, %{success: true, message: "Welcome email sent"})
{:error, error} ->
conn
|> put_status(:unprocessable_entity)
|> json(%{success: false, error: error.message})
end
end
defp render_welcome_email(name) do
"""
<h1>Welcome, #{name}!</h1>
<p>Thank you for joining our service.</p>
"""
end
end
```
### Background Jobs with Oban
```elixir
defmodule MyApp.Workers.EmailWorker do
use Oban.Worker, queue: :emails
@impl Oban.Worker
def perform(%Oban.Job{args: %{"type" => "welcome", "email" => email, "name" => name}}) do
case Poodle.send_html(
"welcome@myapp.com",
email,
"Welcome, #{name}!",
render_welcome_email(name)
) do
{:ok, _response} -> :ok
{:error, _error} -> {:error, "Failed to send email"}
end
end
defp render_welcome_email(name) do
"""
<h1>Welcome, #{name}!</h1>
<p>Thank you for joining our service.</p>
"""
end
end
```
## Development
### Running Tests
```bash
# Set test environment variables
export POODLE_API_KEY=test_api_key
# Run tests
mix test
# Run tests with coverage
mix test --cover
```
### Code Quality
```bash
# Format code
mix format
# Run Credo
mix credo
# Run Dialyzer
mix dialyzer
```
## Contributing
Contributions are welcome! Please read our [Contributing Guide](CONTRIBUTING.md) for details on the process for submitting pull requests and our [Code of Conduct](CODE_OF_CONDUCT.md).
## License
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.