├── .gitignore ├── LICENSE ├── Other └── logo.png ├── Package.resolved ├── Package.swift ├── README.md ├── Sources ├── MailCore │ ├── Extensions │ │ ├── Message+Mailgun.swift │ │ ├── Message+SMTP.swift │ │ ├── Message+SendGrid.swift │ │ └── Request+Mail.swift │ └── MailCore.swift └── MailCoreTestTools │ ├── Extensions │ └── MailProperty+Tools.swift │ └── MailCoreTestTools.swift ├── Tests └── MailCoreTests │ └── MailCoreTests.swift └── scripts ├── update.sh └── upgrade.sh /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.gitignore.io/api/vapor 3 | 4 | ### Vapor ### 5 | Config/secrets 6 | 7 | ### Vapor Patch ### 8 | Packages 9 | .build 10 | xcuserdata 11 | *.xcodeproj 12 | DerivedData/ 13 | .DS_Store 14 | 15 | # End of https://www.gitignore.io/api/vapor 16 | /.swiftpm 17 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 LiveUI 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Other/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LiveUI/MailCore/43716728291958a4bbe278720c85aef13cb8c248/Other/logo.png -------------------------------------------------------------------------------- /Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "object": { 3 | "pins": [ 4 | { 5 | "package": "Cryptor", 6 | "repositoryURL": "https://github.com/IBM-Swift/BlueCryptor.git", 7 | "state": { 8 | "branch": null, 9 | "revision": "6e83ae419817e7272f2f80a7a005a26df366dd8e", 10 | "version": "1.0.31" 11 | } 12 | }, 13 | { 14 | "package": "Socket", 15 | "repositoryURL": "https://github.com/IBM-Swift/BlueSocket.git", 16 | "state": { 17 | "branch": null, 18 | "revision": "49cab699e9f151eeb870ab93d14642425e824c19", 19 | "version": "1.0.49" 20 | } 21 | }, 22 | { 23 | "package": "SSLService", 24 | "repositoryURL": "https://github.com/IBM-Swift/BlueSSLService.git", 25 | "state": { 26 | "branch": null, 27 | "revision": "de36b4d078a12fffba982d17f59d2b7ca587bec9", 28 | "version": "1.0.49" 29 | } 30 | }, 31 | { 32 | "package": "Console", 33 | "repositoryURL": "https://github.com/vapor/console.git", 34 | "state": { 35 | "branch": null, 36 | "revision": "74cfbea629d4aac34a97cead2447a6870af1950b", 37 | "version": "3.1.1" 38 | } 39 | }, 40 | { 41 | "package": "Core", 42 | "repositoryURL": "https://github.com/vapor/core.git", 43 | "state": { 44 | "branch": null, 45 | "revision": "89c6989fd8b1e08acfd198afba1c38971bb814b2", 46 | "version": "3.10.1" 47 | } 48 | }, 49 | { 50 | "package": "Crypto", 51 | "repositoryURL": "https://github.com/vapor/crypto.git", 52 | "state": { 53 | "branch": null, 54 | "revision": "105c2f875588bf40dd24c00cef3644bf8e327770", 55 | "version": "3.4.1" 56 | } 57 | }, 58 | { 59 | "package": "DatabaseKit", 60 | "repositoryURL": "https://github.com/vapor/database-kit.git", 61 | "state": { 62 | "branch": null, 63 | "revision": "8f352c8e66dab301ab9bfef912a01ce1361ba1e4", 64 | "version": "1.3.3" 65 | } 66 | }, 67 | { 68 | "package": "HTTP", 69 | "repositoryURL": "https://github.com/vapor/http.git", 70 | "state": { 71 | "branch": null, 72 | "revision": "0464b715a4b59f54078bcf7a4b424767b03db5a5", 73 | "version": "3.4.0" 74 | } 75 | }, 76 | { 77 | "package": "LoggerAPI", 78 | "repositoryURL": "https://github.com/IBM-Swift/LoggerAPI.git", 79 | "state": { 80 | "branch": null, 81 | "revision": "e29073bb7cecf3673e56bcb16180e8fd0cb091f6", 82 | "version": "1.8.1" 83 | } 84 | }, 85 | { 86 | "package": "Multipart", 87 | "repositoryURL": "https://github.com/vapor/multipart.git", 88 | "state": { 89 | "branch": null, 90 | "revision": "fb216c5a8ef07dcd90aec8a4155e86c831acce97", 91 | "version": "3.1.3" 92 | } 93 | }, 94 | { 95 | "package": "Routing", 96 | "repositoryURL": "https://github.com/vapor/routing.git", 97 | "state": { 98 | "branch": null, 99 | "revision": "d76f339c9716785e5079af9d7075d28ff7da3d92", 100 | "version": "3.1.0" 101 | } 102 | }, 103 | { 104 | "package": "SendGrid", 105 | "repositoryURL": "https://github.com/vapor-community/sendgrid-provider.git", 106 | "state": { 107 | "branch": null, 108 | "revision": "4fca9d02fee4f343666e73651fedd4d26ed048e8", 109 | "version": "3.0.6" 110 | } 111 | }, 112 | { 113 | "package": "Service", 114 | "repositoryURL": "https://github.com/vapor/service.git", 115 | "state": { 116 | "branch": null, 117 | "revision": "fa5b5de62bd68bcde9a69933f31319e46c7275fb", 118 | "version": "1.0.2" 119 | } 120 | }, 121 | { 122 | "package": "swift-nio", 123 | "repositoryURL": "https://github.com/apple/swift-nio.git", 124 | "state": { 125 | "branch": null, 126 | "revision": "546610d52b19be3e19935e0880bb06b9c03f5cef", 127 | "version": "1.14.4" 128 | } 129 | }, 130 | { 131 | "package": "swift-nio-ssl", 132 | "repositoryURL": "https://github.com/apple/swift-nio-ssl.git", 133 | "state": { 134 | "branch": null, 135 | "revision": "0f3999f3e3c359cc74480c292644c3419e44a12f", 136 | "version": "1.4.0" 137 | } 138 | }, 139 | { 140 | "package": "swift-nio-ssl-support", 141 | "repositoryURL": "https://github.com/apple/swift-nio-ssl-support.git", 142 | "state": { 143 | "branch": null, 144 | "revision": "c02eec4e0e6d351cd092938cf44195a8e669f555", 145 | "version": "1.0.0" 146 | } 147 | }, 148 | { 149 | "package": "swift-nio-zlib-support", 150 | "repositoryURL": "https://github.com/apple/swift-nio-zlib-support.git", 151 | "state": { 152 | "branch": null, 153 | "revision": "37760e9a52030bb9011972c5213c3350fa9d41fd", 154 | "version": "1.0.0" 155 | } 156 | }, 157 | { 158 | "package": "SwiftSMTP", 159 | "repositoryURL": "https://github.com/IBM-Swift/Swift-SMTP.git", 160 | "state": { 161 | "branch": null, 162 | "revision": "23360cff5fcb92fb37041f5dd1ef16d7b8fe5ff0", 163 | "version": "5.1.1" 164 | } 165 | }, 166 | { 167 | "package": "TemplateKit", 168 | "repositoryURL": "https://github.com/vapor/template-kit.git", 169 | "state": { 170 | "branch": null, 171 | "revision": "4370aa99c01fc19cc8272b67bf7204b2d2063680", 172 | "version": "1.5.0" 173 | } 174 | }, 175 | { 176 | "package": "URLEncodedForm", 177 | "repositoryURL": "https://github.com/vapor/url-encoded-form.git", 178 | "state": { 179 | "branch": null, 180 | "revision": "20f68fbe7fac006d4d0617ea4edcba033227359e", 181 | "version": "1.1.0" 182 | } 183 | }, 184 | { 185 | "package": "Validation", 186 | "repositoryURL": "https://github.com/vapor/validation.git", 187 | "state": { 188 | "branch": null, 189 | "revision": "4de213cf319b694e4ce19e5339592601d4dd3ff6", 190 | "version": "2.1.1" 191 | } 192 | }, 193 | { 194 | "package": "Vapor", 195 | "repositoryURL": "https://github.com/vapor/vapor.git", 196 | "state": { 197 | "branch": null, 198 | "revision": "642f3d4d1f0eafad651c85524d0d1c698b55399f", 199 | "version": "3.3.3" 200 | } 201 | }, 202 | { 203 | "package": "Mailgun", 204 | "repositoryURL": "https://github.com/twof/VaporMailgunService.git", 205 | "state": { 206 | "branch": null, 207 | "revision": "9c41e744bced6f2f854bd3632333d57d6db46d51", 208 | "version": "1.8.1" 209 | } 210 | }, 211 | { 212 | "package": "VaporTestTools", 213 | "repositoryURL": "https://github.com/LiveUI/VaporTestTools.git", 214 | "state": { 215 | "branch": null, 216 | "revision": "135d02e2e2a632c134567754d65ce2afe262bab3", 217 | "version": "0.1.7" 218 | } 219 | }, 220 | { 221 | "package": "WebSocket", 222 | "repositoryURL": "https://github.com/vapor/websocket.git", 223 | "state": { 224 | "branch": null, 225 | "revision": "d85e5b6dce4d04065865f77385fc3324f98178f6", 226 | "version": "1.1.2" 227 | } 228 | } 229 | ] 230 | }, 231 | "version": 1 232 | } 233 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:4.0 2 | import PackageDescription 3 | 4 | let package = Package( 5 | name: "MailCore", 6 | products: [ 7 | .library(name: "MailCore", targets: ["MailCore"]), 8 | .library(name: "MailCoreTestTools", targets: ["MailCoreTestTools"]) 9 | ], 10 | dependencies: [ 11 | .package(url: "https://github.com/vapor/vapor.git", from: "3.0.0"), 12 | .package(url: "https://github.com/twof/VaporMailgunService.git", .upToNextMinor(from: "1.8.0")), 13 | .package(url: "https://github.com/vapor-community/sendgrid-provider.git", from: "3.0.5"), 14 | .package(url: "https://github.com/IBM-Swift/Swift-SMTP.git", from: "5.1.0"), 15 | .package(url: "https://github.com/IBM-Swift/LoggerAPI.git", .upToNextMinor(from: "1.8.0")), 16 | .package(url: "https://github.com/LiveUI/VaporTestTools.git", from: "0.1.5") 17 | ], 18 | targets: [ 19 | .target(name: "MailCore", dependencies: [ 20 | "Vapor", 21 | "Mailgun", 22 | "SendGrid", 23 | "SwiftSMTP" 24 | ] 25 | ), 26 | .target(name: "MailCoreTestTools", dependencies: [ 27 | "Vapor", 28 | "VaporTestTools", 29 | "MailCore" 30 | ] 31 | ), 32 | .testTarget(name: "MailCoreTests", dependencies: [ 33 | "MailCore", 34 | "MailCoreTestTools", 35 | "VaporTestTools" 36 | ] 37 | ) 38 | ] 39 | ) 40 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![LiveUI MailCore](https://github.com/LiveUI/MailCore/raw/master/Other/logo.png) 2 | 3 | ## 4 | 5 | [![Slack](https://img.shields.io/badge/join-slack-745EAF.svg?style=flat)](http://bit.ly/2B0dEyt) 6 | [![Jenkins](https://ci.liveui.io/job/LiveUI/job/MailCore/job/master/badge/icon)](https://ci.liveui.io/job/LiveUI/job/MailCore/) 7 | [![Platforms](https://img.shields.io/badge/platforms-macOS%2010.13%20|%20Ubuntu%2016.04%20LTS-ff0000.svg?style=flat)](https://github.com/LiveUI/Boost) 8 | [![Swift Package Manager](https://img.shields.io/badge/SPM-compatible-4BC51D.svg?style=flat)](https://swift.org/package-manager/) 9 | [![Swift 4](https://img.shields.io/badge/swift-4.0-orange.svg?style=flat)](http://swift.org) 10 | [![Vapor 3](https://img.shields.io/badge/vapor-3.0-blue.svg?style=flat)](https://vapor.codes) 11 | 12 | 13 | Mailing wrapper for multiple mailing services like Mailgun, SendGrid or SMTP 14 | 15 | # Features 16 | 17 | - [x] Mailgun 18 | - [x] SendGrid 19 | - [x] SMTP 20 | - [ ] Attachments 21 | - [ ] Multiple emails sent at the same time 22 | - [x] Multiple recipint, CC & BCC fields 23 | 24 | # Install 25 | 26 | Just add following line package to your `Package.swift` file. 27 | 28 | ```swift 29 | .package(url: "https://github.com/LiveUI/MailCore.git", .branch("master")) 30 | ``` 31 | 32 | # Usage 33 | 34 | Usage is really simple mkey! 35 | 36 | ## 1/3) Configure 37 | 38 | First create your client configuration: 39 | 40 | #### Mailgun 41 | 42 | ```swift 43 | let config = Mailer.Config.mailgun(key: "{mailgunApi}", domain: "{mailgunDomain}", region: "{mailgunRegion}") 44 | ``` 45 | 46 | #### SendGrid 47 | 48 | ```swift 49 | let config = Mailer.Config.sendGrid(key: "{sendGridApiKey}") 50 | ``` 51 | 52 | #### SMTP 53 | 54 | Use the `SMTP` struct as a handle to your SMTP server: 55 | 56 | ```swift 57 | let smtp = SMTP(hostname: "smtp.gmail.com", // SMTP server address 58 | email: "user@gmail.com", // username to login 59 | password: "password") // password to login 60 | 61 | let config = Mailer.Config.smtp(smtp) 62 | ``` 63 | 64 | #### SMTP using TLS 65 | 66 | All parameters of `SMTP` struct: 67 | 68 | ```swift 69 | let smtp = SMTP(hostname: String, 70 | email: String, 71 | password: String, 72 | port: Int32 = 465, 73 | useTLS: Bool = true, 74 | tlsConfiguration: TLSConfiguration? = nil, 75 | authMethods: [AuthMethod] = [], 76 | accessToken: String? = nil, 77 | domainName: String = "localhost", 78 | timeout: UInt = 10) 79 | 80 | let config = Mailer.Config.smtp(smtp) 81 | ``` 82 | 83 | ## 2/3) Register service 84 | 85 | Register and configure the service in your apps `configure` method. 86 | 87 | ```swift 88 | Mailer(config: config, registerOn: &services) 89 | ``` 90 | 91 | `Mailer.Config` is an `enum` and you can choose from any integrated services to be used 92 | 93 | ## 3/3) Send an email 94 | 95 | ```swift 96 | let mail = Mailer.Message(from: "admin@liveui.io", to: "bobby.ewing@southfork.com", subject: "Oil spill", text: "Oooops I did it again", html: "

Oooops I did it again

") 97 | return try req.mail.send(mail).flatMap(to: Response.self) { mailResult in 98 | print(mailResult) 99 | // ... Return your response for example 100 | } 101 | ``` 102 | 103 | # Testing 104 | 105 | Mailcore provides a `MailCoreTestTools` framework which you can import into your tests to get `MailerMock`. 106 | 107 | To register, and potentially override any existing "real" Mailer service, just initialize `MailerMock` with your services. 108 | 109 | ```swift 110 | // Register 111 | MailerMock(services: &services) 112 | 113 | // Retrieve in your tests 114 | let mailer = try! req.make(MailerService.self) as! MailerMock 115 | ``` 116 | 117 | `MailerMock` will store the last used result as well as the received message and request. Structure of the moct can be seen below: 118 | 119 | ```swift 120 | public class MailerMock: MailerService { 121 | 122 | public var result: Mailer.Result = .success 123 | public var receivedMessage: Mailer.Message? 124 | public var receivedRequest: Request? 125 | 126 | // MARK: Initialization 127 | 128 | @discardableResult public init(services: inout Services) { 129 | services.remove(type: Mailer.self) 130 | services.register(self, as: MailerService.self) 131 | } 132 | 133 | // MARK: Public interface 134 | 135 | public func send(_ message: Mailer.Message, on req: Request) throws -> Future { 136 | receivedMessage = message 137 | receivedRequest = req 138 | return req.eventLoop.newSucceededFuture(result: result) 139 | } 140 | 141 | public func clear() { 142 | result = .success 143 | receivedMessage = nil 144 | receivedRequest = nil 145 | } 146 | 147 | } 148 | ``` 149 | 150 | # Support 151 | 152 | Join our [Slack](http://bit.ly/2B0dEyt), channel #help-boost to ... well, get help :) 153 | 154 | # Enterprise AppStore 155 | 156 | Core package for [Einstore](http://www.einstore.io), a completely open source enterprise AppStore written in Swift! 157 | - Website: http://www.einstore.io 158 | - Github: https://github.com/Einstore/Einstore 159 | 160 | # Other core packages 161 | 162 | * [EinstoreCore](https://github.com/Einstore/EinstoreCore/) - AppStore core module 163 | * [ApiCore](https://github.com/LiveUI/ApiCore/) - Base user & team management including forgotten passwords, etc ... 164 | 165 | # Implemented thirdparty providers 166 | 167 | * Mailgun - https://github.com/twof/VaporMailgunService 168 | * SendGrig - https://github.com/vapor-community/sendgrid-provider 169 | * SMTP - https://github.com/IBM-Swift/Swift-SMTP 170 | 171 | # Code contributions 172 | 173 | We love PR’s, we can’t get enough of them ... so if you have an interesting improvement, bug-fix or a new feature please don’t hesitate to get in touch. If you are not sure about something before you start the development you can always contact our dev and product team through our Slack. 174 | 175 | # Author 176 | 177 | Ondrej Rafaj (@rafiki270 on [Github](https://github.com/rafiki270), [Twitter](https://twitter.com/rafiki270), [LiveUI Slack](http://bit.ly/2B0dEyt) and [Vapor Slack](https://vapor.team/)) 178 | 179 | # License 180 | 181 | MIT license, please see LICENSE file for more details. 182 | 183 | -------------------------------------------------------------------------------- /Sources/MailCore/Extensions/Message+Mailgun.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Message+Mailgun.swift 3 | // MailCore 4 | // 5 | // Created by Ondrej Rafaj on 19/03/2018. 6 | // 7 | 8 | import Foundation 9 | import Vapor 10 | import Mailgun 11 | 12 | 13 | extension Mailer.Message { 14 | 15 | /// Message as a Mailgun content 16 | func asMailgunContent() -> Mailgun.Message { 17 | return Mailgun.Message( 18 | from: from, 19 | to: to, 20 | cc: cc?.joined(separator: ","), 21 | bcc: bcc?.joined(separator: ","), 22 | subject: subject, 23 | text: text, 24 | html: html 25 | ) 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /Sources/MailCore/Extensions/Message+SMTP.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Message+SMTP.swift 3 | // MailCore 4 | // 5 | // Created by Ondrej Rafaj on 11/04/2018. 6 | // 7 | 8 | import Foundation 9 | import Vapor 10 | import SwiftSMTP 11 | 12 | 13 | extension Mailer.Message { 14 | 15 | /// Message as an SMTP email 16 | func asSmtpMail() -> Mail { 17 | let fromUser = Mail.User(email: from) 18 | 19 | let toUser = Mail.User(email: to) 20 | let ccUsers = (cc ?? []).map({ Mail.User(email: $0) }) 21 | let bccUsers = (bcc ?? []).map({ Mail.User(email: $0) }) 22 | 23 | let attachments: [Attachment] 24 | if let html = html { 25 | attachments = [Attachment(htmlContent: html)] 26 | } else { 27 | attachments = [] 28 | } 29 | 30 | let mail = Mail(from: fromUser, to: [toUser], cc: ccUsers, bcc: bccUsers, subject: subject, text: text, attachments: attachments) 31 | return mail 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /Sources/MailCore/Extensions/Message+SendGrid.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Message+SendGrid.swift 3 | // MailCore 4 | // 5 | // Created by Ondrej Rafaj on 11/04/2018. 6 | // 7 | 8 | import Foundation 9 | import Vapor 10 | import SendGrid 11 | 12 | 13 | extension Mailer.Message { 14 | 15 | /// Message as a SendGrid content 16 | func asSendGridContent() -> SendGridEmail { 17 | var content = [ 18 | [ 19 | "type": "text/plain", 20 | "value": text 21 | ] 22 | ] 23 | if let html = html { 24 | content.append( 25 | [ 26 | "type": "text/html", 27 | "value": html 28 | ] 29 | ) 30 | } 31 | 32 | if cc != nil || bcc != nil { 33 | print("SendGrid client doesn't yet support CC or BCC") 34 | } 35 | 36 | let message = SendGridEmail( 37 | personalizations: [Personalization(to: [EmailAddress(email: to)])], 38 | from: EmailAddress(email: from), 39 | subject: subject, 40 | content: content) 41 | 42 | return message 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /Sources/MailCore/Extensions/Request+Mail.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Request+Mail.swift 3 | // MailCore 4 | // 5 | // Created by Ondrej Rafaj on 19/03/2018. 6 | // 7 | 8 | import Foundation 9 | import Vapor 10 | 11 | 12 | public struct MailProperty { 13 | 14 | /// Request reference 15 | let request: Request 16 | 17 | /// Send email 18 | public func send(_ message: Mailer.Message) throws -> EventLoopFuture { 19 | let mailer = try request.make(MailerService.self) 20 | return try mailer.send(message, on: request) 21 | } 22 | 23 | /// Send email 24 | public func send(_ messages: [Mailer.Message]) throws -> EventLoopFuture<[(Mail, Mailer.Result)]> { 25 | let mailer = try request.make(MailerService.self) 26 | return try mailer.send(messages, on: request) 27 | } 28 | 29 | /// Send email 30 | public func send(from: String, to: String, subject: String, text: String) throws -> EventLoopFuture { 31 | return try send(Mailer.Message(from: from, to: to, subject: subject, text: text)) 32 | } 33 | } 34 | 35 | extension Request { 36 | 37 | /// Mail functionality accessor for request 38 | public var mail: MailProperty { 39 | return MailProperty(request: self) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Sources/MailCore/MailCore.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Boost.swift 3 | // MailCore 4 | // 5 | // Created by Ondrej Rafaj on 19/3/2018. 6 | // 7 | 8 | import Foundation 9 | import Vapor 10 | import Mailgun 11 | import SendGrid 12 | @_exported import SwiftSMTP 13 | 14 | 15 | /// Emailing service 16 | public protocol MailerService: Service { 17 | func send(_ message: Mailer.Message, on req: Request) throws -> Future 18 | func send(_ messages: [Mailer.Message], on req: Request) throws -> Future<[(Mail, Mailer.Result)]> 19 | } 20 | 21 | 22 | /// Mailer class 23 | public class Mailer: MailerService { 24 | 25 | /// Basic message 26 | public struct Message { 27 | public let from: String 28 | public let to: String 29 | public let cc: [String]? 30 | public let bcc: [String]? 31 | public let subject: String 32 | public let text: String 33 | public let html: String? 34 | 35 | /// Message init 36 | public init(from: String, to: String, cc: [String]? = nil, bcc: [String]? = nil, subject: String, text: String, html: String? = nil) { 37 | self.from = from 38 | self.to = to 39 | self.cc = cc 40 | self.bcc = bcc 41 | self.subject = subject 42 | self.text = text 43 | self.html = html 44 | } 45 | } 46 | 47 | /// Result returned by the services 48 | public enum Result { 49 | case serviceNotConfigured 50 | case success 51 | case failure(error: Error) 52 | } 53 | 54 | /// Service configuration 55 | public enum Config { 56 | case none 57 | case mailgun(key: String, domain: String, region: Mailgun.Region) 58 | case sendGrid(key: String) 59 | case smtp(SMTP) 60 | } 61 | 62 | /// Current email service configuration 63 | var config: Config 64 | 65 | 66 | // MARK: Initialization 67 | 68 | /// Mailer initialization. MailerService get's registered to the services at this point, there is no need to do that manually! 69 | @discardableResult public init(config: Config, registerOn services: inout Services) throws { 70 | self.config = config 71 | 72 | switch config { 73 | case .mailgun(let key, let domain, let region): 74 | services.register(Mailgun(apiKey: key, domain: domain, region: region), as: Mailgun.self) 75 | case .sendGrid(key: let key): 76 | let config = SendGridConfig(apiKey: key) 77 | services.register(config) 78 | try services.register(SendGridProvider()) 79 | default: 80 | break 81 | } 82 | 83 | services.register(self, as: MailerService.self) 84 | } 85 | 86 | // MARK: Public interface 87 | 88 | /// Send a message using a provider defined in `config: Config` 89 | public func send(_ message: Message, on req: Request) throws -> Future { 90 | switch config { 91 | case .mailgun: 92 | let mailgunClient = try req.make(Mailgun.self) 93 | return try mailgunClient.send(message.asMailgunContent(), on: req).map(to: Mailer.Result.self) { _ in 94 | return Mailer.Result.success 95 | }.catchMap({ error in 96 | return Mailer.Result.failure(error: error) 97 | } 98 | ) 99 | case .sendGrid(_): 100 | let email = message.asSendGridContent() 101 | let sendGridClient = try req.make(SendGridClient.self) 102 | return try sendGridClient.send([email], on: req.eventLoop).map(to: Mailer.Result.self) { _ in 103 | return Mailer.Result.success 104 | }.catchMap({ error in 105 | return Mailer.Result.failure(error: error) 106 | } 107 | ) 108 | case .smtp(let smtp): 109 | let promise = req.eventLoop.newPromise(Mailer.Result.self) 110 | smtp.send(message.asSmtpMail()) { error in 111 | if let error = error { 112 | promise.succeed(result: Mailer.Result.failure(error: error)) 113 | } else { 114 | promise.succeed(result: Mailer.Result.success) 115 | } 116 | } 117 | return promise.futureResult 118 | default: 119 | return req.eventLoop.newSucceededFuture(result: Mailer.Result.serviceNotConfigured) 120 | } 121 | } 122 | 123 | /// Send multiple messages using a provider defined in `config: Config` 124 | public func send(_ messages: [Message], on req: Request) throws -> Future<[(Mail, Mailer.Result)]> { 125 | switch config { 126 | case .mailgun: 127 | throw Abort(.notImplemented, reason: "Sending mass email using Mailgun is not currently supported.") 128 | case .sendGrid(_): 129 | throw Abort(.notImplemented, reason: "Sending mass email using SendGrid is not currently supported.") 130 | case .smtp(let smtp): 131 | let promise = req.eventLoop.newPromise([(Mail, Mailer.Result)].self) 132 | smtp.send(messages.map { $0.asSmtpMail() }, progress: nil) { mails, errors in 133 | let errors = errors.map { ($0.0, Mailer.Result.failure(error: $0.1)) } 134 | let successes = mails.map { ($0, Mailer.Result.success) } 135 | promise.succeed(result: errors + successes) 136 | } 137 | return promise.futureResult 138 | default: 139 | throw Abort(.serviceUnavailable, reason: "Mails could not be sent: you need to configure your mail service.") 140 | } 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /Sources/MailCoreTestTools/Extensions/MailProperty+Tools.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MailProperty+Tools.swift 3 | // MailCoreTestTools 4 | // 5 | // Created by Ondrej Rafaj on 20/03/2018. 6 | // 7 | 8 | import Foundation 9 | @testable import MailCore 10 | 11 | 12 | extension MailProperty { 13 | 14 | public var mock: MailerMock { 15 | let mailer = try! request.make(MailerService.self) 16 | return mailer as! MailerMock 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /Sources/MailCoreTestTools/MailCoreTestTools.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Boost.swift 3 | // MailCore 4 | // 5 | // Created by Ondrej Rafaj on 19/3/2018. 6 | // 7 | 8 | import Foundation 9 | import Vapor 10 | @testable import Service 11 | @_exported import MailCore 12 | import VaporTestTools 13 | 14 | 15 | public class MailerMock: MailerService { 16 | public var result: Mailer.Result = .success 17 | public var receivedMessage: Mailer.Message? 18 | public var receivedRequest: Request? 19 | 20 | 21 | // MARK: Initialization 22 | 23 | @discardableResult public init(services: inout Services) { 24 | services.remove(type: Mailer.self) 25 | services.register(self, as: MailerService.self) 26 | } 27 | 28 | // MARK: Public interface 29 | 30 | public func send(_ message: Mailer.Message, on req: Request) throws -> Future { 31 | receivedMessage = message 32 | receivedRequest = req 33 | return req.eventLoop.newSucceededFuture(result: result) 34 | } 35 | 36 | public func send(_ messages: [Mailer.Message], on req: Request) throws -> EventLoopFuture<[(Mail, Mailer.Result)]> { 37 | fatalError("Not implemented") 38 | } 39 | 40 | public func clear() { 41 | result = .success 42 | receivedMessage = nil 43 | receivedRequest = nil 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /Tests/MailCoreTests/MailCoreTests.swift: -------------------------------------------------------------------------------- 1 | import Dispatch 2 | import XCTest 3 | 4 | 5 | final class MailCoreTests : XCTestCase { 6 | 7 | func testNothing() throws { 8 | XCTAssert(true) 9 | } 10 | 11 | static let allTests = [ 12 | ("testNothing", testNothing), 13 | ] 14 | 15 | } 16 | -------------------------------------------------------------------------------- /scripts/update.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | rm -rf .build 4 | vapor clean -y --verbose 5 | vapor xcode -n --verbose 6 | -------------------------------------------------------------------------------- /scripts/upgrade.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | rm -rf .build 4 | vapor clean -y --verbose 5 | rm Package.resolved 6 | vapor xcode -n --verbose 7 | --------------------------------------------------------------------------------