Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
matthewmcgarvey committed Mar 23, 2021
0 parents commit 4230f43
Show file tree
Hide file tree
Showing 11 changed files with 459 additions and 0 deletions.
9 changes: 9 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
root = true

[*.cr]
charset = utf-8
end_of_line = lf
insert_final_newline = true
indent_style = space
indent_size = 2
trim_trailing_whitespace = true
50 changes: 50 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
name: Carbon SendGrid Adapter CI

on:
push:
branches: [master]
pull_request:
branches: "*"

jobs:
check_format:
strategy:
fail-fast: false
matrix:
crystal_version:
- 0.36.1
- 1.0.0
experimental:
- false
runs-on: ubuntu-latest
continue-on-error: ${{ matrix.experimental }}
container: crystallang/crystal:${{ matrix.crystal_version }}-alpine
steps:
- uses: actions/checkout@v1
- name: Install shards
run: shards install
- name: Format
run: crystal tool format --check
- name: Lint
run: ./bin/ameba
specs:
strategy:
fail-fast: false
matrix:
crystal_version:
- 0.36.1
- 1.0.0
experimental:
- false
runs-on: ubuntu-latest
continue-on-error: ${{ matrix.experimental }}
container: crystallang/crystal:${{ matrix.crystal_version }}-alpine
steps:
- uses: actions/checkout@v2
- name: Cache Crystal
uses: actions/cache@v1
with:
path: ~/.cache/crystal
key: ${{ runner.os }}-crystal
- name: Run tests
run: crystal spec
10 changes: 10 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/docs/
/lib/
/bin/
/.shards/
*.dwarf
.env

# Libraries don't need dependency lock
# Dependencies will be locked in applications that use them
/shard.lock
21 changes: 21 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
The MIT License (MIT)

Copyright (c) 2021 your-name-here

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
55 changes: 55 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# Carbon SendGrid Adapter

Integration for Lucky's [Carbon](https://github.com/luckyframework/carbon) email library and [SendGrid](https://sendgrid.com).

## Installation

1. Add the dependency to your `shard.yml`:

```yaml
dependencies:
carbon_sendgrid_adapter:
github: luckyframework/carbon_sendgrid_adapter
```
2. Run `shards install`

## Usage

Create an environment variable called `SEND_GRID_KEY` with your SendGrid api key.

Update your `config/email.cr` file to use SendGrid

```crystal
require "carbon_sendgrid_adapter"
BaseEmail.configure do |settings|
if Lucky::Env.production?
send_grid_key = send_grid_key_from_env
settings.adapter = Carbon::SendGridAdapter.new(api_key: send_grid_key)
else
settings.adapter = Carbon::DevAdapter.enw
end
end
private def send_grid_key_from_env
ENV["SEND_GRID_KEY"]? || raise_missing_key_message
end
private def raise_missing_key_message
puts "Missing SEND_GRID_KEY. Set the SEND_GRID_KEY env variable to 'unused' if not sending emails, or set the SEND_GRID_KEY ENV var.".colorize.red
exit(1)
end
```

## Contributing

1. Fork it (<https://github.com/your-github-user/carbon_sendgrid_adapter/fork>)
2. Create your feature branch (`git checkout -b my-new-feature`)
3. Commit your changes (`git commit -am 'Add some feature'`)
4. Push to the branch (`git push origin my-new-feature`)
5. Create a new Pull Request

## Contributors

- [Matthew McGarvey](https://github.com/matthewmcgarvey) - maintainer
26 changes: 26 additions & 0 deletions shard.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
name: carbon_sendgrid_adapter
version: 0.1.0

authors:
- Matthew McGarvey <[email protected]>

crystal: '>= 0.36.1, < 2.0.0'

license: MIT

dependencies:
carbon:
github: luckyframework/carbon
version: ~> 0.1.3
habitat:
github: luckyframework/habitat
version: ~> 0.4.4

development_dependencies:
dotenv:
github: gdotdesign/cr-dotenv
version: 0.7.0

ameba:
github: crystal-ameba/ameba
version: ~> 0.13.4
123 changes: 123 additions & 0 deletions spec/carbon_sendgrid_adapter_spec.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
require "./spec_helper"

describe Carbon::SendGridAdapter 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("[email protected]")]
end

it "delivers emails with reply_to set" do
send_email_to_send_grid text_body: "text template",
to: [Carbon::Address.new("[email protected]")],
headers: {"Reply-To" => "[email protected]"}
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" => "[email protected]", "Header" => "value"}
params = params_for(headers: headers)

params[:headers].should eq({"Header" => "value"})
params[:reply_to].should eq({email: "[email protected]"})
end

it "sets extracts reply-to header regardless of case" do
headers = {"Reply-To" => "[email protected]", "Header" => "value"}
params = params_for(headers: headers)

params[:headers].should eq({"Header" => "value"})
params[:reply_to].should eq({email: "[email protected]"})
end

it "sets personalizations" do
to_without_name = Carbon::Address.new("[email protected]")
to_with_name = Carbon::Address.new("Jimmy", "[email protected]")
cc_without_name = Carbon::Address.new("[email protected]")
cc_with_name = Carbon::Address.new("Kim", "[email protected]")
bcc_without_name = Carbon::Address.new("[email protected]")
bcc_with_name = Carbon::Address.new("James", "[email protected]")

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: "[email protected]"},
{name: "Jimmy", email: "[email protected]"},
]
)
recipient_params[:cc].should eq(
[
{name: nil, email: "[email protected]"},
{name: "Kim", email: "[email protected]"},
]
)
recipient_params[:bcc].should eq(
[
{name: nil, email: "[email protected]"},
{name: "James", email: "[email protected]"},
]
)
end

it "removes empty recipients from personalizations" do
to_without_name = Carbon::Address.new("[email protected]")

recipient_params = params_for(to: [to_without_name])[:personalizations].first

recipient_params.keys.should eq [:to]
recipient_params[:to].should eq [{name: nil, email: "[email protected]"}]
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("[email protected]")
params_for(from: address)[:from].should eq({email: "[email protected]"}.to_h)

address = Carbon::Address.new("Sally", "[email protected]")
params_for(from: address)[:from].should eq({name: "Sally", email: "[email protected]"}.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
6 changes: 6 additions & 0 deletions spec/spec_helper.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
require "spec"
require "../src/carbon_sendgrid_adapter"
require "./support/*"
require "dotenv"

Dotenv.load
21 changes: 21 additions & 0 deletions spec/support/fake_email.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
class FakeEmail < Carbon::Email
getter text_body, html_body

def initialize(
@from = Carbon::Address.new("[email protected]"),
@to = [] of Carbon::Address,
@cc = [] of Carbon::Address,
@bcc = [] of Carbon::Address,
@headers = {} of String => String,
@subject = "subject",
@text_body : String? = nil,
@html_body : String? = nil
)
end

from @from
to @to
cc @cc
bcc @bcc
subject @subject
end
Loading

0 comments on commit 4230f43

Please sign in to comment.