Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,48 @@ Resend.Emails.send(client, %{

View additional documentation at <https://hexdocs.pm/resend>.

## Templates

Resend supports [templates](https://resend.com/docs/dashboard/templates/introduction) for sending transactional emails. You can use templates with both the direct API and the Swoosh adapter.

### Using Templates with the Direct API

```ex
Resend.Emails.send(%{
from: "Acme <[email protected]>",
to: "[email protected]",
template: %{
id: "order-confirmation",
variables: %{
PRODUCT: "Vintage Macintosh",
PRICE: 499
}
}
})
```

### Using Templates with Swoosh

To use templates with the Swoosh adapter, use `put_provider_option/3` to add the template information:

```ex
import Swoosh.Email

new()
|> from({"Acme", "[email protected]"})
|> to("[email protected]")
|> put_provider_option(:template, %{
id: "order-confirmation",
variables: %{
PRODUCT: "Vintage Macintosh",
PRICE: 499
}
})
|> MyApp.Mailer.deliver()
```

When using templates, you don't need to set the `html_body` or `text_body` as the template will provide the content.

## Swoosh Adapter

This library includes a Swoosh adapter to make using Resend with a new Phoenix project as easy as
Expand Down
30 changes: 27 additions & 3 deletions lib/resend/emails.ex
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,29 @@ defmodule Resend.Emails do
* `:html` - The HTML-formatted body of the email
* `:text` - The text-formatted body of the email
* `:attachments` - List of attachments to include in the email
* `:template` - Template to use for the email, a map with `:id` and `:variables` keys

You must include one or both of the `:html` and `:text` options.
You must include one or both of the `:html` and `:text` options, or use a `:template`.

## Template Example

Resend.Emails.send(%{
from: "Acme <[email protected]>",
to: "[email protected]",
template: %{
id: "order-confirmation",
variables: %{
PRODUCT: "Vintage Macintosh",
PRICE: 499
}
}
})

"""
@spec send(map()) :: Resend.Client.response(Email.t())
@spec send(Resend.Client.t(), map()) :: Resend.Client.response(Email.t())
def send(client \\ Resend.client(), opts) do
Resend.Client.post(client, Email, "/emails", %{
payload = %{
subject: opts[:subject],
to: opts[:to],
from: opts[:from],
Expand All @@ -38,7 +53,16 @@ defmodule Resend.Emails do
html: opts[:html],
text: opts[:text],
attachments: opts[:attachments]
})
}

payload =
if opts[:template] do
Map.put(payload, :template, opts[:template])
else
payload
end

Resend.Client.post(client, Email, "/emails", payload)
end

@doc """
Expand Down
3 changes: 3 additions & 0 deletions lib/resend/emails/email.ex
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ defmodule Resend.Emails.Email do
text: String.t() | nil,
html: String.t() | nil,
attachments: list(Attachment.t()) | nil,
template: map() | nil,
last_event: String.t() | nil,
created_at: DateTime.t() | nil
}
Expand All @@ -37,6 +38,7 @@ defmodule Resend.Emails.Email do
:text,
:html,
:attachments,
:template,
:last_event,
:created_at
]
Expand All @@ -55,6 +57,7 @@ defmodule Resend.Emails.Email do
text: map["text"],
html: map["html"],
attachments: map["attachments"],
template: map["template"],
last_event: map["last_event"],
created_at: Util.parse_iso8601(map["created_at"])
}
Expand Down
36 changes: 34 additions & 2 deletions lib/resend/swoosh/adapter.ex
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,36 @@ defmodule Resend.Swoosh.Adapter do
```

And just like that, you should be all set to send emails with Resend!

## Using Templates

To use Resend templates with Swoosh, add the template information using `put_provider_option/3`:

```ex
import Swoosh.Email

new()
|> from({"Acme", "[email protected]"})
|> to("[email protected]")
|> put_provider_option(:template, %{
id: "order-confirmation",
variables: %{
PRODUCT: "Vintage Macintosh",
PRICE: 499
}
})
|> MyApp.Mailer.deliver()
```

When using templates, you don't need to set the `html_body` or `text_body` as the template
will provide the content.
"""

@behaviour Swoosh.Adapter

@impl true
def deliver(%Swoosh.Email{} = email, config) do
Resend.Emails.send(Resend.client(config), %{
payload = %{
subject: email.subject,
from: format_sender(email.from),
to: format_recipients(email.to),
Expand All @@ -45,7 +68,16 @@ defmodule Resend.Swoosh.Adapter do
html: email.html_body,
text: email.text_body,
attachments: format_attachments(email.attachments)
})
}

payload =
if template = email.provider_options[:template] do
Map.put(payload, :template, template)
else
payload
end

Resend.Emails.send(Resend.client(config), payload)
end

@impl true
Expand Down
91 changes: 91 additions & 0 deletions test/resend/emails_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,97 @@ defmodule Resend.EmailsTest do
end
end

test "Sends an email with a template", context do
to = context.to
from = context.from
template_id = "order-confirmation"
variables = %{PRODUCT: "Vintage Macintosh", PRICE: 499}

opts = [
to: to,
from: from,
template: %{
id: template_id,
variables: variables
}
]

Tesla.Mock.mock(fn request ->
assert request.method == :post
assert request.url == "https://api.resend.com/emails"

body = Jason.decode!(request.body)

# Assert template fields are present
assert body["template"]["id"] == template_id
assert body["template"]["variables"]["PRODUCT"] == "Vintage Macintosh"
assert body["template"]["variables"]["PRICE"] == 499

%Tesla.Env{status: 200, body: %{"id" => context.sent_email_id}}
end)

assert {:ok, %Resend.Emails.Email{}} = Resend.Emails.send(Map.new(opts))
end

test "Sends email with template via Swoosh adapter", context do
# Create Swoosh email with template in provider_options
email =
Swoosh.Email.new()
|> Swoosh.Email.to(context.to)
|> Swoosh.Email.from(context.from)
|> Swoosh.Email.put_provider_option(:template, %{
id: "welcome-email",
variables: %{
NAME: "John Doe",
COMPANY: "Acme Corp"
}
})

# Mock the API call and assert template is sent
Tesla.Mock.mock(fn request ->
assert request.method == :post
assert request.url == "https://api.resend.com/emails"

body = Jason.decode!(request.body)

# Assert template fields are present
assert body["template"]["id"] == "welcome-email"
assert body["template"]["variables"]["NAME"] == "John Doe"
assert body["template"]["variables"]["COMPANY"] == "Acme Corp"

%Tesla.Env{status: 200, body: %{"id" => context.sent_email_id}}
end)

# Deliver via Swoosh adapter
config = [api_key: context.api_key]
assert {:ok, _} = Resend.Swoosh.Adapter.deliver(email, config)
end

test "Sends email without template when not present in Swoosh email", context do
# Create regular Swoosh email without template
email =
Swoosh.Email.new()
|> Swoosh.Email.to(context.to)
|> Swoosh.Email.from(context.from)
|> Swoosh.Email.subject("Regular Email")
|> Swoosh.Email.text_body("This is a regular email")

# Mock the API call
Tesla.Mock.mock(fn request ->
body = Jason.decode!(request.body)

# Assert template field is not present
refute Map.has_key?(body, "template")
assert body["text"] == "This is a regular email"

%Tesla.Env{status: 200, body: %{"id" => context.sent_email_id}}
end)

# Deliver via Swoosh adapter
config = [api_key: context.api_key]
assert {:ok, _} = Resend.Swoosh.Adapter.deliver(email, config)
end

test "Attachment struct accepts content_id field" do
attachment = %Resend.Emails.Attachment{
filename: "logo.jpg",
Expand Down