feat: Add invitation email and improve email templates
- Add invitation email service with Slovak diacritics - Move certificate email HTML template to separate file - Add invitation email HTML/TXT templates - Remove template caching for development flexibility - Add send invitation endpoint (POST /registracie/:id/send-invitation) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -306,3 +306,22 @@ export const sendCertificateEmail = async (req, res, next) => {
|
|||||||
next(error);
|
next(error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// ==================== INVITATION EMAIL ====================
|
||||||
|
|
||||||
|
export const sendInvitationEmail = async (req, res, next) => {
|
||||||
|
try {
|
||||||
|
const { registraciaId } = req.params;
|
||||||
|
const userId = req.user.id;
|
||||||
|
const { sendInvitationEmail: sendInvitation } = await import('../services/ai-kurzy/invitation-email.service.js');
|
||||||
|
|
||||||
|
const result = await sendInvitation(parseInt(registraciaId), userId);
|
||||||
|
|
||||||
|
res.json({
|
||||||
|
data: result,
|
||||||
|
message: result.message,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|||||||
@@ -180,4 +180,12 @@ router.post(
|
|||||||
aiKurzyController.sendCertificateEmail
|
aiKurzyController.sendCertificateEmail
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// ==================== INVITATION EMAIL ====================
|
||||||
|
|
||||||
|
router.post(
|
||||||
|
'/registracie/:registraciaId/send-invitation',
|
||||||
|
validateParams(registraciaIdSchema),
|
||||||
|
aiKurzyController.sendInvitationEmail
|
||||||
|
);
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import fs from 'fs/promises';
|
import fs from 'fs/promises';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
|
import { fileURLToPath } from 'url';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import { db } from '../../config/database.js';
|
import { db } from '../../config/database.js';
|
||||||
import { prilohy, registracie, ucastnici, kurzy, emailAccounts, userEmailAccounts } from '../../db/schema.js';
|
import { prilohy, registracie, ucastnici, kurzy, emailAccounts, userEmailAccounts } from '../../db/schema.js';
|
||||||
@@ -9,6 +10,14 @@ import { logger } from '../../utils/logger.js';
|
|||||||
import { jmapRequest, getMailboxes, getIdentities } from '../jmap/client.js';
|
import { jmapRequest, getMailboxes, getIdentities } from '../jmap/client.js';
|
||||||
import { getJmapConfig } from '../jmap/config.js';
|
import { getJmapConfig } from '../jmap/config.js';
|
||||||
|
|
||||||
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
|
const __dirname = path.dirname(__filename);
|
||||||
|
|
||||||
|
// Template paths
|
||||||
|
const TEMPLATES_DIR = path.join(__dirname, '../../templates/emails');
|
||||||
|
const CERTIFICATE_EMAIL_HTML_TEMPLATE = path.join(TEMPLATES_DIR, 'certificate-email.html');
|
||||||
|
const CERTIFICATE_EMAIL_TEXT_TEMPLATE = path.join(TEMPLATES_DIR, 'certificate-email.txt');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Format date to Slovak locale
|
* Format date to Slovak locale
|
||||||
*/
|
*/
|
||||||
@@ -21,136 +30,40 @@ const formatDate = (date) => {
|
|||||||
}).format(new Date(date));
|
}).format(new Date(date));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load email templates (fresh load each time for development flexibility)
|
||||||
|
*/
|
||||||
|
const loadTemplates = async () => {
|
||||||
|
const htmlTemplate = await fs.readFile(CERTIFICATE_EMAIL_HTML_TEMPLATE, 'utf-8');
|
||||||
|
const textTemplate = await fs.readFile(CERTIFICATE_EMAIL_TEXT_TEMPLATE, 'utf-8');
|
||||||
|
return { htmlTemplate, textTemplate };
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Replace template placeholders with actual values
|
||||||
|
*/
|
||||||
|
const replaceTemplatePlaceholders = (template, data) => {
|
||||||
|
return template
|
||||||
|
.replace(/\{\{participantName\}\}/g, data.participantName || '')
|
||||||
|
.replace(/\{\{courseName\}\}/g, data.courseName || '')
|
||||||
|
.replace(/\{\{courseDate\}\}/g, data.courseDate || '')
|
||||||
|
.replace(/\{\{courseType\}\}/g, data.courseType || 'Kurz');
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate HTML email template for certificate notification
|
* Generate HTML email template for certificate notification
|
||||||
*/
|
*/
|
||||||
export const generateCertificateEmailHtml = ({ participantName, courseName, courseDate, courseType }) => {
|
export const generateCertificateEmailHtml = async ({ participantName, courseName, courseDate, courseType }) => {
|
||||||
return `
|
const { htmlTemplate } = await loadTemplates();
|
||||||
<!DOCTYPE html>
|
return replaceTemplatePlaceholders(htmlTemplate, { participantName, courseName, courseDate, courseType });
|
||||||
<html lang="sk">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>Certifikát o absolvovaní kurzu</title>
|
|
||||||
</head>
|
|
||||||
<body style="margin: 0; padding: 0; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; background-color: #f8fafc;">
|
|
||||||
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%" style="background-color: #f8fafc;">
|
|
||||||
<tr>
|
|
||||||
<td style="padding: 40px 20px;">
|
|
||||||
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%" style="max-width: 600px; margin: 0 auto; background-color: #ffffff; border-radius: 16px; box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);">
|
|
||||||
|
|
||||||
<!-- Header with gradient -->
|
|
||||||
<tr>
|
|
||||||
<td style="padding: 40px 40px 30px 40px; text-align: center; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); border-radius: 16px 16px 0 0;">
|
|
||||||
<div style="margin-bottom: 16px;">
|
|
||||||
<span style="font-size: 48px;">🎓</span>
|
|
||||||
</div>
|
|
||||||
<h1 style="margin: 0; font-size: 26px; font-weight: 700; color: #ffffff; letter-spacing: -0.5px;">
|
|
||||||
Gratulujeme k úspešnému absolvovaniu!
|
|
||||||
</h1>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
<!-- Content -->
|
|
||||||
<tr>
|
|
||||||
<td style="padding: 40px;">
|
|
||||||
<p style="margin: 0 0 24px 0; font-size: 17px; color: #334155; line-height: 1.7;">
|
|
||||||
Vážený/á <strong style="color: #1e293b;">${participantName}</strong>,
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<p style="margin: 0 0 28px 0; font-size: 16px; color: #475569; line-height: 1.7;">
|
|
||||||
Srdečne Vám ďakujeme za účasť na našom kurze. Úspešne ste ho absolvovali a s potešením Vám zasielame Váš certifikát.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<!-- Course Details Card -->
|
|
||||||
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%" style="background: linear-gradient(135deg, #f0f9ff 0%, #e0f2fe 100%); border-radius: 12px; border: 1px solid #bae6fd; margin-bottom: 28px;">
|
|
||||||
<tr>
|
|
||||||
<td style="padding: 24px;">
|
|
||||||
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%">
|
|
||||||
<tr>
|
|
||||||
<td style="padding-bottom: 16px;">
|
|
||||||
<span style="display: inline-block; padding: 6px 14px; background-color: #0ea5e9; color: #ffffff; font-size: 11px; font-weight: 600; border-radius: 20px; text-transform: uppercase; letter-spacing: 0.5px;">
|
|
||||||
${courseType || 'Kurz'}
|
|
||||||
</span>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<h2 style="margin: 0 0 12px 0; font-size: 20px; font-weight: 700; color: #0c4a6e;">
|
|
||||||
${courseName}
|
|
||||||
</h2>
|
|
||||||
<p style="margin: 0; font-size: 14px; color: #0369a1;">
|
|
||||||
<span style="display: inline-block; margin-right: 8px;">📅</span>
|
|
||||||
${courseDate}
|
|
||||||
</p>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
|
|
||||||
<!-- Certificate note -->
|
|
||||||
<div style="background-color: #fefce8; border-radius: 10px; padding: 16px 20px; margin-bottom: 28px; border-left: 4px solid #eab308;">
|
|
||||||
<p style="margin: 0; font-size: 14px; color: #713f12; line-height: 1.6;">
|
|
||||||
<strong>📎 Príloha:</strong> Váš certifikát nájdete v prílohe tohto emailu vo formáte PDF.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<p style="margin: 0 0 8px 0; font-size: 16px; color: #475569; line-height: 1.7;">
|
|
||||||
Prajeme Vám veľa úspechov pri uplatňovaní nových vedomostí v praxi!
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<p style="margin: 24px 0 0 0; font-size: 16px; color: #334155;">
|
|
||||||
S pozdravom,<br>
|
|
||||||
<strong style="color: #1e293b;">Tím AI Kurzov</strong>
|
|
||||||
</p>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
<!-- Footer -->
|
|
||||||
<tr>
|
|
||||||
<td style="padding: 24px 40px; background-color: #f1f5f9; border-top: 1px solid #e2e8f0; border-radius: 0 0 16px 16px;">
|
|
||||||
<p style="margin: 0; font-size: 12px; color: #64748b; text-align: center; line-height: 1.6;">
|
|
||||||
Táto správa bola automaticky vygenerovaná.<br>
|
|
||||||
V prípade otázok nás neváhajte kontaktovať.
|
|
||||||
</p>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
</table>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
`.trim();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate plain text version of the certificate email
|
* Generate plain text version of the certificate email
|
||||||
*/
|
*/
|
||||||
export const generateCertificateEmailText = ({ participantName, courseName, courseDate, courseType }) => {
|
export const generateCertificateEmailText = async ({ participantName, courseName, courseDate, courseType }) => {
|
||||||
return `
|
const { textTemplate } = await loadTemplates();
|
||||||
Vážený/á ${participantName},
|
return replaceTemplatePlaceholders(textTemplate, { participantName, courseName, courseDate, courseType });
|
||||||
|
|
||||||
Srdečne Vám ďakujeme za účasť na našom kurze. Úspešne ste ho absolvovali a s potešením Vám zasielame Váš certifikát.
|
|
||||||
|
|
||||||
KURZ: ${courseName}
|
|
||||||
TYP: ${courseType || 'Kurz'}
|
|
||||||
DÁTUM: ${courseDate}
|
|
||||||
|
|
||||||
Váš certifikát nájdete v prílohe tohto emailu vo formáte PDF.
|
|
||||||
|
|
||||||
Prajeme Vám veľa úspechov pri uplatňovaní nových vedomostí v praxi!
|
|
||||||
|
|
||||||
S pozdravom,
|
|
||||||
Tím AI Kurzov
|
|
||||||
|
|
||||||
---
|
|
||||||
Táto správa bola automaticky vygenerovaná.
|
|
||||||
V prípade otázok nás neváhajte kontaktovať.
|
|
||||||
`.trim();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -410,8 +323,8 @@ export const sendCertificateEmail = async (prilohaId, userId) => {
|
|||||||
courseType: registration.kurzTyp,
|
courseType: registration.kurzTyp,
|
||||||
};
|
};
|
||||||
|
|
||||||
const htmlBody = generateCertificateEmailHtml(emailData);
|
const htmlBody = await generateCertificateEmailHtml(emailData);
|
||||||
const textBody = generateCertificateEmailText(emailData);
|
const textBody = await generateCertificateEmailText(emailData);
|
||||||
const subject = `Certifikát o absolvovaní kurzu: ${registration.kurzNazov}`;
|
const subject = `Certifikát o absolvovaní kurzu: ${registration.kurzNazov}`;
|
||||||
|
|
||||||
// Get JMAP config
|
// Get JMAP config
|
||||||
|
|||||||
253
src/services/ai-kurzy/invitation-email.service.js
Normal file
253
src/services/ai-kurzy/invitation-email.service.js
Normal file
@@ -0,0 +1,253 @@
|
|||||||
|
import fs from 'fs/promises';
|
||||||
|
import path from 'path';
|
||||||
|
import { fileURLToPath } from 'url';
|
||||||
|
import axios from 'axios';
|
||||||
|
import { db } from '../../config/database.js';
|
||||||
|
import { registracie, ucastnici, kurzy, emailAccounts, userEmailAccounts } from '../../db/schema.js';
|
||||||
|
import { eq, and } from 'drizzle-orm';
|
||||||
|
import { NotFoundError, BadRequestError } from '../../utils/errors.js';
|
||||||
|
import { logger } from '../../utils/logger.js';
|
||||||
|
import { jmapRequest, getMailboxes, getIdentities } from '../jmap/client.js';
|
||||||
|
import { getJmapConfig } from '../jmap/config.js';
|
||||||
|
|
||||||
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
|
const __dirname = path.dirname(__filename);
|
||||||
|
|
||||||
|
// Template paths
|
||||||
|
const TEMPLATES_DIR = path.join(__dirname, '../../templates/emails');
|
||||||
|
const INVITATION_EMAIL_HTML_TEMPLATE = path.join(TEMPLATES_DIR, 'invitation-email.html');
|
||||||
|
const INVITATION_EMAIL_TEXT_TEMPLATE = path.join(TEMPLATES_DIR, 'invitation-email.txt');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format date to Slovak locale
|
||||||
|
*/
|
||||||
|
const formatDate = (date) => {
|
||||||
|
if (!date) return '';
|
||||||
|
return new Intl.DateTimeFormat('sk-SK', {
|
||||||
|
day: 'numeric',
|
||||||
|
month: 'long',
|
||||||
|
year: 'numeric',
|
||||||
|
}).format(new Date(date));
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load email templates (fresh load each time for development flexibility)
|
||||||
|
*/
|
||||||
|
const loadTemplates = async () => {
|
||||||
|
const htmlTemplate = await fs.readFile(INVITATION_EMAIL_HTML_TEMPLATE, 'utf-8');
|
||||||
|
const textTemplate = await fs.readFile(INVITATION_EMAIL_TEXT_TEMPLATE, 'utf-8');
|
||||||
|
return { htmlTemplate, textTemplate };
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Replace template placeholders with actual values
|
||||||
|
*/
|
||||||
|
const replaceTemplatePlaceholders = (template, data) => {
|
||||||
|
return template
|
||||||
|
.replace(/\{\{courseName\}\}/g, data.courseName || '')
|
||||||
|
.replace(/\{\{courseDate\}\}/g, data.courseDate || '');
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate HTML email template for invitation
|
||||||
|
*/
|
||||||
|
export const generateInvitationEmailHtml = async ({ courseName, courseDate }) => {
|
||||||
|
const { htmlTemplate } = await loadTemplates();
|
||||||
|
return replaceTemplatePlaceholders(htmlTemplate, { courseName, courseDate });
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate plain text version of the invitation email
|
||||||
|
*/
|
||||||
|
export const generateInvitationEmailText = async ({ courseName, courseDate }) => {
|
||||||
|
const { textTemplate } = await loadTemplates();
|
||||||
|
return replaceTemplatePlaceholders(textTemplate, { courseName, courseDate });
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send email via JMAP
|
||||||
|
*/
|
||||||
|
const sendEmailViaJmap = async (jmapConfig, to, subject, htmlBody, textBody) => {
|
||||||
|
logger.info(`Odosielam pozvánku emailom na: ${to}`);
|
||||||
|
|
||||||
|
// Get mailboxes
|
||||||
|
const mailboxes = await getMailboxes(jmapConfig);
|
||||||
|
const sentMailbox = mailboxes.find((m) => m.role === 'sent' || m.name === 'Sent');
|
||||||
|
|
||||||
|
if (!sentMailbox) {
|
||||||
|
throw new Error('Priečinok Odoslané nebol nájdený');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create email
|
||||||
|
const createResponse = await jmapRequest(jmapConfig, [
|
||||||
|
[
|
||||||
|
'Email/set',
|
||||||
|
{
|
||||||
|
accountId: jmapConfig.accountId,
|
||||||
|
create: {
|
||||||
|
draft: {
|
||||||
|
mailboxIds: {
|
||||||
|
[sentMailbox.id]: true,
|
||||||
|
},
|
||||||
|
from: [{ email: jmapConfig.username }],
|
||||||
|
to: [{ email: to }],
|
||||||
|
subject: subject,
|
||||||
|
bodyStructure: {
|
||||||
|
type: 'multipart/alternative',
|
||||||
|
subParts: [
|
||||||
|
{
|
||||||
|
type: 'text/plain',
|
||||||
|
partId: 'text',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'text/html',
|
||||||
|
partId: 'html',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
bodyValues: {
|
||||||
|
html: { value: htmlBody },
|
||||||
|
text: { value: textBody },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'set1',
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
|
||||||
|
const createResult = createResponse.methodResponses[0][1];
|
||||||
|
const createdEmailId = createResult.created?.draft?.id;
|
||||||
|
|
||||||
|
if (!createdEmailId) {
|
||||||
|
logger.error('Nepodarilo sa vytvoriť email', createResult.notCreated?.draft);
|
||||||
|
throw new Error('Nepodarilo sa vytvoriť email');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get user identity
|
||||||
|
const identities = await getIdentities(jmapConfig);
|
||||||
|
const identity = identities.find((i) => i.email === jmapConfig.username) || identities[0];
|
||||||
|
|
||||||
|
if (!identity) {
|
||||||
|
throw new Error('Nenašla sa identita pre odosielanie emailov');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Submit the email
|
||||||
|
const submitResponse = await jmapRequest(jmapConfig, [
|
||||||
|
[
|
||||||
|
'EmailSubmission/set',
|
||||||
|
{
|
||||||
|
accountId: jmapConfig.accountId,
|
||||||
|
create: {
|
||||||
|
submission: {
|
||||||
|
emailId: createdEmailId,
|
||||||
|
identityId: identity.id,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'submit1',
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
|
||||||
|
const submissionId = submitResponse.methodResponses[0][1].created?.submission?.id;
|
||||||
|
|
||||||
|
if (!submissionId) {
|
||||||
|
const error = submitResponse.methodResponses[0][1].notCreated?.submission;
|
||||||
|
logger.error('Nepodarilo sa odoslať email', error);
|
||||||
|
throw new Error('Nepodarilo sa odoslať email');
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.success(`Email s pozvánkou úspešne odoslaný`);
|
||||||
|
return { success: true, submissionId };
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send invitation email to participant
|
||||||
|
* @param {number} registraciaId - ID of the registration
|
||||||
|
* @param {number} userId - ID of the user sending the email (for getting email account)
|
||||||
|
*/
|
||||||
|
export const sendInvitationEmail = async (registraciaId, userId) => {
|
||||||
|
// Get registration with participant and course info
|
||||||
|
const [registration] = await db
|
||||||
|
.select({
|
||||||
|
id: registracie.id,
|
||||||
|
ucastnikMeno: ucastnici.meno,
|
||||||
|
ucastnikPriezvisko: ucastnici.priezvisko,
|
||||||
|
ucastnikTitul: ucastnici.titul,
|
||||||
|
ucastnikEmail: ucastnici.email,
|
||||||
|
kurzNazov: kurzy.nazov,
|
||||||
|
kurzTyp: kurzy.typKurzu,
|
||||||
|
kurzDatumOd: kurzy.datumOd,
|
||||||
|
kurzDatumDo: kurzy.datumDo,
|
||||||
|
})
|
||||||
|
.from(registracie)
|
||||||
|
.leftJoin(ucastnici, eq(registracie.ucastnikId, ucastnici.id))
|
||||||
|
.leftJoin(kurzy, eq(registracie.kurzId, kurzy.id))
|
||||||
|
.where(eq(registracie.id, registraciaId))
|
||||||
|
.limit(1);
|
||||||
|
|
||||||
|
if (!registration) {
|
||||||
|
throw new NotFoundError('Registrácia nenájdená');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!registration.ucastnikEmail) {
|
||||||
|
throw new BadRequestError('Účastník nemá zadaný email');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get email account for sending (through userEmailAccounts join table)
|
||||||
|
const [emailAccountResult] = await db
|
||||||
|
.select({
|
||||||
|
id: emailAccounts.id,
|
||||||
|
email: emailAccounts.email,
|
||||||
|
emailPassword: emailAccounts.emailPassword,
|
||||||
|
jmapAccountId: emailAccounts.jmapAccountId,
|
||||||
|
isActive: emailAccounts.isActive,
|
||||||
|
})
|
||||||
|
.from(userEmailAccounts)
|
||||||
|
.innerJoin(emailAccounts, eq(userEmailAccounts.emailAccountId, emailAccounts.id))
|
||||||
|
.where(and(eq(userEmailAccounts.userId, userId), eq(emailAccounts.isActive, true)))
|
||||||
|
.limit(1);
|
||||||
|
|
||||||
|
if (!emailAccountResult) {
|
||||||
|
throw new BadRequestError('Nemáte nastavený aktívny emailový účet pre odosielanie');
|
||||||
|
}
|
||||||
|
|
||||||
|
const emailAccount = emailAccountResult;
|
||||||
|
|
||||||
|
// Build course date string
|
||||||
|
const courseDate = registration.kurzDatumOd
|
||||||
|
? registration.kurzDatumDo && registration.kurzDatumDo !== registration.kurzDatumOd
|
||||||
|
? `${formatDate(registration.kurzDatumOd)} - ${formatDate(registration.kurzDatumDo)}`
|
||||||
|
: formatDate(registration.kurzDatumOd)
|
||||||
|
: '';
|
||||||
|
|
||||||
|
// Generate email content
|
||||||
|
const emailData = {
|
||||||
|
courseName: registration.kurzNazov || 'Kurz',
|
||||||
|
courseDate,
|
||||||
|
};
|
||||||
|
|
||||||
|
const htmlBody = await generateInvitationEmailHtml(emailData);
|
||||||
|
const textBody = await generateInvitationEmailText(emailData);
|
||||||
|
const subject = `Pozvánka na školenie: ${registration.kurzNazov}`;
|
||||||
|
|
||||||
|
// Get JMAP config
|
||||||
|
const jmapConfig = getJmapConfig(emailAccount, true);
|
||||||
|
|
||||||
|
// Send email
|
||||||
|
await sendEmailViaJmap(
|
||||||
|
jmapConfig,
|
||||||
|
registration.ucastnikEmail,
|
||||||
|
subject,
|
||||||
|
htmlBody,
|
||||||
|
textBody
|
||||||
|
);
|
||||||
|
|
||||||
|
logger.success(`Pozvánka odoslaná na ${registration.ucastnikEmail}`);
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
message: `Pozvánka bola odoslaná na ${registration.ucastnikEmail}`,
|
||||||
|
recipientEmail: registration.ucastnikEmail,
|
||||||
|
};
|
||||||
|
};
|
||||||
129
src/templates/emails/certificate-email.html
Normal file
129
src/templates/emails/certificate-email.html
Normal file
@@ -0,0 +1,129 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="sk">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Certifikát o absolvovaní kurzu</title>
|
||||||
|
</head>
|
||||||
|
<body style="margin: 0; padding: 0; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; background-color: #f5f5f5;">
|
||||||
|
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%" style="background-color: #f5f5f5;">
|
||||||
|
<tr>
|
||||||
|
<td style="padding: 32px 16px;">
|
||||||
|
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%" style="max-width: 600px; margin: 0 auto; background-color: #ffffff; border-radius: 16px; overflow: hidden; box-shadow: 0 4px 24px rgba(0, 0, 0, 0.12);">
|
||||||
|
|
||||||
|
<!-- Header with Logo -->
|
||||||
|
<tr>
|
||||||
|
<td style="background: linear-gradient(135deg, #0f172a 0%, #1e293b 100%); padding: 48px 32px; text-align: center;">
|
||||||
|
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%">
|
||||||
|
<tr>
|
||||||
|
<td style="text-align: center; padding-bottom: 28px;">
|
||||||
|
<img src="https://www.slovensko.ai/wp-content/uploads/2024/07/2LOGO_slovensko_AI_1_b-2048x434.png" alt="Slovensko.AI" style="height: 48px; max-width: 280px;" />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<div style="width: 60px; height: 60px; background: linear-gradient(135deg, #10b981 0%, #059669 100%); border-radius: 16px; margin: 0 auto 20px auto; text-align: center; line-height: 60px; font-size: 28px;">
|
||||||
|
🎓
|
||||||
|
</div>
|
||||||
|
<h1 style="margin: 0; font-size: 28px; font-weight: 700; color: #ffffff; letter-spacing: -0.5px;">
|
||||||
|
Gratulujeme k úspešnému absolvovaniu!
|
||||||
|
</h1>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<!-- Content -->
|
||||||
|
<tr>
|
||||||
|
<td style="padding: 40px 32px;">
|
||||||
|
<p style="margin: 0 0 24px 0; font-size: 17px; color: #334155; line-height: 1.7;">
|
||||||
|
Vážený/á <strong style="color: #0f172a;">{{participantName}}</strong>,
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p style="margin: 0 0 28px 0; font-size: 16px; color: #475569; line-height: 1.7;">
|
||||||
|
Srdečne Vám ďakujeme za účasť na našom kurze. Úspešne ste ho absolvovali a s potešením Vám zasielame Váš certifikát.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<!-- Course Details Card -->
|
||||||
|
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%" style="background: linear-gradient(135deg, #ecfdf5 0%, #d1fae5 100%); border-radius: 16px; border: 1px solid #a7f3d0; margin-bottom: 28px; overflow: hidden;">
|
||||||
|
<tr>
|
||||||
|
<td style="background: linear-gradient(135deg, #10b981 0%, #059669 100%); padding: 12px 24px;">
|
||||||
|
<span style="display: inline-block; font-size: 12px; font-weight: 700; color: rgba(255,255,255,0.9); text-transform: uppercase; letter-spacing: 1px;">
|
||||||
|
{{courseType}}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td style="padding: 24px;">
|
||||||
|
<h2 style="margin: 0 0 12px 0; font-size: 22px; font-weight: 700; color: #064e3b;">
|
||||||
|
{{courseName}}
|
||||||
|
</h2>
|
||||||
|
<p style="margin: 0; font-size: 15px; color: #065f46;">
|
||||||
|
<span style="display: inline-block; margin-right: 8px;">📅</span>
|
||||||
|
{{courseDate}}
|
||||||
|
</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<!-- Certificate note -->
|
||||||
|
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%" style="background: linear-gradient(135deg, #fef3c7 0%, #fde68a 100%); border-radius: 12px; margin-bottom: 28px;">
|
||||||
|
<tr>
|
||||||
|
<td style="padding: 20px 24px;">
|
||||||
|
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%">
|
||||||
|
<tr>
|
||||||
|
<td width="40" style="vertical-align: top;">
|
||||||
|
<div style="width: 36px; height: 36px; background-color: #f59e0b; border-radius: 10px; text-align: center; line-height: 36px; font-size: 18px;">
|
||||||
|
📎
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td style="vertical-align: middle; padding-left: 16px;">
|
||||||
|
<p style="margin: 0; font-size: 15px; color: #78350f; line-height: 1.5;">
|
||||||
|
<strong>Príloha:</strong> Váš certifikát nájdete v prílohe tohto e-mailu vo formáte PDF.
|
||||||
|
</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<p style="margin: 0 0 8px 0; font-size: 16px; color: #475569; line-height: 1.7;">
|
||||||
|
Prajeme Vám veľa úspechov pri uplatňovaní nových vedomostí v praxi!
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p style="margin: 28px 0 0 0; font-size: 16px; color: #334155;">
|
||||||
|
S pozdravom,<br>
|
||||||
|
<strong style="color: #0f172a;">Tím Slovensko.AI</strong>
|
||||||
|
</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<!-- Footer -->
|
||||||
|
<tr>
|
||||||
|
<td style="background: linear-gradient(135deg, #0f172a 0%, #1e293b 100%); padding: 28px 32px;">
|
||||||
|
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%">
|
||||||
|
<tr>
|
||||||
|
<td style="text-align: center;">
|
||||||
|
<p style="margin: 0 0 12px 0; font-size: 12px; color: #94a3b8;">
|
||||||
|
Táto správa bola automaticky vygenerovaná.<br>
|
||||||
|
V prípade otázok nás neváhajte kontaktovať.
|
||||||
|
</p>
|
||||||
|
<p style="margin: 0;">
|
||||||
|
<a href="mailto:info@slovensko.ai" style="color: #10b981; text-decoration: none; font-size: 13px; font-weight: 600;">info@slovensko.ai</a>
|
||||||
|
<span style="color: #475569; margin: 0 8px;">|</span>
|
||||||
|
<a href="https://www.slovensko.ai" style="color: #10b981; text-decoration: none; font-size: 13px; font-weight: 600;">www.slovensko.ai</a>
|
||||||
|
</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
20
src/templates/emails/certificate-email.txt
Normal file
20
src/templates/emails/certificate-email.txt
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
Vážený/á {{participantName}},
|
||||||
|
|
||||||
|
Srdečne Vám ďakujeme za účasť na našom kurze. Úspešne ste ho absolvovali a s potešením Vám zasielame Váš certifikát.
|
||||||
|
|
||||||
|
KURZ: {{courseName}}
|
||||||
|
TYP: {{courseType}}
|
||||||
|
DÁTUM: {{courseDate}}
|
||||||
|
|
||||||
|
Váš certifikát nájdete v prílohe tohto e-mailu vo formáte PDF.
|
||||||
|
|
||||||
|
Prajeme Vám veľa úspechov pri uplatňovaní nových vedomostí v praxi!
|
||||||
|
|
||||||
|
S pozdravom,
|
||||||
|
Tím Slovensko.AI
|
||||||
|
|
||||||
|
---
|
||||||
|
Táto správa bola automaticky vygenerovaná.
|
||||||
|
V prípade otázok nás neváhajte kontaktovať.
|
||||||
|
|
||||||
|
info@slovensko.ai | www.slovensko.ai
|
||||||
351
src/templates/emails/invitation-email.html
Normal file
351
src/templates/emails/invitation-email.html
Normal file
@@ -0,0 +1,351 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="sk">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Pozvánka na kurz</title>
|
||||||
|
</head>
|
||||||
|
<body style="margin: 0; padding: 0; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; background-color: #f5f5f5;">
|
||||||
|
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%" style="background-color: #f5f5f5;">
|
||||||
|
<tr>
|
||||||
|
<td style="padding: 32px 16px;">
|
||||||
|
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%" style="max-width: 600px; margin: 0 auto; background-color: #ffffff; border-radius: 16px; overflow: hidden; box-shadow: 0 4px 24px rgba(0, 0, 0, 0.12);">
|
||||||
|
|
||||||
|
<!-- Header with Logo -->
|
||||||
|
<tr>
|
||||||
|
<td style="background: linear-gradient(135deg, #0f172a 0%, #1e293b 100%); padding: 48px 32px; text-align: center;">
|
||||||
|
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%">
|
||||||
|
<tr>
|
||||||
|
<td style="text-align: center; padding-bottom: 28px;">
|
||||||
|
<img src="https://www.slovensko.ai/wp-content/uploads/2024/07/2LOGO_slovensko_AI_1_b-2048x434.png" alt="Slovensko.AI" style="height: 48px; max-width: 280px;" />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<div style="width: 60px; height: 4px; background: linear-gradient(90deg, #10b981 0%, #34d399 100%); margin: 0 auto 24px auto; border-radius: 2px;"></div>
|
||||||
|
<h1 style="margin: 0; font-size: 32px; font-weight: 700; color: #ffffff; letter-spacing: -0.5px;">
|
||||||
|
Pozvánka na školenie
|
||||||
|
</h1>
|
||||||
|
<p style="margin: 12px 0 0 0; font-size: 15px; color: #94a3b8;">
|
||||||
|
Všetky organizačné informácie
|
||||||
|
</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<!-- Greeting -->
|
||||||
|
<tr>
|
||||||
|
<td style="padding: 36px 32px 24px 32px;">
|
||||||
|
<p style="margin: 0 0 16px 0; font-size: 17px; color: #334155; line-height: 1.6;">
|
||||||
|
Dobrý deň,
|
||||||
|
</p>
|
||||||
|
<p style="margin: 0; font-size: 16px; color: #475569; line-height: 1.7;">
|
||||||
|
pripravili sme pre Vás súhrn všetkých potrebných organizačných informácií k Vášmu kurzu. <strong style="color: #334155;">Potvrďte nám prosím prečítanie tohto e-mailu.</strong>
|
||||||
|
</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<!-- Course Info Card -->
|
||||||
|
<tr>
|
||||||
|
<td style="padding: 0 32px;">
|
||||||
|
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%" style="background: linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%); border-radius: 16px; border: 1px solid #e2e8f0; overflow: hidden;">
|
||||||
|
<!-- Course Header -->
|
||||||
|
<tr>
|
||||||
|
<td style="background: linear-gradient(135deg, #10b981 0%, #059669 100%); padding: 16px 24px;">
|
||||||
|
<p style="margin: 0; font-size: 11px; font-weight: 700; color: rgba(255,255,255,0.8); text-transform: uppercase; letter-spacing: 1px;">
|
||||||
|
Kurz
|
||||||
|
</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td style="padding: 24px;">
|
||||||
|
<!-- Course Name -->
|
||||||
|
<h2 style="margin: 0 0 20px 0; font-size: 24px; font-weight: 700; color: #0f172a; line-height: 1.3;">
|
||||||
|
{{courseName}}
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<!-- Date & Time Row -->
|
||||||
|
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%">
|
||||||
|
<tr>
|
||||||
|
<td width="50%" style="vertical-align: top; padding-right: 12px;">
|
||||||
|
<div style="background-color: #ffffff; border-radius: 12px; padding: 16px; border: 1px solid #e2e8f0;">
|
||||||
|
<p style="margin: 0 0 4px 0; font-size: 11px; font-weight: 600; color: #64748b; text-transform: uppercase; letter-spacing: 0.5px;">
|
||||||
|
📅 Termín
|
||||||
|
</p>
|
||||||
|
<p style="margin: 0; font-size: 17px; font-weight: 700; color: #0f172a;">
|
||||||
|
{{courseDate}}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td width="50%" style="vertical-align: top; padding-left: 12px;">
|
||||||
|
<div style="background-color: #ffffff; border-radius: 12px; padding: 16px; border: 1px solid #e2e8f0;">
|
||||||
|
<p style="margin: 0 0 4px 0; font-size: 11px; font-weight: 600; color: #64748b; text-transform: uppercase; letter-spacing: 0.5px;">
|
||||||
|
🕑 Čas
|
||||||
|
</p>
|
||||||
|
<p style="margin: 0; font-size: 17px; font-weight: 700; color: #0f172a;">
|
||||||
|
09:00 - 15:00
|
||||||
|
</p>
|
||||||
|
<p style="margin: 4px 0 0 0; font-size: 13px; color: #64748b;">
|
||||||
|
+ Diskusia
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<!-- Format Info -->
|
||||||
|
<div style="margin-top: 20px; padding: 16px; background-color: #ffffff; border-radius: 12px; border: 1px solid #e2e8f0;">
|
||||||
|
<p style="margin: 0 0 4px 0; font-size: 11px; font-weight: 600; color: #64748b; text-transform: uppercase; letter-spacing: 0.5px;">
|
||||||
|
🎓 Forma školenia
|
||||||
|
</p>
|
||||||
|
<p style="margin: 0; font-size: 15px; color: #334155; line-height: 1.6;">
|
||||||
|
Školenie sa bude konať <strong>prezenčne</strong>. Program bude pozostávať z výukových blokov, s prestávkami 10-15 minút a jednou 45-60 minútovou obedovou prestávkou.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<!-- Tip Box -->
|
||||||
|
<tr>
|
||||||
|
<td style="padding: 24px 32px;">
|
||||||
|
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%" style="background: linear-gradient(135deg, #fef3c7 0%, #fde68a 100%); border-radius: 12px; overflow: hidden;">
|
||||||
|
<tr>
|
||||||
|
<td style="padding: 20px 24px;">
|
||||||
|
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%">
|
||||||
|
<tr>
|
||||||
|
<td width="40" style="vertical-align: top;">
|
||||||
|
<div style="width: 36px; height: 36px; background-color: #f59e0b; border-radius: 10px; text-align: center; line-height: 36px; font-size: 18px;">
|
||||||
|
💡
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td style="vertical-align: top; padding-left: 16px;">
|
||||||
|
<p style="margin: 0 0 4px 0; font-size: 14px; font-weight: 700; color: #92400e;">
|
||||||
|
Tip pre lepší zážitok
|
||||||
|
</p>
|
||||||
|
<p style="margin: 0; font-size: 14px; color: #78350f; line-height: 1.5;">
|
||||||
|
Odporúčame priniesť si vlastný notebook. Budete si môcť vyskúšať AI priamo v akcii počas praktických úloh. V cene školenia je občerstvenie (káva, čaj).
|
||||||
|
</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<!-- Location Section -->
|
||||||
|
<tr>
|
||||||
|
<td style="padding: 0 32px;">
|
||||||
|
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%" style="background: linear-gradient(135deg, #ecfdf5 0%, #d1fae5 100%); border-radius: 16px; border: 1px solid #a7f3d0; overflow: hidden;">
|
||||||
|
<tr>
|
||||||
|
<td style="padding: 24px;">
|
||||||
|
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%">
|
||||||
|
<tr>
|
||||||
|
<td width="48" style="vertical-align: top;">
|
||||||
|
<div style="width: 44px; height: 44px; background: linear-gradient(135deg, #10b981 0%, #059669 100%); border-radius: 12px; text-align: center; line-height: 44px; font-size: 20px;">
|
||||||
|
📍
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td style="vertical-align: top; padding-left: 16px;">
|
||||||
|
<p style="margin: 0 0 4px 0; font-size: 11px; font-weight: 700; color: #059669; text-transform: uppercase; letter-spacing: 1px;">
|
||||||
|
Miesto konania
|
||||||
|
</p>
|
||||||
|
<p style="margin: 0 0 4px 0; font-size: 20px; font-weight: 700; color: #064e3b;">
|
||||||
|
INBOX SK s.r.o.
|
||||||
|
</p>
|
||||||
|
<p style="margin: 0; font-size: 15px; color: #065f46; line-height: 1.5;">
|
||||||
|
Seberíniho 1, Bratislava<br>
|
||||||
|
<strong>7. poschodie vpravo</strong>
|
||||||
|
</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<!-- Transport Section -->
|
||||||
|
<tr>
|
||||||
|
<td style="padding: 24px 32px;">
|
||||||
|
<h3 style="margin: 0 0 20px 0; font-size: 18px; font-weight: 700; color: #0f172a;">
|
||||||
|
🚌 Doprava a parkovanie
|
||||||
|
</h3>
|
||||||
|
|
||||||
|
<!-- Transport Options -->
|
||||||
|
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%" style="background-color: #f8fafc; border-radius: 12px; border: 1px solid #e2e8f0;">
|
||||||
|
<tr>
|
||||||
|
<td style="padding: 20px;">
|
||||||
|
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%">
|
||||||
|
<tr>
|
||||||
|
<td style="padding-bottom: 16px; border-bottom: 1px solid #e2e8f0;">
|
||||||
|
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%">
|
||||||
|
<tr>
|
||||||
|
<td width="36" style="vertical-align: top;">
|
||||||
|
<div style="width: 28px; height: 28px; background: linear-gradient(135deg, #3b82f6 0%, #2563eb 100%); border-radius: 8px; text-align: center; line-height: 28px; font-size: 14px; color: #ffffff; font-weight: 700;">
|
||||||
|
B
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td style="vertical-align: top; padding-left: 12px;">
|
||||||
|
<p style="margin: 0; font-size: 14px; color: #334155;">
|
||||||
|
<strong>Autobusom:</strong> Nevädzová (66, 75, 96), Tomášikova (50)
|
||||||
|
</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td style="padding: 16px 0; border-bottom: 1px solid #e2e8f0;">
|
||||||
|
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%">
|
||||||
|
<tr>
|
||||||
|
<td width="36" style="vertical-align: top;">
|
||||||
|
<div style="width: 28px; height: 28px; background: linear-gradient(135deg, #ec4899 0%, #db2777 100%); border-radius: 8px; text-align: center; line-height: 28px; font-size: 14px; color: #ffffff; font-weight: 700;">
|
||||||
|
T
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td style="vertical-align: top; padding-left: 12px;">
|
||||||
|
<p style="margin: 0; font-size: 14px; color: #334155;">
|
||||||
|
<strong>Trolejbusom:</strong> Gagarinova (71, 72)
|
||||||
|
</p>
|
||||||
|
<p style="margin: 4px 0 0 0; font-size: 13px; color: #64748b;">
|
||||||
|
Z Hlavnej stanice trolejbus 71, z Autobusovej stanice trolejbus 72
|
||||||
|
</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td style="padding: 16px 0; border-bottom: 1px solid #e2e8f0;">
|
||||||
|
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%">
|
||||||
|
<tr>
|
||||||
|
<td width="36" style="vertical-align: top;">
|
||||||
|
<div style="width: 28px; height: 28px; background: linear-gradient(135deg, #eab308 0%, #ca8a04 100%); border-radius: 8px; text-align: center; line-height: 28px; font-size: 14px; color: #ffffff; font-weight: 700;">
|
||||||
|
E
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td style="vertical-align: top; padding-left: 12px;">
|
||||||
|
<p style="margin: 0; font-size: 14px; color: #334155;">
|
||||||
|
<strong>Električkou:</strong> Tomášikova (9)
|
||||||
|
</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td style="padding-top: 16px;">
|
||||||
|
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%">
|
||||||
|
<tr>
|
||||||
|
<td width="36" style="vertical-align: top;">
|
||||||
|
<div style="width: 28px; height: 28px; background: linear-gradient(135deg, #64748b 0%, #475569 100%); border-radius: 8px; text-align: center; line-height: 28px; font-size: 14px; color: #ffffff; font-weight: 700;">
|
||||||
|
A
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td style="vertical-align: top; padding-left: 12px;">
|
||||||
|
<p style="margin: 0; font-size: 14px; color: #334155;">
|
||||||
|
<strong>Autom:</strong> GPS N 48° 09" 13.295', E 17° 09" 53.773'
|
||||||
|
</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<!-- Parking Info -->
|
||||||
|
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%" style="margin-top: 16px; background: linear-gradient(135deg, #fef2f2 0%, #fee2e2 100%); border-radius: 12px; border: 1px solid #fecaca;">
|
||||||
|
<tr>
|
||||||
|
<td style="padding: 20px;">
|
||||||
|
<p style="margin: 0 0 12px 0; font-size: 14px; font-weight: 700; color: #991b1b;">
|
||||||
|
🚗 Parkovanie
|
||||||
|
</p>
|
||||||
|
<ul style="margin: 0; padding: 0 0 0 20px; font-size: 14px; color: #7f1d1d; line-height: 1.7;">
|
||||||
|
<li style="margin-bottom: 6px;">Parkovisko pri budove: <strong>3,70 EUR/hod</strong> (15 min zadarmo)</li>
|
||||||
|
<li style="margin-bottom: 6px;">Hotel Bratislava: <strong>10 EUR/celý deň</strong></li>
|
||||||
|
<li style="margin-bottom: 6px;"><strong>PAAS zóna Ružinov-Pošeň:</strong> 1,50 EUR/hod (od 12:00) - zaparkujete na uliciach Obilná, Jašíkova, Andreja Mráza, Seberíniho, Babušková</li>
|
||||||
|
<li>Parkovisko pred budovou je určené pre nájomníkov, nie pre hostí</li>
|
||||||
|
</ul>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<!-- Map Button -->
|
||||||
|
<tr>
|
||||||
|
<td style="padding: 8px 32px 32px 32px; text-align: center;">
|
||||||
|
<a href="https://www.google.sk/maps/place/Seber%C3%ADniho+482%2F1,+821+04+Bratislava/@48.1537526,17.1627419,17z"
|
||||||
|
style="display: inline-block; padding: 16px 32px; background: linear-gradient(135deg, #10b981 0%, #059669 100%); color: #ffffff; text-decoration: none; font-size: 15px; font-weight: 700; border-radius: 12px; letter-spacing: 0.3px; box-shadow: 0 4px 14px rgba(16, 185, 129, 0.4);">
|
||||||
|
🗺 Zobraziť na mape
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<!-- Closing Message -->
|
||||||
|
<tr>
|
||||||
|
<td style="padding: 0 32px 40px 32px; text-align: center;">
|
||||||
|
<div style="width: 60px; height: 4px; background: linear-gradient(90deg, #10b981 0%, #34d399 100%); margin: 0 auto 20px auto; border-radius: 2px;"></div>
|
||||||
|
<p style="margin: 0; font-size: 20px; font-weight: 700; color: #0f172a;">
|
||||||
|
Tešíme sa na Vás!
|
||||||
|
</p>
|
||||||
|
<p style="margin: 8px 0 0 0; font-size: 15px; color: #64748b;">
|
||||||
|
Prajeme Vám veľa úspechov pri školení.
|
||||||
|
</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<!-- Footer -->
|
||||||
|
<tr>
|
||||||
|
<td style="background: linear-gradient(135deg, #0f172a 0%, #1e293b 100%); padding: 32px;">
|
||||||
|
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%">
|
||||||
|
<tr>
|
||||||
|
<td style="text-align: center;">
|
||||||
|
<p style="margin: 0 0 16px 0; font-size: 12px; color: #94a3b8;">
|
||||||
|
Tento e-mail je odosielaný účastníkom školenia pred jeho začatím.
|
||||||
|
</p>
|
||||||
|
<table role="presentation" cellspacing="0" cellpadding="0" border="0" style="margin: 0 auto;">
|
||||||
|
<tr>
|
||||||
|
<td style="padding-right: 24px; border-right: 1px solid #334155;">
|
||||||
|
<p style="margin: 0; font-size: 12px; color: #cbd5e1;">
|
||||||
|
INBOX SK s.r.o.<br>
|
||||||
|
Seberíniho 1, 821 03 Bratislava
|
||||||
|
</p>
|
||||||
|
</td>
|
||||||
|
<td style="padding-left: 24px;">
|
||||||
|
<p style="margin: 0; font-size: 12px; color: #cbd5e1;">
|
||||||
|
+421 950 608 326<br>
|
||||||
|
<a href="mailto:info@slovensko.ai" style="color: #10b981; text-decoration: none;">info@slovensko.ai</a>
|
||||||
|
</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<p style="margin: 20px 0 0 0; font-size: 11px; color: #64748b;">
|
||||||
|
IČO: 44813295 | IČ DPH: SK2022842657 | 2621117083/1100 (TatraBanka)
|
||||||
|
</p>
|
||||||
|
<p style="margin: 16px 0 0 0;">
|
||||||
|
<a href="https://www.slovensko.ai" style="color: #10b981; text-decoration: none; font-size: 12px; font-weight: 600;">www.slovensko.ai</a>
|
||||||
|
</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
52
src/templates/emails/invitation-email.txt
Normal file
52
src/templates/emails/invitation-email.txt
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
Dobrý deň,
|
||||||
|
|
||||||
|
pripravili sme pre Vás súhrn všetkých potrebných organizačných informácií k Vášmu kurzu.
|
||||||
|
Potvrďte nám prosím prečítanie tohto e-mailu.
|
||||||
|
|
||||||
|
═══════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
KURZ: {{courseName}}
|
||||||
|
TERMÍN: {{courseDate}}
|
||||||
|
ČAS: 09:00 - 15:00 + Diskusia
|
||||||
|
|
||||||
|
Školenie sa bude konať prezenčne. Program bude pozostávať z výukových blokov, s prestávkami 10-15 minút a jednou 45-60 minútovou obedovou prestávkou.
|
||||||
|
|
||||||
|
TIP: Pre ešte lepší zážitok zo školenia odporúčame priniesť si vlastný notebook. V cene školenia je občerstvenie (káva, čaj).
|
||||||
|
|
||||||
|
═══════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
MIESTO KONANIA:
|
||||||
|
INBOX SK s.r.o.
|
||||||
|
Seberíniho 1, Bratislava
|
||||||
|
7. poschodie vpravo
|
||||||
|
|
||||||
|
═══════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
DOPRAVA:
|
||||||
|
• Autobusom: Nevädzová (66, 75, 96), Tomášikova (50)
|
||||||
|
• Trolejbusom: Gagarinova (71, 72)
|
||||||
|
Z Hlavnej stanice trolejbus 71, z Autobusovej stanice trolejbus 72
|
||||||
|
• Električkou: Tomášikova (9)
|
||||||
|
• Autom: GPS N 48° 09" 13.295', E 17° 09" 53.773'
|
||||||
|
|
||||||
|
PARKOVANIE:
|
||||||
|
• Parkovisko pri budove: 3,70 EUR/hod (15 min zadarmo)
|
||||||
|
• Hotel Bratislava: 10 EUR/celý deň
|
||||||
|
• PAAS zóna Ružinov-Pošeň: 1,50 EUR/hod (od 12:00)
|
||||||
|
Zaparkujete na uliciach Obilná, Jašíkova, Andreja Mráza, Seberíniho, Babušková
|
||||||
|
• Parkovisko pred budovou je určené pre nájomníkov, nie pre hostí
|
||||||
|
|
||||||
|
MAPA: https://www.google.sk/maps/place/Seber%C3%ADniho+482%2F1,+821+04+Bratislava/@48.1537526,17.1627419,17z
|
||||||
|
|
||||||
|
═══════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
Tešíme sa na Vás!
|
||||||
|
Prajeme Vám veľa úspechov pri školení.
|
||||||
|
|
||||||
|
---
|
||||||
|
Tento e-mail je odosielaný účastníkom školenia pred jeho začatím.
|
||||||
|
|
||||||
|
INBOX SK s.r.o., Seberíniho 1, 821 03 Bratislava
|
||||||
|
Kontakt: +421 950 608 326, info@slovensko.ai
|
||||||
|
www.slovensko.ai
|
||||||
|
IČO: 44813295 | IČ DPH: SK2022842657 | 2621117083/1100 (TatraBanka)
|
||||||
Reference in New Issue
Block a user