Swiftpack.co - Package - vapor-community/VaporMailgunService

Vapor Mailgun Service

Discord Platforms Swift 5.2 Vapor 4

Mailgun is a Vapor 4 service for a popular email sending API

Note: Vapor3 version is available in vapor3 branch and from 3.0.0 tag

Installation

Vapor Mailgun Service can be installed with Swift Package Manager

.package(url: "https://github.com/twof/VaporMailgunService.git", from: "4.0.0-rc")

.target(name: "App", dependencies: [
    .product(name: "Vapor", package: "vapor"),
    .product(name: "Mailgun", package: "VaporMailgunService")
])

Usage

Sign up and set up a Mailgun account here

Make sure you get an API key and register a custom domain

Configure

In configure.swift:

import Mailgun

// Called before your application initializes.
func configure(_ app: Application) throws {
    /// case 1
    /// put into your environment variables the following keys:
    /// MAILGUN_API_KEY=...
    app.mailgun.configuration = .environment

    /// case 2
    /// manually
    app.mailgun.configuration = .init(apiKey: "<api key>")
}

Note: If your private api key begins with key-, be sure to include it

Declare all your domains

extension MailgunDomain {
    static var myApp1: MailgunDomain { .init("mg.myapp1.com", .us) }
    static var myApp2: MailgunDomain { .init("mg.myapp2.com", .eu) }
    static var myApp3: MailgunDomain { .init("mg.myapp3.com", .us) }
    static var myApp4: MailgunDomain { .init("mg.myapp4.com", .eu) }
}

Set default domain in configure.swift

app.mailgun.defaultDomain = .myApp1

Usage

Mailgun is available on both Application and Request

// call it without arguments to use default domain
app.mailgun().send(...)
req.mailgun().send(...)

// or call it with domain
app.mailgun(.myApp1).send(...)
req.mailgun(.myApp1).send(...)

In configure.swift

import Mailgun

// Called before your application initializes.
func configure(_ app: Application) throws {
    /// configure mailgun

    /// then you're ready to use it
    app.mailgun(.myApp1).send(...).whenSuccess { response in
        print("just sent: \(response)")
    }
}

💡 NOTE: All the examples below will be with Request, but you could do the same with Application as in example above.

In routes.swift:

Without attachments
import Mailgun

func routes(_ app: Application) throws {
    app.post("mail") { req -> EventLoopFuture<ClientResponse> in
        let message = MailgunMessage(
            from: "postmaster@example.com",
            to: "example@gmail.com",
            subject: "Newsletter",
            text: "This is a newsletter",
            html: "<h1>This is a newsletter</h1>"
        )
        return req.mailgun().send(message)
    }
}
With attachments
import Mailgun

func routes(_ app: Application) throws {
    app.post("mail") { req -> EventLoopFuture<ClientResponse> in
        let fm = FileManager.default
        guard let attachmentData = fm.contents(atPath: "/tmp/test.pdf") else {
          throw Abort(.internalServerError)
        }
        let bytes: [UInt8] = Array(attachmentData)
        var bytesBuffer = ByteBufferAllocator().buffer(capacity: bytes.count)
        bytesBuffer.writeBytes(bytes)
        let attachment = File.init(data: bytesBuffer, filename: "test.pdf")
        let message = MailgunMessage(
            from: "postmaster@example.com",
            to: "example@gmail.com",
            subject: "Newsletter",
            text: "This is a newsletter",
            html: "<h1>This is a newsletter</h1>",
            attachments: [attachment]
        )
        return req.mailgun().send(message)
    }
}
With template (attachments can be used in same way)
import Mailgun

func routes(_ app: Application) throws {
    app.post("mail") { req -> EventLoopFuture<ClientResponse> in
        let message = MailgunTemplateMessage(
            from: "postmaster@example.com",
            to: "example@gmail.com",
            subject: "Newsletter",
            template: "my-template",
            templateData: ["foo": "bar"]
        )
        return req.mailgun().send(message)
    }
}
Setup content through Leaf

Using Vapor Leaf, you can easily setup your HTML Content.

First setup a leaf file in Resources/Views/Emails/my-email.leaf

<html>
    <body>
        <p>Hi #(name)</p>
    </body>
</html>

With this, you can change the #(name) with a variable from your Swift code, when sending the mail

import Mailgun

func routes(_ app: Application) throws {
    app.post("mail") { req -> EventLoopFuture<ClientResponse> in
        let content = try req.view().render("Emails/my-email", [
            "name": "Bob"
        ])

        let message = Mailgun.Message(
            from: "postmaster@example.com",
            to: "example@gmail.com",
            subject: "Newsletter",
            text: "",
            html: content
        )

        return req.mailgun().send(message)
    }
}
Setup routes
public func configure(_ app: Application) throws {
    // sets up a catch_all forward for the route listed
    let routeSetup = MailgunRouteSetup(forwardURL: "http://example.com/mailgun/all", description: "A route for all emails")
    app.mailgun().setup(forwarding: routeSetup).whenSuccess { response in
        print(response)
    }
}
Handle routes
import Mailgun

func routes(_ app: Application) throws {
    let mailgunGroup = app.grouped("mailgun")
    mailgunGroup.post("all") { req -> String in
        do {
            let incomingMail = try req.content.decode(MailgunIncomingMessage.self)
            print("incomingMail: (incomingMail)")
            return "Hello"
        } catch {
            throw Abort(.internalServerError, reason: "Could not decode incoming message")
        }
    }
}
Creating templates
import Mailgun

func routes(_ app: Application) throws {
    let mailgunGroup = app.grouped("mailgun")
    mailgunGroup.post("template") { req -> EventLoopFuture<ClientResponse> in
        let template = MailgunTemplate(name: "my-template", description: "api created :)", template: "<h1>Hello {{ name }}</h1>")
        return req.mailgun().createTemplate(template)
    }
}

Github

link
Stars: 69

Dependencies

Used By

Total: 0

Releases

Swift 5.2, Vapor 4 RC and Bug Fixes - 2020-03-03 08:00:14

Vapor 4 support - 2020-01-21 19:26:53

Mailgun now supports Vapor 4! Development for Vapor 4 will be done on master from here on out and Vapor 3 development will be done on the vapor3 branch.

import Mailgun

// Called before your application initializes.
func configure(_ app: Application) throws {
    /// case 1
    /// put into your environment variables the following keys:
    /// MAILGUN_API_KEY=...
    app.mailgun.configuration = .environment

    /// case 2
    /// manually
    app.mailgun.configuration = .init(apiKey: "<api key>")
} 
// call it without arguments to use default domain
app.mailgun().send(...)
req.mailgun().send(...)

// or call it with domain
app.mailgun(.myApp1).send(...)
req.mailgun(.myApp1).send(...)

Support Multiple Domains - 2020-01-21 19:23:14

You can now use multiple domains to send emails with Mailgun

let mailgun = Mailgun(apiKey: "<api key>")
services.register(mailgun, as: Mailgun.self)

// Put this extension at the bottom or create a new file for it
extension Mailgun.DomainConfig {
    static var euDomain: Mailgun.DomainConfig {
        return Mailgun.DomainConfig("mg.example.com", region: .eu)
    }
    static var usDomain: Mailgun.DomainConfig {
        return Mailgun.DomainConfig("mg2.example.com", region: .us)
    }
}
mailgun.send(message, domain: .euDomain, on: req) 

The new major version for this release is 3.0.0 to align with the supported Vapor version. 4.0.0 tag coming soon.

Fix for empty templates - 2019-08-27 22:39:40

In this release: #34

Templates Support - 2019-08-03 16:00:40

Thanks to @saicu for the implementation! https://documentation.mailgun.com/en/latest/user_manual.html#templates

Sending templated emails

let message = Mailgun.TemplateMessage(
    from: "postmaster@example.com",
    to: "example@gmail.com",
    subject: "Newsletter",
    template: "my-template",
    templateData: ["foo": "bar"]
)

let mailgun = try req.make(Mailgun.self)
return try mailgun.send(message, on: req)

Setting up email templates

let template = Mailgun.Template(name: "my-template", description: "api created :)", template: "<h1>Hello {{ name }}</h1>")
    
let mailgun = try req.make(Mailgun.self)
return try mailgun.createTemplate(template, on: req)

EU support - 2019-06-19 20:31:15

Added Inline Image Support - 2019-05-10 22:20:51

You can now add inline images that will display when the email is rendered. It uses the same format as image attachments.

Breaking API Changes - 2018-09-18 20:12:38

Now supporting "reply-to" header - 2018-09-12 23:55:24

Can now attach files - 2018-07-24 22:50:27

router.post("mail") { (req) -> Future<Response> in
    let fm = FileManager.default
    guard let attachmentData = fm.contents(atPath: "/tmp/test.pdf") else {
        throw Abort(.internalServerError)
    }
    let attachment = File(data: attachmentData, filename: "test.pdf")
    let message = Mailgun.Message(
        from: "postmaster@example.com",
        to: "example@gmail.com",
        subject: "Newsletter",
        text: "This is a newsletter",
        html: "<h1>This is a newsletter</h1>",
        attachments: [attachment]
    )
    
    let mailgun = try req.make(Mailgun.self)
    return try mailgun.send(message, on: req)
}

Full API key now required - 2018-05-31 17:55:00

It was pointed out in https://github.com/twof/VaporMailgunService/issues/11 that some api keys don't begin with key- so the bit prepending key- to every request that didn't include it was breaking.

Full api keys are now required to be used.

Mail can be sent with an application in addition to a request - 2018-05-10 17:27:08

Bug fix - 2018-05-06 19:35:45

There was an incorrect header that was preventing sending mail

Updated for Vapor 3 - 2018-05-05 02:30:54

No public api changes, but the primary features of Mailgun are supported now

Add Forwarding Setup - 2018-05-01 01:45:01

public func boot(_ app: Application) throws {
    // sets up a catch_all forward for the route listed
    let routeSetup = RouteSetup(forwardURL: "http://example.com/mailgun/all", description: "A route for all emails")
    let mailgunClient = try app.make(Mailgun.self)
    try mailgunClient.setupForwarding(setup: routeSetup, with: app).map { (resp) in
        print(resp)
    }
}

Added IncomingMailgun - 2018-05-01 01:18:48

Enables easy decoding of forwarded emails from Mailgun

mailgunGroup.post("all") { (req) -> Future<String> in
    do {
        return try req.content.decode(IncomingMailgun.self).map { (incomingMail) in
            return "Hello"
        }
    } catch {
        throw Abort(HTTPStatus.internalServerError, reason: "Could not decode incoming Mailgun")
    }
}

Cleanup - 2018-03-26 19:27:42

Removed unnecessary extension on HTTPRequest

NIO changes - 2018-03-22 07:09:52