Repository: directus/directus
File: api/src/services/mail/index.ts (TypeScript)

Code before:

	async send<T>(options: EmailOptions): Promise<T | null> {
		const payload = await emitter.emitFilter(`email.send`, options, {});

		if (!payload) return null;

		const { template, ...emailOptions } = payload;

		let { html } = options;

		const defaultTemplateData = await this.getDefaultTemplateData();

		const from = (() => {
			if (typeof emailOptions.from === 'object' && emailOptions.from !== null) {
				if (!emailOptions.from.name || !emailOptions.from.address) {
					throw new InvalidPayloadError({ reason: 'Email from object must have both name and address properties' });
				}

				return emailOptions.from;
			}

			return {
				name: defaultTemplateData.projectName,
				address: emailOptions.from || (env['EMAIL_FROM'] as string),
			};
		})();

		if (template) {
			let templateData = template.data;

			templateData = {
				...defaultTemplateData,
				...templateData,
			};

			html = await this.renderTemplate(template.name, templateData);
		}

		if (typeof html === 'string') {
			// Some email clients start acting funky when line length exceeds 75 characters. See #6074
			html = html
				.split('\n')
				.map((line) => line.trim())
				.join('\n');
		}

		const info = await this.mailer.sendMail({ ...emailOptions, from, html });
		return info;
	}

	private async renderTemplate(template: string, variables: Record<string, any>) {

Review comment (refactor):
We can utilize the isObject here and simplify this a bit, something along the lines of

```suggestion
		if (isObject(emailOptions.from) && (!emailOptions.from.name || !emailOptions.from.address)) {
			throw new InvalidPayloadError({ reason: 'A name and address property are required in the "from" object' });
		}

		const from = isObject(emailOptions.from)
			? emailOptions.from
			: {
					name: defaultTemplateData.projectName,
					address: emailOptions.from || (env['EMAIL_FROM'] as string),
			  };
```

Code after:
	async send<T>(options: EmailOptions): Promise<T | null> {
		const payload = await emitter.emitFilter(`email.send`, options, {});

		if (!payload) return null;

		const { template, ...emailOptions } = payload;

		let { html } = options;

		const defaultTemplateData = await this.getDefaultTemplateData();

		if (isObject(emailOptions.from) && (!emailOptions.from.name || !emailOptions.from.address)) {
			throw new InvalidPayloadError({ reason: 'A name and address property are required in the "from" object' });
		}

		const from: SendMailOptions['from'] = isObject(emailOptions.from)
			? emailOptions.from
			: {
					name: defaultTemplateData.projectName,
					address: (emailOptions.from as string) || (env['EMAIL_FROM'] as string),
			  };

		if (template) {
			let templateData = template.data;

			templateData = {
				...defaultTemplateData,
				...templateData,
			};

			html = await this.renderTemplate(template.name, templateData);
		}

		if (typeof html === 'string') {
			// Some email clients start acting funky when line length exceeds 75 characters. See #6074
			html = html
				.split('\n')
				.map((line) => line.trim())
				.join('\n');
		}

		const info = await this.mailer.sendMail({ ...emailOptions, from, html });
		return info;
	}

	private async renderTemplate(template: string, variables: Record<string, any>) {
		const customTemplatePath = path.resolve(env['EMAIL_TEMPLATES_PATH'] as string, template + '.liquid');
		const systemTemplatePath = path.join(__dirname, 'templates', template + '.liquid');

		const templatePath = (await fse.pathExists(customTemplatePath)) ? customTemplatePath : systemTemplatePath;