Added e-mail test button.
This commit is contained in:
@@ -1,9 +1,10 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { inject } from 'vue';
|
import { inject, ref } from 'vue';
|
||||||
|
|
||||||
import Admin from '#/components/admin/admin.vue';
|
import Admin from '#/components/admin/admin.vue';
|
||||||
|
import { post } from '#/src/api.js';
|
||||||
|
|
||||||
const { pageProps } = inject('pageContext');
|
const { pageProps, user } = inject('pageContext');
|
||||||
|
|
||||||
const {
|
const {
|
||||||
template,
|
template,
|
||||||
@@ -11,12 +12,55 @@ const {
|
|||||||
text,
|
text,
|
||||||
error,
|
error,
|
||||||
} = pageProps;
|
} = pageProps;
|
||||||
|
|
||||||
|
const testAddress = ref(user?.email || '');
|
||||||
|
const sending = ref(false);
|
||||||
|
|
||||||
|
async function sendTest() {
|
||||||
|
const to = testAddress.value.trim();
|
||||||
|
|
||||||
|
sending.value = true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await post(`/admin/emails/${encodeURIComponent(template)}/test`, { to }, {
|
||||||
|
successFeedback: `Sent test e-mail to ${to}`,
|
||||||
|
errorFeedback: 'Failed to send test e-mail',
|
||||||
|
appendErrorMessage: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
catch (_error) {
|
||||||
|
// feedback is shown by the api module
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
sending.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<Admin class="page">
|
<Admin class="page">
|
||||||
<div class="header">
|
<div class="header">
|
||||||
<h2 class="heading">{{ template }}</h2>
|
<h2 class="heading">{{ template }}</h2>
|
||||||
|
|
||||||
|
<form
|
||||||
|
v-if="!error"
|
||||||
|
class="test"
|
||||||
|
@submit.prevent="sendTest"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
v-model="testAddress"
|
||||||
|
type="email"
|
||||||
|
class="input"
|
||||||
|
placeholder="E-mail address"
|
||||||
|
required
|
||||||
|
>
|
||||||
|
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
class="button button-primary"
|
||||||
|
:disabled="sending"
|
||||||
|
>{{ sending ? 'Sending...' : 'Send test' }}</button>
|
||||||
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p v-if="error" class="error">Failed to render template: {{ error }}</p>
|
<p v-if="error" class="error">Failed to render template: {{ error }}</p>
|
||||||
@@ -39,11 +83,23 @@ const {
|
|||||||
<style scoped>
|
<style scoped>
|
||||||
.header {
|
.header {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
align-items: baseline;
|
align-items: baseline;
|
||||||
gap: 1rem;
|
gap: 1rem;
|
||||||
margin-bottom: 1rem;
|
margin-bottom: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.test {
|
||||||
|
display: flex;
|
||||||
|
align-items: stretch;
|
||||||
|
gap: .5rem;
|
||||||
|
margin-left: auto;
|
||||||
|
|
||||||
|
.input {
|
||||||
|
width: 18rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.error {
|
.error {
|
||||||
color: var(--warn);
|
color: var(--warn);
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import config from 'config';
|
|||||||
import { Resend } from 'resend';
|
import { Resend } from 'resend';
|
||||||
|
|
||||||
import { emailTemplates } from './email-templates.js';
|
import { emailTemplates } from './email-templates.js';
|
||||||
|
import { HttpError } from './errors.js';
|
||||||
import initLogger from './logger.js';
|
import initLogger from './logger.js';
|
||||||
|
|
||||||
const logger = initLogger();
|
const logger = initLogger();
|
||||||
@@ -82,3 +83,33 @@ export async function sendEmails(items) {
|
|||||||
|
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// sends a template the way the admin preview displays it, rendered with the template's own mock props
|
||||||
|
export async function sendTestEmail(template, to) {
|
||||||
|
if (!config.email.enabled) {
|
||||||
|
throw new HttpError('E-mails are disabled', 503);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!resend) {
|
||||||
|
throw new HttpError('E-mails are enabled, but Resend is not initialized', 503);
|
||||||
|
}
|
||||||
|
|
||||||
|
const { html, text } = await emailTemplates.render(`${template}.vue`);
|
||||||
|
|
||||||
|
const { data, error } = await resend.emails.send({
|
||||||
|
from: config.email.from,
|
||||||
|
to,
|
||||||
|
subject: `Test: ${template}`,
|
||||||
|
html,
|
||||||
|
text,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
logger.error(`Failed to send '${template}' test e-mail to '${to}' (${error.statusCode} ${error.name}): ${error.message}`);
|
||||||
|
throw new HttpError(`Failed to send test e-mail: ${error.message}`, 502);
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info(`Sent '${template}' test e-mail to '${to}'`);
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import { authRouter, setUserApi } from './auth.js';
|
|||||||
|
|
||||||
import consentHandler from './consent.js';
|
import consentHandler from './consent.js';
|
||||||
|
|
||||||
|
import { sendTestEmailApi } from './emails.js';
|
||||||
import { fetchEntitiesApi } from './entities.js';
|
import { fetchEntitiesApi } from './entities.js';
|
||||||
import errorHandler from './error.js';
|
import errorHandler from './error.js';
|
||||||
import { graphqlApi } from './graphql.js';
|
import { graphqlApi } from './graphql.js';
|
||||||
@@ -143,6 +144,7 @@ export default async function initServer() {
|
|||||||
router.get('/api/tags', fetchTagsApi);
|
router.get('/api/tags', fetchTagsApi);
|
||||||
|
|
||||||
router.post('/api/caches', initCachesApi);
|
router.post('/api/caches', initCachesApi);
|
||||||
|
router.post('/api/admin/emails/:template/test', sendTestEmailApi);
|
||||||
|
|
||||||
if (config.apiAccess.graphqlEnabled) {
|
if (config.apiAccess.graphqlEnabled) {
|
||||||
router.post('/graphql', graphqlApi);
|
router.post('/graphql', graphqlApi);
|
||||||
|
|||||||
Reference in New Issue
Block a user