Skip to content
Merged
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
15 changes: 13 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

API-powered email delivery for Ruby apps.

![A cute cartoon mascot wearing a blue postal uniform with red scarf and cap, carrying a leather messenger bag, representing an API-powered email delivery system for Ruby applications](https://raw.githubusercontent.com/Rails-Designer/courrier/HEAD/.github/cover.jpg)
![A cute cartoon mascot wearing a blue postal uniform with red scarf and cap, carrying a leather messenger bag, representing an API-powered email delivery gem for Ruby apps](https://raw.githubusercontent.com/Rails-Designer/courrier/HEAD/.github/cover.jpg)

```ruby
# Quick example
Expand Down Expand Up @@ -96,7 +96,7 @@ end
class OrderEmail < Courrier::Email
configure from: "[email protected]",
cc: "[email protected]",
provider: "mailgun",
provider: "mailgun"
end
```

Expand Down Expand Up @@ -176,6 +176,17 @@ Courrier supports these transactional email providers:
Additional functionality to help with development and testing:


### Background Jobs (Rails only)

Use `deliver_later` to enqueue delivering using Rails' ActiveJob. You can set
various ActiveJob-supported options in the email class, like so: `enqueue queue: "emails", wait: 5.minutes`.

- `queue`, enqueue the email on the specified queue;
- `wait`, enqueue the email to be delivered with a delay;
- `wait_until`, enqueue the email to be delivered at (after) a specific date/time;
- `priority`, enqueues the email with the specified priority.


### Inbox (Rails only)

You can preview your emails in the inbox:
Expand Down
56 changes: 49 additions & 7 deletions lib/courrier/email.rb
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
# frozen_string_literal: true

require "courrier/email/address"
require "courrier/jobs/email_delivery_job" if defined?(Rails)
require "courrier/email/layouts"
require "courrier/email/options"
require "courrier/email/provider"

module Courrier
class Email
attr_accessor :provider, :api_key, :default_url_options, :options
attr_accessor :provider, :api_key, :default_url_options, :options, :queue_options

@queue_options = {}

class << self
%w[provider api_key from reply_to cc bcc layouts default_url_options].each do |attribute|
Expand All @@ -22,20 +25,35 @@ class << self
end
end

def deliver(options = {})
new(options).deliver_now
end
alias_method :deliver_now, :deliver

def configure(**options)
options.each { |key, value| send("#{key}=", value) if respond_to?("#{key}=") }
end
alias_method :set, :configure

def layout(options = {})
def queue_options
@queue_options ||= {}
end

attr_writer :queue_options

def enqueue(**options)
self.queue_options = options
end
alias_method :enqueue_with, :enqueue

def layout(**options)
self.layouts = options
end

def deliver(**options)
new(options).deliver_now
end
alias_method :deliver_now, :deliver

def deliver_later(**options)
new(options).deliver_later
end

def inherited(subclass)
super

Expand Down Expand Up @@ -85,6 +103,30 @@ def deliver
end
alias_method :deliver_now, :deliver

def deliver_later
if delivery_disabled?
Courrier.configuration&.logger&.info "[Courrier] Email delivery skipped: delivery is disabled via environment variable"

return nil
end

data = {
email_class: self.class.name,
provider: @provider,
api_key: @api_key,
options: @options.to_h,
provider_options: Courrier.configuration&.providers&.[](@provider.to_s.downcase.to_sym),
context_options: @context_options
}

job = Courrier::Jobs::EmailDeliveryJob
job = job.set(**self.class.queue_options) if self.class.queue_options.any?

job.perform_later(data)
rescue => error
raise Courrier::BackgroundDeliveryError, "Failed to enqueue email: #{error.message}"
end

private

def delivery_disabled?
Expand Down
15 changes: 15 additions & 0 deletions lib/courrier/email/options.rb
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,21 @@ def text = wrap(transformed_text, with_layout: :text)

def html = wrap(@html, with_layout: :html)

def to_h
{
from: @from,
to: @to,
reply_to: @reply_to,
cc: @cc,
bcc: @bcc,
subject: @subject,
text: @text,
html: @html,
auto_generate_text: @auto_generate_text,
layouts: @layouts
}
end

private

def wrap(content, with_layout:)
Expand Down
28 changes: 14 additions & 14 deletions lib/courrier/email/provider.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,20 @@
module Courrier
class Email
class Provider
PROVIDERS = {
inbox: Courrier::Email::Providers::Inbox,
logger: Courrier::Email::Providers::Logger,
loops: Courrier::Email::Providers::Loops,
mailgun: Courrier::Email::Providers::Mailgun,
mailjet: Courrier::Email::Providers::Mailjet,
mailpace: Courrier::Email::Providers::Mailpace,
postmark: Courrier::Email::Providers::Postmark,
resend: Courrier::Email::Providers::Resend,
sendgrid: Courrier::Email::Providers::Sendgrid,
sparkpost: Courrier::Email::Providers::Sparkpost,
userlist: Courrier::Email::Providers::Userlist
}

def initialize(provider: nil, api_key: nil, options: {}, provider_options: {}, context_options: {})
@provider = provider
@api_key = api_key
Expand All @@ -38,20 +52,6 @@ def deliver

private

PROVIDERS = {
inbox: Courrier::Email::Providers::Inbox,
logger: Courrier::Email::Providers::Logger,
loops: Courrier::Email::Providers::Loops,
mailgun: Courrier::Email::Providers::Mailgun,
mailjet: Courrier::Email::Providers::Mailjet,
mailpace: Courrier::Email::Providers::Mailpace,
postmark: Courrier::Email::Providers::Postmark,
resend: Courrier::Email::Providers::Resend,
sendgrid: Courrier::Email::Providers::Sendgrid,
sparkpost: Courrier::Email::Providers::Sparkpost,
userlist: Courrier::Email::Providers::Userlist
}.freeze

def configuration_missing_in_production?
production? && required_attributes_blank?
end
Expand Down
4 changes: 3 additions & 1 deletion lib/courrier/errors.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@
module Courrier
class Error < StandardError; end

class ConfigurationError < Error; end

class ArgumentError < ::ArgumentError; end

class NotImplementedError < ::NotImplementedError; end

class ConfigurationError < Error; end
class BackgroundDeliveryError < StandardError; end
end
23 changes: 23 additions & 0 deletions lib/courrier/jobs/email_delivery_job.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# frozen_string_literal: true

module Courrier
module Jobs
class EmailDeliveryJob < ActiveJob::Base
def perform(data)
email_class = data[:email_class].constantize

email_class.new(
provider: data[:provider],
api_key: data[:api_key],
from: data[:options][:from],
to: data[:options][:to],
reply_to: data[:options][:reply_to],
cc: data[:options][:cc],
bcc: data[:options][:bcc],
provider_options: data[:provider_options],
context_options: data[:context_options]
).deliver_now
end
end
end
end
2 changes: 1 addition & 1 deletion lib/generators/courrier/templates/initializer.rb.tt
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ Courrier.configure do |config|

# Choose your email delivery provider
# Default: `logger`
# config.provider = ""
# config.provider = "" # choose from: <%= Courrier::Email::Provider::PROVIDERS.keys.join(", ") %>

# Add your email provider's API key
# config.api_key = ""
Expand Down
6 changes: 6 additions & 0 deletions test/courrier/email_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,12 @@ def test_initialization_with_options
assert_equal "[email protected]", email.options.bcc
end

def test_enqueue_sets_queue_options
TestEmail.enqueue(queue: "emails", wait: 300)

assert_equal({queue: "emails", wait: 300}, TestEmail.queue_options)
end

def test_abstract_methods_raise_error
email = Courrier::Email.new(from: "[email protected]", to: "[email protected]")

Expand Down