Send Welcome Emails using Appwrite's Swift Cloud Functions
Tags: swift, tutorial, serverless, cloud
Published: 2021-10-22 09:18:55

![Cover Image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/aeofzxl2bcjq9t6dfe3g.png)

For all the Swift developers out there, we have good news! We just announced our Swift SDK to work with server side APIs of Appwrite. You can learn more about our Swift SDK in our [Swift SDK announcement post](swift-SDK-announcement-post).

In this tutorial however, we are going to write, deploy and run our very own **Welcome Email Cloud Function** with Swift. Let's get started. 

## 🤔 What are Appwrite Cloud Functions?
[Appwrite Cloud Functions](https://appwrite.io/docs/functions) are a way for you to extend and customize your [Appwrite BaaS](https://appwrite.io) functionality by allowing you to execute custom code. Appwrite can execute your function either explicitly or in response to any Appwrite system event like account creation, user login, database updates and much more. You can also schedule your functions to run according to a CRON schedule or trigger them manually by hitting an HTTP endpoint using the Appwrite client or server APIs.

## 🗒️ Prerequisites

In order to continue with this tutorial, you'll need to have the latest version of Appwrite installed and an Appwrite project setup to test this function. If you have not already installed Appwrite, please do so. Installing Appwrite is really simple. Based on your operating system, run one of the following commands and installation should be complete in less than 2 minutes.

### Unix
```bash
docker run -it --rm \
    --volume /var/run/docker.sock:/var/run/docker.sock \
    --volume "$(pwd)"/appwrite:/usr/src/code/appwrite:rw \
    --entrypoint="install" \
    appwrite/appwrite:0.11.0
```

### Windows CMD
```powershell
docker run -it --rm ^
    --volume //var/run/docker.sock:/var/run/docker.sock ^
    --volume "%cd%"/appwrite:/usr/src/code/appwrite:rw ^
    --entrypoint="install" ^
    appwrite/appwrite:0.11.0
```

### Windows PowerShell
```powershell
docker run -it --rm ,
    --volume /var/run/docker.sock:/var/run/docker.sock ,
    --volume ${pwd}/appwrite:/usr/src/code/appwrite:rw ,
    --entrypoint="install" ,
    appwrite/appwrite:0.11.0
```

You can also find detailed installation instructions in the official Appwrite [installation docs](https://appwrite.io/docs/installation).

Another requirement to follow along is that you have a [Mailgun account](https://www.mailgun.com/) with a valid **Mailgun Domain** and **API Key**. With that aside, we're ready to get started. 

## 🔓️ Activate Swift Functions Runtime

The Swift runtime needs to be `enabled` for you to be able to use it. This can be done easily using environment variables. The environment variables can be found in the `.env` file located in the Appwrite installation folder. If it's not present already, you'll need to add the following key-value pair to the `.env` file.

```
_APP_FUNCTIONS_RUNTIMES=swift-5.5
```

Next, restart your stack using `docker-compose up -d`

## 🔑 Initialize Your Swift Function

First, create a project folder where you will create all the necessary files for your function. We will call this folder `welcome-email`. Once inside this folder, you can create a new Swift project with the following command.

```bash
docker run --rm -it -v $(pwd):/app -w /app swift:5.5 swift package init WelcomeEmail
```

This will initialize a new Swift package project. It should create bunch of files, important ones to notice are

```
.
├── Sources/WelcomeEmail/main.swift
└── Package.swift
```

## ➕ Add Appwrite Swift SDK dependency
Open the `welcome-email` folder in your favorite IDE and add the following code to your `Package.swift` file.

```swift
import PackageDescription

let package = Package(
    name: "WelcomeEmail",
    dependencies: [
        .package(url: "https://github.com/swift-server/async-http-client.git", from: "1.0.0"),
    ],
    targets: [
        .target(
            name: "WelcomeEmail",
            dependencies: [
                .product(name: "AsyncHTTPClient", package: "async-http-client"),
            ])
    ]
)
```
Here we add the `async-http-client` SDK for swift under dependencies as well as under the executable's target dependencies.

## ✍️  Write your Function
Open `Sources/WelcomeEmail/main.swift` and fill in the following code:

```swift
import AsyncHTTPClient
import Foundation

func sendSimpleMessage(name: String, email: String) throws  {
    let message = "Welcome \(name)!"
    let targetURL = "https://api.mailgun.net/v3/\(MAILGUN_DOMAIN)/messages"
    let params =  [
        "from" : "Excited User <hello@appwrite.io>",
        "to" : email,
        "subject" : "hello",
        "text" : message
    ]

    var request: HTTPClient.Request
    do {
        request = try HTTPClient.Request(
            url: targetURL,
            method: .RAW(value: "POST")
        )

        let auth = "api:\(MAILGUN_API_KEY)".data(using: String.Encoding.utf8)!.base64EncodedString()
        request.headers.add(name: "Content-Type", value: "multipart/form-data")
        request.headers.add(name: "Authorization", value: "Basic \(auth)")
        
        buildMultipart(&request, with: params)

        httpClient.execute(request: request).whenComplete { result in
            switch result {
            case .failure(let error):
                print("Error: \(error)")
            case .success(let response):
                if response.status == .ok {
                    print("Message sent!")
                } else {
                    print("Error: \(response.status)")
                }
            }

            group.leave();
        }

    } catch let error {
        print(error)
        return
    }
}

let MAILGUN_DOMAIN = ProcessInfo.processInfo.environment["MAILGUN_DOMAIN"] ?? "";
let MAILGUN_API_KEY = ProcessInfo.processInfo.environment["MAILGUN_API_KEY"] ?? "";
let APPWRITE_FUNCTION_EVENT_DATA = ProcessInfo.processInfo.environment["APPWRITE_FUNCTION_EVENT_DATA"] ?? "{}"

let httpClient = HTTPClient(eventLoopGroupProvider: .createNew)
let group = DispatchGroup()

do {
    let data = Data(APPWRITE_FUNCTION_EVENT_DATA.utf8)

    guard let json = try JSONSerialization.jsonObject(with: data) as? [String: Any] else {
        throw "Unable to parse APPWRITE_FUNCTION_EVENT_DATA"
    }
    
    guard let name = json["name"] as? String else {
        throw "Unable to parse name"
    }
    
    guard let email = json["email"] as? String else {
        throw "Unable to parse email"
    }

    group.enter()
    try sendSimpleMessage(name: name, email: email)
    group.wait()

} catch let error {
    print(error)
}
```
The environment variables that we are accessing here are either already available or are later set in the Appwrite Function's settings.

Next create a new file under `Sources/WelcomeEmail/File.swift` that will house one of our helper classes

```swift
import NIO

open class File {

    public let name: String
    public var buffer: ByteBuffer

    public init(name: String, buffer: ByteBuffer) {
        self.name = name
        self.buffer = buffer
    }
}
```

Lastly, we need to create one more file `Sources/WelcomeEmail/Utils.swift` for some of our utility functions. 

```swift
import AsyncHTTPClient
import NIO

let DASHDASH = "--"
let CRLF = "\r\n"
let boundaryChars = "abcdefghijklmnopqrstuvwxyz1234567890"

func randomBoundary() -> String {
    var string = ""
    for _ in 0..<16 {
        string.append(boundaryChars.randomElement()!)
    }
    return string
}

func buildMultipart(
    _ request: inout HTTPClient.Request,
    with params: [String: Any?] = [:]
) {
    func addPart(name: String, value: Any) {
        bodyBuffer.writeString(DASHDASH)
        bodyBuffer.writeString(boundary)
        bodyBuffer.writeString(CRLF)
        bodyBuffer.writeString("Content-Disposition: form-data; name=\"\(name)\"")

        if let file = value as? File {
            bodyBuffer.writeString("; filename=\"\(file.name)\"")
            bodyBuffer.writeString(CRLF)
            bodyBuffer.writeString("Content-Length: \(bodyBuffer.readableBytes)")
            bodyBuffer.writeString(CRLF+CRLF)
            bodyBuffer.writeBuffer(&file.buffer)
            bodyBuffer.writeString(CRLF)
            return
        }

        let string = String(describing: value)
        bodyBuffer.writeString(CRLF)
        bodyBuffer.writeString("Content-Length: \(string.count)")
        bodyBuffer.writeString(CRLF+CRLF)
        bodyBuffer.writeString(string)
        bodyBuffer.writeString(CRLF)
    }

    let boundary = randomBoundary()
    var bodyBuffer = ByteBuffer()

    for (key, value) in params {
        switch key {
        case "file":
            addPart(name: key, value: value!)
        default:
            if let list = value as? [Any] {
                for listValue in list {
                    addPart(name: "\(key)[]", value: listValue)
                }
                continue
            }
            addPart(name: key, value: value!)
        }
    }

    bodyBuffer.writeString(DASHDASH)
    bodyBuffer.writeString(boundary)
    bodyBuffer.writeString(DASHDASH)
    bodyBuffer.writeString(CRLF)

    request.headers.remove(name: "content-type")
    request.headers.add(name: "Content-Length", value: bodyBuffer.readableBytes.description)
    request.headers.add(name: "Content-Type", value: "multipart/form-data;boundary=\"\(boundary)\"")
    request.body = .byteBuffer(bodyBuffer)
}

extension String: Error {}
```

## ⚙️ Build the Function Binary
In order to deploy our function, we need to first build the project. Our runtime is based on the `slim` version of official **Swift** docker image, so we'll use the official **Swift** docker image to build our project.

From the `welcome-email` directory, run the following command

```bash
$ docker run --rm -it -v $(pwd):/app -w /app swift:5.5 swift build
```
This should build the project. Ensure that your folder structure looks like this
```
.
├── .build/x86_64-unknown-linux-gnu/debug/WelcomeEmail
├── Package.swift
├── README.md
└── Sources
    └── WelcomeEmail
        ├── File.swift
        ├── main.swift
        └── Utils.swift
```

> There could be other files and folders as well, but you can ignore those.

## ☁️ Create a Function in Your Appwrite Console

Login to your Appwrite console and open the project of your choosing. On the sidebar, tap on the `Functions` menu. In the following screen, tap the `Add Function` button.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kp55xzrfjp6ztp0a57e4.png)
 

We'll call our Cloud function `WelcomeEmail`. and select `swift-5.5` for the environment. Then tap **Create**.

## 🧑‍💻 Deploy Tag
Once your function is created, you'll be taken to the **Function Overview** screen. Click the **Deploy Tag** button at the bottom of the function overview page and then switch to the **Manual** tab. 

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1l1gghjzmq0oh8jjkgoy.png)
 


Let's first create a tarfile that contains our function. 

```bash
$ tar -zcvf code.tar.gz -C .build/x86_64-unknown-linux-gnu/ debug/WelcomeEmail
```

Head back to the **Deploy a New Tag** dialog and upload the `code.tar.gz` that we just created and use `./WelcomeEmail` for the **Command**.

## ✅ Activate tag

Once you deploy your tag, it will be listed under **Tags** on the **Overview** page. Activate your most recent tag ( if you have multiple versions ).

## 💡 Adding Triggers and Environment Variables

On the **Functions** page, switch to the **Settings** tab. A function can be triggered based on an `event` or a `schedule` you choose. This particular function should be triggered by the `users.create` and the `account.create` events. Select these events from the events section.

In the **Variables** section, tap the **Add Variable** button and add the following variables and click the **Update** button.

* **MAILGUN_API_KEY** - Your Mailgun API Key.
* **MAILGUN_DOMAIN** - Your Mailgun domain.

## ✨️ Verify it's working

It's about time to check if all our hard work has finally paid off! Let's create a user and verify that our Cloud Function has sent them a welcome email! 

Head to the **Users** page from the Sidebar and click the **Add User** button. Give your user a **valid name** and **email ID** and click **Create**. Back in the **Functions page**, you can now examine the logs of your function execution.

> If you're using a sandbox Mailgun account, ensure that the email ID is an [Authorized Recipient](https://help.mailgun.com/hc/en-us/articles/217531258-Authorized-Recipients) 

## 📚️ Resources

Hope you enjoyed this article! We love contributions and encourage you to take a look at our [open issues](https://github.com/appwrite/appwrite/issues) and [ongoing RFCs](https://github.com/appwrite/rfc/pulls).

If you get stuck anywhere, feel free to reach out to us on our [friendly support channels](https://appwrite.io/discord) run by humans 👩‍💻.

Here are some handy links for more information:

- [Appwrite Contribution Guide](https://github.com/appwrite/appwrite/blob/master/CONTRIBUTING.md)
- [Appwrite Github](https://github.com/appwrite)
- [Appwrite Documentation](https://appwrite.io/docs)