# RFC: Email service
## Ventura email API
- Integrated with admin templates defined in GO
- Support for nested templates and translations
- Can be consumed from external services like Make via Hasura actions
- Allow basic customization
------------------------------------------------------------------------------------------
#### Sending email
<details>
<summary><code>POST</code> <code><b>/send</b></code> <code>(using a GO template)</code></summary>
##### Parameters
| name | type | data type | description |
|-----------|-----------|-------------------------|-----------------------------------------------------------------------|
| templateId | required | string | UUID of `email_template` entity in GO |
| from | required | string | sender email address
| to | required | string | receiver email address
| bcc | optional | string | blind carbon copy email address
| lang | optional | string | `locale_id` if it's a multilang template. By default, will be the master language (en).
| variables | optional | object | variables to be injected in the template
| attachments | optional | string[] | list of object names (file key) stored in Google Cloud Storage. These will be independent to the static attachments the template may have.
| brandStyles | optional | boolean | `false` by default. Include branding header and footer. This option will overwrite the value defined in the original template.
| customStyle | optional | string | custom CSS string
##### Responses
| http code | content-type | response |
|---------------|-----------------------------------|---------------------------------------------------------------------|
| `200` | `application/json` | <pre>{<br> "status": "success",<br> "message": "Email queued successfully",<br> "data": {<br> "emailId": "23e4567-e89b-12d3-a456-426614174000",<br> "templateId": "23e4567-e89b-12d3-a456-426614174000",<br> "from": "karen@gmail.com",<br> "to": "sophia@example.com",<br> "bcc": "jhon@example.com",<br> "attachments": [<br> "mybucket/pfs/something.pdf"<br> ],<br> "body": "Hello Sophia, this is a <b>test</b>"<br> }<br>}</pre> | |
| `400` | `application/json` | <pre>{<br> "status": "error",<br> "message": "invalid request",<br> "error": "Template `23e4567-e89b-12d3-a456-426614174000` not found"<br>}</pre> |
| `400` | `application/json` | <pre>{<br> "status": "error",<br> "message": "invalid request",<br> "error": "Variable `FAKE_VAR` not found in template `23e4567-e89b-12d3-a456-426614174000`"<br>}</pre> |
| `400` | `application/json` | <pre>{<br> "status": "error",<br> "message": "invalid request",<br> "error": "`witherror@something` is not a valid email address"<br>}</pre> |
| `400` | `application/json` | <pre>{<br> "status": "error",<br> "message": "invalid request",<br> "error": "attachment `mybucket/pfs/something.pdf` not found"<br>}</pre> |
| `500` | `application/json` | <pre>{<br> "status": "error",<br> "message": "server error",<br> "error": "unknown error: something went wrong, please check the logs"<br>}</pre> | |
</details>
------------------------------------------------------------------------------------------
<details>
<summary>Implementation details</summary>
The most important entities that hold complex logic are templates, variables and attachments. Here explained in context:
#### Template
Template entity holds the main data about the email, such as:
- Subject and Body with conditional blocks handling
- Translations
- Linked brands
with this new service, we have to add support for:
- A `boolean` flag to enable/disable branding decoration (logos, header, footer). e.g: `is_brand_style_enabled`
#### Variables
It's kay-value where the key must match the names of the variables defined in the template. The GO must extend the current functionality to support new types of variables.
There are 3 types of variables:
##### Simple text
The value is a string or a number, most common variables will be just like the following example:
```typescript
export const standard: SendEmailRequest = {
templateId: "23e4567-e89b-12d3-a456-426614174000",
from: "sophia@galapatours.com",
to: "karen@gmail.com",
bcc: "jhon@example.com",
attachments: ["mybucket/pfs/something.pdf"],
variables: {
VAR_1: "test",
CLIENT_NAME: "Karen",
REMAINING_TICKETS: 11,
},
};
```
##### Formatted text
The value is an object with 2 keys:
```typescript
export type FormattedVar = {
format: "html" | "draft" | "text";
content: string;
};
```
By default, all variables saved in GO are `"draft"` which is a tree-like format but services that consume this API could use html as well:
```typescript
export const standardWithHtmlVar: SendEmailRequest = {
templateId: "23e4567-e89b-12d3-a456-426614174000",
from: "sophia@galapatours.com",
to: "karen@gmail.com",
bcc: "jhon@example.com",
attachments: ["mybucket/pfs/something.pdf"],
variables: {
VAR_1: {
format: "html",
content: "<b>test</b>",
},
CLIENT_NAME: "Karen", // this is equivalent to { format: "text", content: "Karen"}
},
};
```
##### Nested Template
This type of variable unlocks the possibility to have nested templates, as the previous, it is also an object (or array of objects) but with the following shape:
```typescript
export type NestedTemplateVar = {
templateId: string;
variables?: Record<string, string | number>;
}[];
```
The `templateId` represents a different template than the original and `variables` represents variables of simple text or formatted text and are scoped in to only this template. The API will support just one (1) level of nested TemplateVariables.
Example:
```typescript
export const withNested: SendEmailRequest = {
templateId: "23e4567-e89b-12d3-a456-426614174000",
from: "sophia@galapatours.com",
to: "karen@gmail.com",
bcc: "jhon@example.com",
attachments: ["mybucket/pfs/something.pdf"],
variables: {
VAR_1: "test",
CLIENT_NAME: "Karen",
MY_NESTED_VAR: {
templateId: "23e4567-e89b-12d3-a456-426614174000",
variables: {
VAR_1: "test nested",
},
},
},
};
```
You can also pass an array of `NestedTemplateVar` to handle an iterable block of templates:
```typescript
export const withNestedIterable: SendEmailRequest = {
templateId: "23e4567-e89b-12d3-a456-426614174000",
from: "sophia@galapatours.com",
to: "karen@gmail.com",
bcc: "jhon@example.com",
attachments: ["mybucket/pfs/something.pdf"],
variables: {
VAR_1: "test",
CLIENT_NAME: "Karen",
MY_NESTED_VAR: [
{
templateId: "23e4567-e89b-12d3-a456-426614174000",
variables: {
VAR_1: "test nested 1",
},
},
{
templateId: "23e4567-e89b-12d3-a456-426614174000",
variables: {
VAR_1: "test nested 2",
},
},
],
},
};
```
This is useful to render structured lists in the email at scale since all the features of email template admin applies here, with some limitations:
- **Attachments**: they will be ignored
- **Lang**: will be use the same specified in the request or in the root template, make sense, right?
- **Nested template variables**: only support one(1) level of nesting.
###### Impact in GO
The **email variables admin** needs to have a way to link an existing template as a variable, and should be filtreable within the variables admin. And the **template admin** should be able to filter templates that are used as variables. This can be achieved with a new foreign field `template_id` in the `email_variable` table.
Addionally, the **template admin** the variables chooser (dropdown) and the embed variable table (in Email tab) should distinguish the if the type of variable is a normal one (simple text) or a nested one (email template var). This can be done using different icons for each one.
#### Attachments
Attachments must include the bucket in the path because the file could be retrieved from anywhere in the Ventura's cloud workspace.
```
["mybucket/pfs/103849.pdf", "mybucket/imgs/xmas_card_japan.jpg"]
```
</details>
### Flow

#### Interfaces
<details>
<summary>Typescript</summary>
```typescript
/* Request */
export interface SendEmailRequest {
templateId: string;
from: string;
to: string;
bcc?: string;
attachments?: string[];
variables?: Record<
string,
string | number | FormattedVar | NestedTemplateVar | NestedTemplateVar[]
>;
}
export type NestedTemplateVar = {
templateId: string;
variables?: Record<string, string | number>;
};
export type FormattedVar = {
format: "html" | "draft" | "text";
content: string;
};
/* Response */
export interface SendEmailResponse {
status: "success" | "error";
message: string;
data?: {
emailId: string;
templateId: string;
from: string;
to: string;
bcc: string;
attachments: string[];
body: string;
};
error?: string;
}
```
</details>
------------------------------------------------------------------------------------------