Added e-mail test button.

This commit is contained in:
2026-11-12 21:26:19 +01:00
parent a42a0fdf6d
commit fa19b9ad1e
3 changed files with 91 additions and 2 deletions
+58 -2
View File
@@ -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;
+31
View File
@@ -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;
}
+2
View File
@@ -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);