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.
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 are a way for you to extend and customize your Appwrite BaaS 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
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
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
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.
Another requirement to follow along is that you have a Mailgun account 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.
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.
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:
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
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.
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
$ 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.
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.
Let's first create a tarfile that contains our function.
$ 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
ποΈ Resources
Hope you enjoyed this article! We love contributions and encourage you to take a look at our open issues and ongoing RFCs.
If you get stuck anywhere, feel free to reach out to us on our friendly support channels run by humans π©βπ».
Here are some handy links for more information:
Top comments (1)
At Initialize Your Swift Function