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:
richardtekula
2026-01-30 12:51:13 +01:00
parent 525a3eb551
commit 722c9fd80b
8 changed files with 869 additions and 124 deletions

View File

@@ -306,3 +306,22 @@ export const sendCertificateEmail = async (req, res, next) => {
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);
}
};

View File

@@ -180,4 +180,12 @@ router.post(
aiKurzyController.sendCertificateEmail
);
// ==================== INVITATION EMAIL ====================
router.post(
'/registracie/:registraciaId/send-invitation',
validateParams(registraciaIdSchema),
aiKurzyController.sendInvitationEmail
);
export default router;

View File

@@ -1,5 +1,6 @@
import fs from 'fs/promises';
import path from 'path';
import { fileURLToPath } from 'url';
import axios from 'axios';
import { db } from '../../config/database.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 { 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
*/
@@ -21,136 +30,40 @@ const formatDate = (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
*/
export const generateCertificateEmailHtml = ({ participantName, courseName, courseDate, courseType }) => {
return `
<!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: #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();
export const generateCertificateEmailHtml = async ({ participantName, courseName, courseDate, courseType }) => {
const { htmlTemplate } = await loadTemplates();
return replaceTemplatePlaceholders(htmlTemplate, { participantName, courseName, courseDate, courseType });
};
/**
* Generate plain text version of the certificate email
*/
export const generateCertificateEmailText = ({ participantName, courseName, courseDate, courseType }) => {
return `
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 || '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();
export const generateCertificateEmailText = async ({ participantName, courseName, courseDate, courseType }) => {
const { textTemplate } = await loadTemplates();
return replaceTemplatePlaceholders(textTemplate, { participantName, courseName, courseDate, courseType });
};
/**
@@ -410,8 +323,8 @@ export const sendCertificateEmail = async (prilohaId, userId) => {
courseType: registration.kurzTyp,
};
const htmlBody = generateCertificateEmailHtml(emailData);
const textBody = generateCertificateEmailText(emailData);
const htmlBody = await generateCertificateEmailHtml(emailData);
const textBody = await generateCertificateEmailText(emailData);
const subject = `Certifikát o absolvovaní kurzu: ${registration.kurzNazov}`;
// Get JMAP config

View 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,
};
};

View 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;">
&#127891;
</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;">&#128197;</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;">
&#128206;
</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>

View 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

View 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;">
&#128197; 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;">
&#128337; Č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;">
&#127891; 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;">
&#128161;
</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;">
&#128205;
</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;">
&#128652; 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;">
&#128663; 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);">
&#128506; 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>

View 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)