Skip to content

Commit f48ebb9

Browse files
committed
Add readme
1 parent 4043292 commit f48ebb9

File tree

1 file changed

+339
-0
lines changed

1 file changed

+339
-0
lines changed

README.md

Lines changed: 339 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,339 @@
1+
# Poodle Elixir SDK
2+
3+
[![Hex.pm](https://img.shields.io/hexpm/v/poodle.svg)](https://hex.pm/packages/poodle)
4+
[![Build Status](https://github.com/usepoodle/poodle-elixir/workflows/CI/badge.svg)](https://github.com/usepoodle/poodle-elixir/actions)
5+
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](./LICENSE)
6+
7+
Elixir SDK for the Poodle email sending API.
8+
9+
## Features
10+
11+
- 🚀 Simple and intuitive API
12+
- 📧 HTML and plain text email support
13+
- 🔒 Comprehensive error handling
14+
- ⚡ Asynchronous email sending
15+
- 🛡️ Built-in input validation
16+
- 📊 Rate limiting support
17+
- 🔧 Configurable via environment variables
18+
- 🎯 Pattern-matchable error tuples
19+
- 📖 Complete documentation and examples
20+
- ✅ Elixir 1.14+ support
21+
22+
## Installation
23+
24+
Add `poodle` to your list of dependencies in `mix.exs`:
25+
26+
```elixir
27+
def deps do
28+
[
29+
{:poodle, "~> 1.0"}
30+
]
31+
end
32+
```
33+
34+
Then run:
35+
36+
```bash
37+
mix deps.get
38+
```
39+
40+
## Quick Start
41+
42+
### 1. Set your API key
43+
44+
```bash
45+
export POODLE_API_KEY=your_api_key_here
46+
```
47+
48+
### 2. Send your first email
49+
50+
```elixir
51+
# Send an HTML email
52+
{:ok, response} = Poodle.send_html(
53+
"sender@yourdomain.com",
54+
"recipient@example.com",
55+
"Hello from Poodle!",
56+
"<h1>Welcome!</h1><p>This is a test email.</p>"
57+
)
58+
59+
IO.puts("Email sent! Message: #{response.message}")
60+
```
61+
62+
## Configuration
63+
64+
The SDK can be configured via environment variables or application config:
65+
66+
### Environment Variables
67+
68+
```bash
69+
export POODLE_API_KEY=your_api_key
70+
export POODLE_BASE_URL=https://api.usepoodle.com
71+
export POODLE_TIMEOUT=30000
72+
export POODLE_DEBUG=false
73+
```
74+
75+
### Application Config
76+
77+
```elixir
78+
# config/config.exs
79+
config :poodle,
80+
api_key: "your_api_key",
81+
base_url: "https://api.usepoodle.com",
82+
timeout: 30_000,
83+
debug: false
84+
```
85+
86+
## Usage Examples
87+
88+
### Basic Email Sending
89+
90+
```elixir
91+
# HTML email
92+
{:ok, response} = Poodle.send_html(
93+
"sender@example.com",
94+
"recipient@example.com",
95+
"Welcome!",
96+
"<h1>Hello World!</h1><p>Welcome to our service!</p>"
97+
)
98+
99+
# Plain text email
100+
{:ok, response} = Poodle.send_text(
101+
"sender@example.com",
102+
"recipient@example.com",
103+
"Welcome!",
104+
"Hello World! Welcome to our service!"
105+
)
106+
107+
# Both HTML and text
108+
{:ok, response} = Poodle.send(
109+
"sender@example.com",
110+
"recipient@example.com",
111+
"Welcome!",
112+
html: "<h1>Hello World!</h1>",
113+
text: "Hello World!"
114+
)
115+
```
116+
117+
### Using Email Structs
118+
119+
```elixir
120+
# Create an email struct
121+
{:ok, email} = Poodle.Email.new(
122+
"sender@example.com",
123+
"recipient@example.com",
124+
"Welcome!",
125+
html: "<h1>Hello World!</h1>",
126+
text: "Hello World!"
127+
)
128+
129+
# Send the email
130+
{:ok, response} = Poodle.send_email(email)
131+
```
132+
133+
### Asynchronous Sending
134+
135+
```elixir
136+
# Send email asynchronously
137+
task = Poodle.send_async(
138+
"sender@example.com",
139+
"recipient@example.com",
140+
"Welcome!",
141+
html: "<h1>Hello World!</h1>"
142+
)
143+
144+
# Wait for result
145+
{:ok, response} = Task.await(task)
146+
```
147+
148+
### Error Handling
149+
150+
```elixir
151+
case Poodle.send_html(from, to, subject, html) do
152+
{:ok, response} ->
153+
IO.puts("Email sent! Message: #{response.message}")
154+
155+
# Check rate limit info
156+
if response.rate_limit do
157+
IO.puts("Rate limit remaining: #{response.rate_limit.remaining}")
158+
end
159+
160+
{:error, %Poodle.Error{type: :unauthorized}} ->
161+
IO.puts("Invalid API key")
162+
163+
{:error, %Poodle.Error{type: :rate_limit_exceeded, retry_after: retry_after}} ->
164+
IO.puts("Rate limited. Retry after #{retry_after} seconds")
165+
166+
{:error, %Poodle.Error{type: :validation_error, message: message}} ->
167+
IO.puts("Validation error: #{message}")
168+
169+
{:error, %Poodle.Error{type: :payment_required}} ->
170+
IO.puts("Subscription expired or limit reached")
171+
172+
{:error, error} ->
173+
IO.puts("Error: #{error.message}")
174+
end
175+
```
176+
177+
## API Reference
178+
179+
### Main Functions
180+
181+
- `Poodle.send/4` - Send email with HTML and/or text content
182+
- `Poodle.send_html/5` - Send HTML email
183+
- `Poodle.send_text/5` - Send plain text email
184+
- `Poodle.send_email/3` - Send email using Email struct
185+
- `Poodle.send_async/4` - Send email asynchronously
186+
- `Poodle.validate_config/1` - Validate configuration
187+
188+
### Data Structures
189+
190+
#### Email Struct
191+
192+
```elixir
193+
%Poodle.Email{
194+
from: "sender@example.com",
195+
to: "recipient@example.com",
196+
subject: "Email Subject",
197+
html: "<h1>HTML content</h1>", # optional
198+
text: "Plain text content" # optional
199+
}
200+
```
201+
202+
#### Response Struct
203+
204+
```elixir
205+
%Poodle.Response{
206+
success: true,
207+
message: "Email queued for sending",
208+
rate_limit: %Poodle.RateLimit{
209+
limit: 2,
210+
remaining: 1,
211+
reset: 1640995200
212+
}
213+
}
214+
```
215+
216+
#### Error Struct
217+
218+
```elixir
219+
%Poodle.Error{
220+
type: :rate_limit_exceeded,
221+
message: "Rate limit exceeded",
222+
status_code: 429,
223+
retry_after: 30,
224+
details: %{...}
225+
}
226+
```
227+
228+
## Error Types
229+
230+
The SDK provides specific error types for different scenarios:
231+
232+
- `:validation_error` - Invalid input data
233+
- `:unauthorized` - Invalid or missing API key
234+
- `:forbidden` - Account suspended
235+
- `:payment_required` - Subscription issues
236+
- `:rate_limit_exceeded` - Rate limit exceeded
237+
- `:server_error` - Server-side errors
238+
- `:network_error` - Network connectivity issues
239+
- `:timeout` - Request timeout
240+
- `:dns_error` - DNS resolution failed
241+
- `:ssl_error` - SSL/TLS errors
242+
243+
## Phoenix Integration
244+
245+
### In a Phoenix Controller
246+
247+
```elixir
248+
defmodule MyAppWeb.EmailController do
249+
use MyAppWeb, :controller
250+
251+
def send_welcome(conn, %{"email" => email, "name" => name}) do
252+
case Poodle.send_html(
253+
"welcome@myapp.com",
254+
email,
255+
"Welcome, #{name}!",
256+
render_welcome_email(name)
257+
) do
258+
{:ok, _response} ->
259+
json(conn, %{success: true, message: "Welcome email sent"})
260+
261+
{:error, error} ->
262+
conn
263+
|> put_status(:unprocessable_entity)
264+
|> json(%{success: false, error: error.message})
265+
end
266+
end
267+
268+
defp render_welcome_email(name) do
269+
"""
270+
<h1>Welcome, #{name}!</h1>
271+
<p>Thank you for joining our service.</p>
272+
"""
273+
end
274+
end
275+
```
276+
277+
### Background Jobs with Oban
278+
279+
```elixir
280+
defmodule MyApp.Workers.EmailWorker do
281+
use Oban.Worker, queue: :emails
282+
283+
@impl Oban.Worker
284+
def perform(%Oban.Job{args: %{"type" => "welcome", "email" => email, "name" => name}}) do
285+
case Poodle.send_html(
286+
"welcome@myapp.com",
287+
email,
288+
"Welcome, #{name}!",
289+
render_welcome_email(name)
290+
) do
291+
{:ok, _response} -> :ok
292+
{:error, _error} -> {:error, "Failed to send email"}
293+
end
294+
end
295+
296+
defp render_welcome_email(name) do
297+
"""
298+
<h1>Welcome, #{name}!</h1>
299+
<p>Thank you for joining our service.</p>
300+
"""
301+
end
302+
end
303+
```
304+
305+
## Development
306+
307+
### Running Tests
308+
309+
```bash
310+
# Set test environment variables
311+
export POODLE_API_KEY=test_api_key
312+
313+
# Run tests
314+
mix test
315+
316+
# Run tests with coverage
317+
mix test --cover
318+
```
319+
320+
### Code Quality
321+
322+
```bash
323+
# Format code
324+
mix format
325+
326+
# Run Credo
327+
mix credo
328+
329+
# Run Dialyzer
330+
mix dialyzer
331+
```
332+
333+
## Contributing
334+
335+
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).
336+
337+
## License
338+
339+
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.

0 commit comments

Comments
 (0)