Creating a Weekly Mailer with Rio
Tags: javascript, beginners, tutorial, aws
Published: 2022-10-24 12:30:44

Project git: https://github.com/rettersoft/rio-samples
Rio Docs: https://docs.retter.io

# What to Learn With This Project?

In this Rio project, our main goal is to creating a backend for an application that mails to its subscribers weekly.
In that road we are going to look at Rio's:

- Scheduling.
- Using other SDK's (Postmark).
- Communication between classes.
- Singleton architecture
- Model Usage

For following along you may want to use Postmark also. To getting your POSTMARK_API_TOKEN visit [Postmark](https://postmarkapp.com).

# Our Project's End Result

First we need to have a logic for our subscribers. For example they need to be able to subscribe :). Like This:

![New Subscriber Image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wkjw3bv10wpcc4x21r84.png)

After we run this method we need to validate the mail. So we e-mail the subscriber. Only then we will be able to send weekly mails to them. Like This:

![Validation Email Image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3pa4270tawwywe4jqh5y.png)

After that we can send our weekly mails to validated subs!

![Weekly Email Image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ekpmszhqoy0z6fe28dd7.png)

Don't worry your mail doesn't need to be so simple. You can use the HTML as you please in your mails.

Let's get to it!

# Creating Models

In Rio `Models` are used to validate method's input, make them more readable and easier to Debug. For More: [Rio Models](https://docs.retter.io/docs/projects/models).

We need only one model, which is `Subscriber`. And only the `email` will be mandatory. Rest will not be the input validation but the variables we need for further use.

`Subscriber`

```json
{
	"type": "object",
	"properties": {
		"email": {
			"type": "string",
			"format": "email"
		},
		"isApproved": {
			"type": "boolean"
		},
		"createdAt": {
			"type": "number"
		}
	},
	"required": ["email"],
	"additionalProperties": false
}
```

# Using Other SDK's 
Using other SDK's are extremely easy in Rio. Just go to your class package.json and add your desired SDK and it's version. Mine is looking like this:

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

Than deploy your project. That' s it you can use your SDK in your class.

Note that we are going to use *POSTMARK* both Subscriber and Mailer class. So *add postmark to both of them*.

In postmark and many other SDK's we need a token to use the accual SDK functions. And adding the tokens manually in the files can be a little bit cumbersome
and unsafe (tokens may be precious). So adding to them to the *Enviroments* is accually a good idea. So lets do that.

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

In our files we can use this token like this:

`const client = new postmark.ServerClient(process.env.POSTMARK_API_TOKEN);`

# Subscriber Class

This class will be responsible of creating subscribers, e-mailing them for validation, unsubscribe them if wanted, and validate them. But first we need to kinda "initialize" the class in `index.ts`. Let's go step by step.

## template.yml

This file determines which methods are going to be used in this class and how to use them. For example we just created our *model*. In template.yml we decide which of the methods are going to use this model. 

Our's looks like this for the *Subscriber* class:

``` yaml
init: index.init
getState: index.getState
getInstanceId: index.getInstanceId
methods:
  - method: getSubscribers
    handler: subscribe.getSubscribers
  - method: subscribe
    inputModel: Subscriber
    handler: subscribe.subscribe
  - method: validate
    inputModel: Subscriber
    handler: subscribe.validate
  - method: unSubscribe
    inputModel: Subscriber
    handler: subscribe.unSubscribe
  - method: sendMailToSubscribers
    handler: subscribe.sendMailToSubscribers
```

As you see we give each method a name in `method`, where to run them in `handler` and if necessary an `inputModel` for the models it is going to use.
There are a few more options if you want to check out in [template docs](https://docs.retter.io/docs/classes/templates).

## index.ts

I am just going to drop the whole `index.ts` then break it down.

```typescript
import RDK, { Data, InitResponse, Response, StepResponse } from "@retter/rdk";

const rdk = new RDK();

export async function authorizer(data: Data): Promise<Response> {
	return { statusCode: 401 };
}

export async function init(data: Data): Promise<InitResponse> {
	return {
		state: {
			private: {
				subscribers: [],
				preSubscribers: [],
			},
		},
	};
}

export async function getInstanceId(data: Data): Promise<string> {
	return "defaultInstance";
}

export async function getState(data: Data): Promise<Response> {
	return { statusCode: 200, body: data.state };
}
```

In **Init** we determine the variables to be created when the class gets created. So we create a subscribers array which will hold the validated subscribers and the preSubscribers which are not validated.
We create them here so we don' t need to worry about "If they exist" later.

**getInstanceId** is a makes this whole project **singleton**. We return the instanceId as "defaultInstance" so there will be only one instance in this project which is the "defaultInstance". You can change it's return value to your needs of course.

Rest of the code is default code actually. So if you get curious about rest check the [docs](https://docs.retter.io)!

## Subscribe.ts

This file going to hold the main methods we are going to use.
The imports and const values for this class looks likes this:

```typescript
import RDK, { Data, InitResponse, Response, StepResponse } from "@retter/rdk";
import { Subscriber } from "./rio";
const postmark = require("postmark");
const rdk = new RDK();
const client = new postmark.ServerClient(process.env.POSTMARK_API_TOKEN); // you put your postmarkToken in Settings -> Enviroment (c.retter.io)
```

Let's keep going!

## subscribe method

This method's input section needs to be looked at. If we look at this sample everything going to make sense:

`export interface Data<I = any, O = any, PUB = KeyValue, PRIV = KeyValue, USER = UserState, ROLE = RoleState>`

So for `I` (input) we typed `Subscriber`. So our Input value should be in the format of _Subscriber_ (our model). And for PRIV (private state) we typed `{ preSubscribers: Subscriber[]; subscribers: Subscriber[]`. So in private state there should be preSubscribers and subscribers array. And their members should be in the format of _Subscriber_. That's it!

```typescript
/**
 * @description creates a preSubscriber and sends a confirmation email
 */
export async function subscribe(
	data: Data<
		Subscriber,
		any,
		any,
		{ preSubscribers: Subscriber[]; subscribers: Subscriber[] }
	>
): Promise<Data> {
	// check if there is already a subscriber or presubscriber with the same email
	let subscriber = data.state.private.preSubscribers.find(
		(s) => s.email === data.request.body.email
	);
	if (!subscriber) {
		subscriber = data.state.private.subscribers.find(
			(s) => s.email === data.request.body.email
		);
	}

	// if there is a subscriber with the same email
	if (subscriber) {
		data.response = {
			statusCode: 404,
			body: {
				status: "This member is already registered to preSubscribers",
			},
		};
	}

	// if there is no subscriber with the same email
	else {
		const preSubscriber: Subscriber = {
			email: data.request.body.email,
			createdAt: Date.now(),
			isApproved: false,
		};
		data.state.private.preSubscribers.push(preSubscriber);
		// send mail to preSubscriber's mail to confirm subscription
		await client.sendEmail({
			From: "denizhan@rettermobile.com",
			To: preSubscriber.email,
			Subject: "Confirm your subscription",
			HtmlBody: `
			<h1>Welcome</h1>
			<p>Thanks for trying Rio News. We’re thrilled to have you on board. To finish your register process, validate your account by clicking the link below:</p>
			<!-- Action -->
			<table class="body-action" align="center" width="100%" cellpadding="0" cellspacing="0">
			  <tr>
				<td align="center">
				  <!-- Border based button https://litmus.com/blog/a-guide-to-bulletproof-buttons-in-email-design -->
				  <table width="100%" border="0" cellspacing="0" cellpadding="0">
					<tr>
					  <td align="center">
						<table border="0" cellspacing="0" cellpadding="0">
						  <tr>
							<td>
								<a href="https://${data.context.projectId}.api.retter.io/${data.context.projectId}/CALL/Subscribe/validate/defaultInstance?email=${preSubscriber.email}">Validate</a>
							</td>
						  </tr>
						</table>
					  </td>
					</tr>
				  </table>
				</td>
			  </tr>
			</table>
			</table>
			`,
			//HtmlBody: `<html> <body> <h1>Confirm your subscription</h1> <p>Click <a href="https://${data.context.projectId}.api.retter.io/${data.context.projectId}/CALL/Subscribe/validate/defaultInstance?email=${preSubscriber.email}">Validate</a> to confirm your subscription</p> </body> </html>`,
			TextBody: `Confirm your subscription by clicking the link below \n https://${data.context.projectId}.api.retter.io/${data.context.projectId}/CALL/Subscribe/validate/defaultInstance?email=${preSubscriber.email}`,
		});

		data.response = {
			statusCode: 200,
			body: {
				status: "New preSubscriber added to preSubscribers",
			},
		};
	}
	// send mail to new preSubscriber

	return data;
}
```

This method basically gets the input e-mail from the request and check if there are any users which has the same e-mail. If not, it sends a mail to input e-mail address using postmark. And in validation link we run the `validate` method.

**How Did We Run a Method via Link?**
If we take a closer look to this link:

`<a href="https://${data.context.projectId}.api.retter.io/${data.context.projectId}/CALL/Subscribe/validate/defaultInstance?email=${preSubscriber.email}">Validate </a>`

We should give the `projectId` via `${data.context.projectId}`. This finds our project. Then we need to _Call_ this method. After that we navigate to the method we want to run (in this case _validate_). Finally the instance value (in this case _defaultInstance_.
Giving parameters when running methods without tokens can be achieved by using _querystringparams_. In this example after we gave our instance we determine the variable `email` and value will be what' s inside the brackets `${ }`.

## Validate Method

As you can see we get the input using `data.requst.queryStringParams` here. After that the variable we want (in this case `email`).

``` typescript
/**
 * @description validates a preSubscriber and adds it to subscribers and removes from preSubscribers
 */
export async function validate(
	data: Data<
		Subscriber,
		any,
		any,
		{ preSubscribers: Subscriber[]; subscribers: Subscriber[] }
	>
): Promise<Data> {
	// check if user already registered to subscribers
	let subscriber = data.state.private.preSubscribers.find(
		(s) => s.email === data.request.queryStringParams.email
	);

	if (!subscriber) {
		data.response = {
			statusCode: 404,
			body: {
				status: "This member is not in the preSubscribers",
			},
		};
	} else {
		data.state.private.preSubscribers =
			data.state.private.preSubscribers.filter(
				(s) => s.email !== data.request.queryStringParams.email
			);
		subscriber.isApproved = true;
		data.state.private.subscribers.push(subscriber);
		data.response = {
			statusCode: 200,
			body: {
				status: `New subscriber ${subscriber.email} added to subscribers`,
			},
		};
	}

	return data;
}
```

In this method we get the input email and check this email if it is in the `preSubscribers` array which we hold in the `state.private`. If nothing goes wrong. We create a subscriber from that email and add that subscriber to subscribers array.

## unSubscribe Method

Nothing different here. This method is for the subsribers wants to leave our application. 

``` typescript
/**
 * @description Removes the subscriber to its email, from the preSubscribers and subscribers
 */
export async function unSubscribe(
	data: Data<
		Subscriber,
		any,
		any,
		{ subscribers: Subscriber[]; preSubscribers: Subscriber[] }
	>
): Promise<Data> {
	// check if user already registered to subscribers
	let subscriber = data.state.private.subscribers.find(
		(s) => s.email === data.request.body.email
	);
	if (!subscriber) {
		subscriber = data.state.private.preSubscribers.find(
			(s) => s.email === data.request.body.email
		);
	}

	if (!subscriber) {
		data.response = {
			statusCode: 404,
			body: {
				status: "This member is not in the subscribers or preSubscribers",
			},
		};
	} else {
		data.state.private.subscribers = data.state.private.subscribers.filter(
			(s) => s.email !== data.request.body.email
		);
		data.state.private.preSubscribers =
			data.state.private.preSubscribers.filter(
				(s) => s.email !== data.request.body.email
			);
		data.response = {
			statusCode: 200,
			body: {
				status: "Subscriber has removed from subscribers and presSubscribers",
			},
		};
	}

	return data;
}

```

We remove the all subscribers or preSubscribers which has the email given.


## getSubscribers

**Communcation Between Classes** is a another aspect of this sample. This method provides that functionallity. We simply return the subscribers in this method to call this method from another class later.

``` typescript
/**
 * @description returns the subscribers list for other classes to use
 */
export async function getSubscribers(
	data: Data<any, any, any, { subscribers: Subscriber[] }>
): Promise<Data> {
	data.response = {
		statusCode: 200,
		body: {
			subscribers: data.state.private.subscribers,
		},
	};
	return data;
}
```

# Mailer Class

This class has only one purpouse: send weekly mail to the subcribers using *scheduling*. 

## template.yml

We determine when and how often the method is going to run in here.

Our template for this class looks like this: 

``` yml
init: index.init
getState: index.getState
getInstanceId: index.getInstanceId
methods:
  - method: sendMailToSubscribers
    type: STATIC
    handler: sendMail.sendMailToSubscribers
    schedule: cron(0 18 ? * MON-FRI *)
```

For scheduled methods, type must be *Static*. And we can determine the schedule ourselves. Rio uses `cron` or we can use `rate` like this: `rate(30 minutes)`. For this example this `sendMailToSubscribers` method is going to be run at 18.00 every weekday. You can customise that whatever you like. For further information about this check [scheduling](https://docs.retter.io/docs/classes/tasks-scheduling).

## sendMailToSubscribers method and Whole File

Because this file only includes this method I tought it is best to share the whole picture. 

```typescript
import RDK, { Data, InitResponse, Response, StepResponse } from "@retter/rdk";
import { Subscriber, Classes } from "./rio"; // get the classes for calling the "getSubscribers" method we saw earlier
const postmark = require("postmark");
const rdk = new RDK();
const client = new postmark.ServerClient(process.env.POSTMARK_API_TOKEN); // you put yours in Settings -> Enviroment (c.retter.io)

/**
 * @description sends email to all subscribers
 */
export async function sendMailToSubscribers(
	data: Data<any, any, any, { subscribers: Subscriber[] }>
): Promise<Data> {
	const subscribeInstance = await Classes.Subscribe.getInstance();
	const subscribers = await subscribeInstance.getSubscribers();
	const subscribersArray = subscribers.body.subscribers;

	//create mail template
	const mailTemplate = {
		From: "denizhan@rettermobile.com",
		To: "",
		Subject: "Hi From Retter!",
		TextBody: "Hi there, here is your news this week: \n Blah Blah Blah",
	};

    // use await because mailing to all subscribers will not be happen instantly.
    // otherwise errors may occure
	await Promise.all(
		subscribersArray.map(async (subscriber) => {
			mailTemplate.To = subscriber.email;
			await client.sendEmail(mailTemplate);
		})
	);

	data.response = {
		statusCode: 200,
		body: {
			status: "Mail sent to all subscribers",
			subscribersArray,
		},
	};

	return data;
}

```

As you see in the code, our first goal is to get subscribersArray. In order to do that we get the instance of Subscribe class we imported. Than we can call the other classes method from now on, such as `getSubscribers` method from *Subscribe* class. 

After getting the subscribers, we create our mail template for Postmark. We change the destination for these mails in a loop and send their recievers.

Note that we used `await Promise.All` here. This is to making sure that every subscribers will get their mail.

# All Done!!!

That' it. Now you can use this to mail your friends weekly? Or in a big project that has 100.000 subs? Who knows? Maybe completely different project is in your mind. Whatever it is, hope this text helped you!

If you have any suggestions for this article please share.

Thanks!