Exploring Teams and SMTP with Appwrite
Tags: webdev, smtp, appwrite, javascript
Published: 2022-06-15 21:45:37

Last weekend I decided to spend some time exploring Appwrite's Teams and SMTP features. As someone who's relatively new to both subjects, I have some fun findings to share. This blog covers my journey and a demo app I built. Enjoy!

## SMTP Setup
Reading our documentation, we needed to include the follow variables to setup SMTP:

`_APP_SMTP_HOST` - SMTP server host name address. Use an empty string to disable all mail sending from the server. The default value for this variable is an empty string

`_APP_SMTP_PORT` - SMTP server TCP port. Empty by default.

`_APP_SMTP_SECURE` - SMTP secure connection protocol. Empty by default, change to ‘tls’ if running on a secure connection.

`_APP_SMTP_USERNAME` - SMTP server user name. Empty by default.

`_APP_SMTP_PASSWORD` - SMTP server user password. Empty by default.

`_APP_SYSTEM_EMAIL_ADDRESS` - Email address used for sending the emails. "team@appwrite.io" by default.

Seems simple enough... right? One blocker I hit while trying to setup SMTP was the example code in our docs:

```bash
_APP_SMTP_HOST=smtp.sendgrid.net
_APP_SMTP_PORT=587
_APP_SMTP_SECURE=tls
_APP_SMTP_USERNAME=YOUR-SMTP-USERNAME
_APP_SMTP_PASSWORD=YOUR-SMTP-PASSWORD
```

Naive as I were, I assumed the default value of `_APP_SYSTEM_EMAIL_ADDRESS` would work just fine. This, as I later learned while looking deeper into SMTP, needs to match the registered sender (either with SendGrid or AWS SES). For example, if I have a domain that I verified with SendGrid called `example.com`, I would have to provide a sender email under the domain, for example `system@example.com`. The default email under the `appwrite.io` domain would obviously not work here.

For every instance where you believe your Appwrite project should have sent out emails, know that the command `docker-compose logs -f appwrite-worker-mails` should give you a log on success or failure. This accelerates your debugging process greatly.

## Teams
Many core features of Appwrite Teams relies heavily on SMTP, which is why I started with SMTP setup.

My special edition of Appwrite Playground uses the following Teams related features:
- Creating a team
- Inviting user to a team
- Accepting a email invite
- Send reset password email (if the user is not already registered)
- Reset password using link from email
- Files and realtime using team and role based permissions.

I'll go through how each step works below, you can follow along by cloning the repository and pointing the Appwrite SDK client to your Appwrite instance.

### Creating a Team
Creating a new team is really simple. You just need a **team name** and **team ID**. Team id can be randomly generated by passing in the string `"unique()"`.

```js
let promise = appwrite.teams.create(id, name);

promise.then(
    function (response) {
        console.log("Team created."); // Success
    },
    function (error) {
        console.log(error); // Failure
    }
);
```

### Send a Team Invitation
Inviting a user to your team is a very similar process. You need to pass in **team name** a **team ID** like before. What's different here is you need a redirect URL to generate a link with attached secrets so the user can accept the invite.

```js
let promise = appwrite.teams.createMembership(id, email, roles, 'http://localhost:8000');

promise.then(
    function (response) {
        console.log("Membership created."); // Success
    },
    function (error) {
        console.log(error); // Failure
    }
);
```

The code above will send an email like this:

![Invite Email](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gl5yyuo7gpk8yt5c3qop.png)

Notice the query parameters appended after `'http://localhost:8000'` in the redirect URL. These secrets are used later to validate and invite.

### Accepting a Team Invitation
Remember the redirect URL from the last part? The URL should redirect to a part of your web app which will parse the generated query parameters to validate an invitation. I have a vanilla JS example below:

```js
function acceptInvite() {
    const params = new Proxy(new URLSearchParams(window.location.search), {
        get: (searchParams, prop) => searchParams.get(prop),
    });
    const [userId, membershipId, teamId, secret] = [params.userId, params.membershipId, params.teamId, params.secret];
    console.log([userId, membershipId, teamId, secret])

    appwrite.teams.updateMembershipStatus(teamId, membershipId, userId, secret).then(r=>{
        console.log("Accepted Invite")
    });

}
```

The example parses `userId`, `membershipId`, `teamId`, and `secret` from the URL sent to the invitee's email and uses them to update a member's verification status using the Teams API from the Appwrite SDK.

### Sending a Password Reset Email
If the user invited was not already registered, they'd need to create a new password. We take advantage of the same API as implementing a "Forgot Password" feature to reset/create a new password for the invited user. This looks something like:

```js
function sendRecover(event) {
    event.preventDefault();
    const email = event.target.elements["recover-email"].value;
    appwrite.account.createRecovery(email, 'http://localhost:8000').then(r=>{
        console.log("Sent Recovery Email")
    });
}
```

The link sent to the user's email will be one with appended query parameters, just like when sending invites:

![Password Recovery Email](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qze2cx1b54u6ixk6e1ml.png)

### Password Reset
Like before, we parse the query parameter from the URL and use the `updateRecovery` method from the Appwrite SDK to reset/create a new password.

```js
function recover(event) {
    event.preventDefault();
    // Parse Query Params
    const params = new Proxy(new URLSearchParams(window.location.search), {
        get: (searchParams, prop) => searchParams.get(prop),
    });
    const [userId, secret, pass, confirmPass] = [params.userId, params.secret, event.target.elements["recover-password"].value, event.target.elements["recover-confirm-password"].value];

    // Use Appwrite SDK to reset password
    appwrite.account.updateRecovery(userId, secret, pass, confirmPass).then((res)=>{
        console.log("Reset Success!")
    })
}
```

Now, the newly invited member of the team has a functioning account.

### Permissions with Teams
Appwrite's collections, files, and functions all work based on a [permission system](https://appwrite.io/docs/permissions). This means, unless specified through explicit permission, a resource is not accessible by anyone. If there's a file that a certain group of users should see, you can of course specify its permission to be `["user:a", "user: b", "user: c" ...]`, but that's a lot of work.

With a team, you can simply specify permissions to be `["team:example-team"]`, which can refer to a whole group of Appwrite users.

Within a team, you can also have more granular roles for users to further segment permission groups. Roles are arbitrarily define and are just strings, which can be `"cake"`, `"cookies"`, or any other string. For example, a user under `example-team` with role `cake` would have access to resources with `["team:example-team"]` or ``["team:example-team/cake"]` permissions. A user can also be a part of multiple roles, further adding flexibility.

## What's the Take-away?

SMTP is annoying! This is certainly item number one for me.

A close second is the importance of teams in permissions. Appwrite has a simple collection-document system to store files. One of the frequent complaints about this system is the lack of complex DB features like views, joins, and complicated queries. The reason why this is usually not a huge problem, is that for end-user applications, carefully planned team permissions will mean your user only ever fetches resources or documents they're meant to see. This greatly reduces the need to filtering, in most cases.

(With that said, more complex DB features are coming, anyway!!!)

📚 Learn more
- 🚀 [Appwrite Github
](https://github.com/appwrite)
- 📜 [Appwrite Docs](https://appwrite.io/docs)
- 💬 [Discord Community](https://appwrite.io/discord)