Skip to content

Commit

Permalink
Merge pull request #17 from vapor-community/vapor3
Browse files Browse the repository at this point in the history
Vapor 3 support
  • Loading branch information
Andrewangeta committed Mar 31, 2018
2 parents dd565b8 + 8bc9d55 commit 3ba1689
Show file tree
Hide file tree
Showing 34 changed files with 552 additions and 825 deletions.
9 changes: 5 additions & 4 deletions .gitignore
@@ -1,5 +1,6 @@
.build
Package.pins

.DS_Store
*.xcodeproj
/.build
/Packages
/*.xcodeproj
Package.pins
Package.resolved
12 changes: 10 additions & 2 deletions Package.swift
@@ -1,8 +1,16 @@
// swift-tools-version:4.0
import PackageDescription

let package = Package(
name: "SendGridProvider",
name: "SendGrid",
products: [
.library(name: "SendGrid", targets: ["SendGrid"])
],
dependencies: [
.Package(url: "https://github.com/vapor/vapor.git", majorVersion: 2),
.package(url: "https://github.com/vapor/vapor.git", from: "3.0.0-rc"),
],
targets: [
.target(name: "SendGrid", dependencies: ["Vapor"]),
.testTarget(name: "SendGridTests", dependencies: ["Vapor", "SendGrid"])
]
)
84 changes: 32 additions & 52 deletions README.md
@@ -1,7 +1,7 @@
# SendGrid Provider for Vapor

![Swift](http://img.shields.io/badge/swift-3.1-brightgreen.svg)
![Vapor](http://img.shields.io/badge/vapor-2.0-brightgreen.svg)
![Swift](http://img.shields.io/badge/swift-4.1-brightgreen.svg)
![Vapor](http://img.shields.io/badge/vapor-3.0-brightgreen.svg)
[![CircleCI](https://circleci.com/gh/vapor-community/sendgrid-provider.svg?style=shield)](https://circleci.com/gh/vapor-community/sendgrid-provider)

Adds a mail backend for SendGrid to the Vapor web framework. Send simple emails,
Expand All @@ -10,66 +10,46 @@ or leverage the full capabilities of SendGrid's V3 API.
## Setup
Add the dependency to Package.swift:

```swift
.Package(url: "https://github.com/vapor-community/sendgrid-provider.git", majorVersion: 2)
```
~~~~swift
.package(url: "https://github.com/vapor-community/sendgrid-provider.git", from: "3.0.0-rc")
~~~~

Add a configuration file named `sendgrid.json` with the following format:
Register the config and the provider.
~~~~swift
let config = SendGridConfig(apiKey: "SG.something")

```JSON
{
"apiKey": "SG.YOUR_API_KEY"
}
```

Register the provider with the configuration system:

```swift
import SendGridProvider

extension Config {
/// Configure providers
private func setupProviders() throws {
...
try addProvider(SendGridProvider.Provider.self)
}
}
```

And finally, change the Droplet's mail implementation by editing `droplet.json`:
services.register(config)

```js
{
"mail": "sendgrid",
// other configuration keys redacted for brevity
}
```
try services.register(SendGridProvider())

## Sending simple emails
app = try Application(services: services)

SendGrid can act as a drop-in replacement for Vapor's built-in SMTP support.
Simply make use of `drop.mail`:
sendGridClient = try app.make(SendGridClient.self)
~~~~

```swift
import SMTP
## Using the API

let email = Email(from: , to: , subject: , body: )
try drop.mail.send(email)
```
You can use all of the available parameters here to build your `SendGridEmail`
Usage in a route closure would be as followed:

Don't forget to `import SMTP` if you need to work with `Email` or
`EmailAddress` objects.
~~~~swift
import SendGrid

## Sending complex emails
let email = SendGridEmail()
let sendGridClient = try req.make(SendGridClient.self)

Use the `SendGridEmail` class to fully configure your email, including open
and click tracking, templating, and multiple recipients.
try sendGridClient.send([email], on: req.eventLoop)
~~~~

```swift
import SendGridProvider
## Error handling
If the request to the API failed for any reason a `SendGridError` is `thrown` and has an `errors` property that contains an array of errors returned by the API.
Simply ensure you catch errors thrown like any other throwing function

if let sendgrid = drop.mail as? SendGrid {
let email = SendGridEmail()
try sendgrid.send(email)
~~~~swift
do {
try sendgridClient.send(...)
}
catch let error as SendGridError {
print(error.localizedDescription)
}
```
~~~~
17 changes: 17 additions & 0 deletions Sourcery/LinuxMain.stencil
@@ -0,0 +1,17 @@
// sourcery:file:Tests/LinuxMain.swift
import XCTest
{{ argument.testimports }}

{% for type in types.classes|based:"XCTestCase" %}
{% if not type.annotations.disableTests %}extension {{ type.name }} {
static var allTests = [
{% for method in type.methods %}{% if method.parameters.count == 0 and method.shortName|hasPrefix:"test" %} ("{{ method.shortName }}", {{ method.shortName }}),
{% endif %}{% endfor %}]
}

{% endif %}{% endfor %}

XCTMain([
{% for type in types.classes|based:"XCTestCase" %}{% if not type.annotations.disableTests %} testCase({{ type.name }}.allTests),
{% endif %}{% endfor %}])
// sourcery:end
14 changes: 14 additions & 0 deletions Sources/SendGrid/Models/AdvancedSuppressionManager.swift
@@ -0,0 +1,14 @@
import Vapor

public struct AdvancedSuppressionManager: Content {
/// The unsubscribe group to associate with this email.
public var groupId: Int?

/// An array containing the unsubscribe groups that you would like to be displayed on the unsubscribe preferences page.
public var groupsToDisplay: [String]?

public enum CodingKeys: CodingKey, String {
case groupId = "group_id"
case groupsToDisplay = "groups_to_display"
}
}
16 changes: 16 additions & 0 deletions Sources/SendGrid/Models/EmailAddress.swift
@@ -0,0 +1,16 @@
//
// EmailAddress.swift
// Sendgrid
//
// Created by Andrew Edwards on 3/29/18.
//

import Vapor

public struct EmailAddress: Content {
/// format: email
var email: String?

/// The name of the person to whom you are sending an email.
var name: String?
}
34 changes: 34 additions & 0 deletions Sources/SendGrid/Models/EmailAttachment.swift
@@ -0,0 +1,34 @@
//
// EmailAttachment.swift
// Sendgrid
//
// Created by Andrew Edwards on 3/29/18.
//

import Vapor

public struct EmailAttachment: Content {

/// The Base64 encoded content of the attachment.
public var content: String?

/// The mime type of the content you are attaching. For example, “text/plain” or “text/html”.
public var type: String?

/// The filename of the attachment.
public var filename: String?

/// The content-disposition of the attachment specifying how you would like the attachment to be displayed.
public var disposition: String?

/// The content id for the attachment. This is used when the disposition is set to “inline” and the attachment is an image, allowing the file to be displayed within the body of your email.
public var contentId: String?

public enum CodingKeys: CodingKey, String {
case content
case type
case filename
case disposition
case contentId = "content_id"
}
}
77 changes: 77 additions & 0 deletions Sources/SendGrid/Models/MailSettings.swift
@@ -0,0 +1,77 @@
//
// MailSettings.swift
// Sendgrid
//
// Created by Andrew Edwards on 3/29/18.
//

import Vapor

public struct MailSettings: Content {
/// This allows you to have a blind carbon copy automatically sent to the specified email address for every email that is sent.
public var bcc: BCC?

/// Allows you to bypass all unsubscribe groups and suppressions to ensure that the email is delivered to every single recipient. This should only be used in emergencies when it is absolutely necessary that every recipient receives your email.
public var bypassListManagement: BypassListManagement?

/// The default footer that you would like included on every email.
public var footer: Footer?

/// This allows you to send a test email to ensure that your request body is valid and formatted correctly.
public var sandboxMode: SandboxMode?

/// This allows you to test the content of your email for spam.
public var spamCheck: SpamCheck?

public enum CodingKeys: CodingKey, String {
case bcc
case bypassListManagement = "bypass_list_management"
case footer
case sandboxMode = "sandbox_mode"
case spamCheck = "spam_check"
}
}

public struct BCC: Content {
/// Indicates if this setting is enabled.
public var enable: Bool?
public var email: String?
}

public struct BypassListManagement: Content {
/// Indicates if this setting is enabled.
public var enable: Bool?
}

public struct Footer: Content {
/// Indicates if this setting is enabled.
public var enable: Bool?

/// The plain text content of your footer.
public var text: String?

/// The HTML content of your footer.
public var html: String?
}

public struct SandboxMode: Content {
/// Indicates if this setting is enabled.
public var enable: Bool?
}

public struct SpamCheck: Content {
/// Indicates if this setting is enabled.
public var enable: Bool?

/// The threshold used to determine if your content qualifies as spam on a scale from 1 to 10, with 10 being most strict, or most likely to be considered as spam.
public var threshold: Int?

/// An Inbound Parse URL that you would like a copy of your email along with the spam report to be sent to.
public var postToUrl: String?

public enum CodingKeys: CodingKey, String {
case enable
case threshold
case postToUrl = "post_to_url"
}
}
39 changes: 39 additions & 0 deletions Sources/SendGrid/Models/Personalization.swift
@@ -0,0 +1,39 @@
import Vapor

public struct Personalization: Content {

/// An array of recipients. Each object within this array may contain the name, but must always contain the email, of a recipient.
public var to: [EmailAddress]?

/// An array of recipients who will receive a copy of your email. Each object within this array may contain the name, but must always contain the email, of a recipient.
public var cc: [EmailAddress]?

/// An array of recipients who will receive a blind carbon copy of your email. Each object within this array may contain the name, but must always contain the email, of a recipient.
public var bcc: [EmailAddress]?

/// The subject of your email.
public var subject: String?

/// A collection of JSON key/value pairs allowing you to specify specific handling instructions for your email.
public var headers: [String: String]?

/// A collection of key/value pairs following the pattern "substitution_tag":"value to substitute".
public var substitutions: [String: String]?

/// Values that are specific to this personalization that will be carried along with the email and its activity data.
public var customArgs: [String: String]?

/// A unix timestamp allowing you to specify when you want your email to be delivered. Scheduling more than 72 hours in advance is forbidden.
public var sendAt: Date?

public enum CodingKeys: CodingKey, String {
case to
case cc
case bcc
case subject
case headers
case substitutions
case customArgs = "custom_args"
case sendAt = "send_at"
}
}
43 changes: 43 additions & 0 deletions Sources/SendGrid/Models/SendGridClient.swift
@@ -0,0 +1,43 @@
import Vapor

public final class SendGridClient: Service {
let httpClient: Client
let apiKey: String
let apiEndpoint = "https://api.sendgrid.com/v3/mail/send"

public init(client: Client, apiKey: String) {
self.httpClient = client
self.apiKey = apiKey
}

public func send(_ emails: [SendGridEmail], on worker: Worker) throws -> Future<Void> {

return try emails.map { (email) in

var headers: HTTPHeaders = [:]
headers.add(name: .contentType, value: MediaType.json.description)
headers.add(name: .authorization, value: "Bearer \(apiKey)")

let encoder = JSONEncoder()

encoder.dateEncodingStrategy = .secondsSince1970

let body = try encoder.encodeBody(from: email)

let request = HTTPRequest(method: .POST, url: URL(string: apiEndpoint) ?? .root, headers: headers, body: body)

return try httpClient.respond(to: Request(http: request, using: httpClient.container)).map(to: Void.self, { (response) -> (Void) in

switch response.http.status {
case .ok, .accepted: return
default:
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .secondsSince1970
let error = try decoder.decode(SendGridError.self, from: response.http.body.data ?? Data())

throw error
}
})
}.flatten(on: worker)
}
}

0 comments on commit 3ba1689

Please sign in to comment.