diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 39e5122..eddf1a8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -50,9 +50,5 @@ jobs: run: shards install - name: Create .env file run: touch .env - - if: env.RUN_INTEGRATION_SPECS == true - name: Run tests with integration + - name: Run tests run: crystal spec - - if: env.RUN_INTEGRATION_SPECS != true - name: Run tests without integration - run: crystal spec -D skip-integration diff --git a/README.md b/README.md index 06cf5fb..4172dc0 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ dependencies: ## Adapters -- `Carbon::SendGridAdapter`- Ships with Carbon. +- `Carbon::SendGridAdapter`- See [luckyframework/carbon_sendgrid_adapter](https://github.com/luckyframework/carbon_sendgrid_adapter). - `Carbon::AwsSesAdapter` - See [keizo3/carbon_aws_ses_adapter](https://github.com/keizo3/carbon_aws_ses_adapter). - `Carbon::SendInBlueAdapter` - See [atnos/carbon_send_in_blue_adapter](https://github.com/atnos/carbon_send_in_blue_adapter). - `Carbon::MailgunAdapter` - See [atnos/carbon_mailgun_adapter](https://github.com/atnos/carbon_mailgun_adapter). @@ -45,7 +45,7 @@ end ```crystal BaseEmail.configure do |settings| - settings.adapter = Carbon::SendGridAdapter.new(api_key: "SEND_GRID_API_KEY") + settings.adapter = Carbon::DevAdapter.new(print_emails: true) end ``` @@ -201,18 +201,7 @@ end - `shards install` - Make changes -- `crystal spec -D skip-integration` (will skip sending test emails to SendGrid) -- `crystal spec` requires a `SEND_GRID_API_KEY` ENV variable. Set this in a .env file: - -``` -# in .env -# If you want to run tests that actually test emails against the SendGrid server -SEND_GRID_API_KEY=get_from_send_grid -``` - -> Note: When you open a PR, Github Actions will not run the integration suite. -> If you need the integeration suite to run, the `RUN_INTEGRATION_SPECS` env var must -> be set to `true`. +- `crystal spec` ## Contributing @@ -226,4 +215,4 @@ SEND_GRID_API_KEY=get_from_send_grid ## Contributors -- [paulcsmith](https://github.com/paulcsmith]) Paul Smith - creator, maintainer +- [paulcsmith](https://github.com/paulcsmith) Paul Smith - creator, maintainer diff --git a/spec/send_grid_adapter_spec.cr b/spec/send_grid_adapter_spec.cr deleted file mode 100644 index d449b90..0000000 --- a/spec/send_grid_adapter_spec.cr +++ /dev/null @@ -1,123 +0,0 @@ -require "./spec_helper" - -describe "SendGrid adapter" do - {% unless flag?("skip-integration") %} - describe "deliver_now" do - it "delivers the email successfully" do - send_email_to_send_grid text_body: "text template", - to: [Carbon::Address.new("paul@thoughtbot.com")] - end - - it "delivers emails with reply_to set" do - send_email_to_send_grid text_body: "text template", - to: [Carbon::Address.new("paul@thoughtbot.com")], - headers: {"Reply-To" => "noreply@badsupport.com"} - end - end - {% end %} - - describe "params" do - it "is not sandboxed by default" do - params_for[:mail_settings][:sandbox_mode][:enable].should be_false - end - - it "handles headers" do - headers = {"Header1" => "value1", "Header2" => "value2"} - params = params_for(headers: headers) - - params[:headers].should eq headers - end - - it "sets extracts reply-to header" do - headers = {"reply-to" => "noreply@badsupport.com", "Header" => "value"} - params = params_for(headers: headers) - - params[:headers].should eq({"Header" => "value"}) - params[:reply_to].should eq({email: "noreply@badsupport.com"}) - end - - it "sets extracts reply-to header regardless of case" do - headers = {"Reply-To" => "noreply@badsupport.com", "Header" => "value"} - params = params_for(headers: headers) - - params[:headers].should eq({"Header" => "value"}) - params[:reply_to].should eq({email: "noreply@badsupport.com"}) - end - - it "sets personalizations" do - to_without_name = Carbon::Address.new("to@example.com") - to_with_name = Carbon::Address.new("Jimmy", "to2@example.com") - cc_without_name = Carbon::Address.new("cc@example.com") - cc_with_name = Carbon::Address.new("Kim", "cc2@example.com") - bcc_without_name = Carbon::Address.new("bcc@example.com") - bcc_with_name = Carbon::Address.new("James", "bcc2@example.com") - - recipient_params = params_for( - to: [to_without_name, to_with_name], - cc: [cc_without_name, cc_with_name], - bcc: [bcc_without_name, bcc_with_name] - )[:personalizations].first - - recipient_params[:to].should eq( - [ - {name: nil, email: "to@example.com"}, - {name: "Jimmy", email: "to2@example.com"}, - ] - ) - recipient_params[:cc].should eq( - [ - {name: nil, email: "cc@example.com"}, - {name: "Kim", email: "cc2@example.com"}, - ] - ) - recipient_params[:bcc].should eq( - [ - {name: nil, email: "bcc@example.com"}, - {name: "James", email: "bcc2@example.com"}, - ] - ) - end - - it "removes empty recipients from personalizations" do - to_without_name = Carbon::Address.new("to@example.com") - - recipient_params = params_for(to: [to_without_name])[:personalizations].first - - recipient_params.keys.should eq [:to] - recipient_params[:to].should eq [{name: nil, email: "to@example.com"}] - end - - it "sets the subject" do - params_for(subject: "My subject")[:subject].should eq "My subject" - end - - it "sets the from address" do - address = Carbon::Address.new("from@example.com") - params_for(from: address)[:from].should eq({email: "from@example.com"}.to_h) - - address = Carbon::Address.new("Sally", "from@example.com") - params_for(from: address)[:from].should eq({name: "Sally", email: "from@example.com"}.to_h) - end - - it "sets the content" do - params_for(text_body: "text")[:content].should eq [{type: "text/plain", value: "text"}] - params_for(html_body: "html")[:content].should eq [{type: "text/html", value: "html"}] - params_for(text_body: "text", html_body: "html")[:content].should eq [ - {type: "text/plain", value: "text"}, - {type: "text/html", value: "html"}, - ] - end - end -end - -private def params_for(**email_attrs) - email = FakeEmail.new(**email_attrs) - Carbon::SendGridAdapter::Email.new(email, api_key: "fake_key").params -end - -private def send_email_to_send_grid(**email_attrs) - api_key = ENV.fetch("SEND_GRID_API_KEY") - email = FakeEmail.new(**email_attrs) - adapter = Carbon::SendGridAdapter.new(api_key: api_key, sandbox: true) - adapter.deliver_now(email) -end diff --git a/src/carbon/adapters/send_grid_adapter.cr b/src/carbon/adapters/send_grid_adapter.cr deleted file mode 100644 index 48309db..0000000 --- a/src/carbon/adapters/send_grid_adapter.cr +++ /dev/null @@ -1,134 +0,0 @@ -require "http" -require "json" - -class Carbon::SendGridAdapter < Carbon::Adapter - private getter api_key : String - private getter? sandbox : Bool - - def initialize(@api_key, @sandbox = false) - end - - def deliver_now(email : Carbon::Email) - Carbon::SendGridAdapter::Email.new(email, api_key, sandbox?).deliver - end - - class Email - BASE_URI = "api.sendgrid.com" - MAIL_SEND_PATH = "/v3/mail/send" - private getter email, api_key - private getter? sandbox : Bool - - def initialize(@email : Carbon::Email, @api_key : String, @sandbox = false) - end - - def deliver - client.post(MAIL_SEND_PATH, body: params.to_json).tap do |response| - unless response.success? - raise JSON.parse(response.body).inspect - end - end - end - - # :nodoc: - # Used only for testing - def params - { - personalizations: [personalizations], - subject: email.subject, - from: from, - content: content, - headers: headers, - reply_to: reply_to_params, - mail_settings: {sandbox_mode: {enable: sandbox?}}, - } - end - - private def reply_to_params - if reply_to_address - {email: reply_to_address} - end - end - - private def reply_to_address : String? - reply_to_header.values.first? - end - - private def reply_to_header - email.headers.select do |key, _value| - key.downcase == "reply-to" - end - end - - private def headers : Hash(String, String) - email.headers.reject do |key, _value| - key.downcase == "reply-to" - end - end - - private def personalizations - { - to: to_send_grid_address(email.to), - cc: to_send_grid_address(email.cc), - bcc: to_send_grid_address(email.bcc), - }.to_h.reject do |_key, value| - value.empty? - end - end - - private def to_send_grid_address(addresses : Array(Carbon::Address)) - addresses.map do |carbon_address| - { - name: carbon_address.name, - email: carbon_address.address, - } - end - end - - private def from - { - email: email.from.address, - name: email.from.name, - }.to_h.reject do |_key, value| - value.nil? - end - end - - private def content - [ - text_content, - html_content, - ].compact - end - - private def text_content - body = email.text_body - if body && !body.empty? - { - type: "text/plain", - value: body, - } - end - end - - private def html_content - body = email.html_body - if body && !body.empty? - { - type: "text/html", - value: body, - } - end - end - - @_client : HTTP::Client? - - private def client : HTTP::Client - @_client ||= HTTP::Client.new(BASE_URI, port: 443, tls: true).tap do |client| - client.before_request do |request| - request.headers["Authorization"] = "Bearer #{api_key}" - request.headers["Content-Type"] = "application/json" - end - end - end - end -end