This commit implements a complete email notifications system for purchase confirmations and event reminders as requested in the medium priority backlog tasks. ## Features Added ### Purchase Confirmation Emails - Automatically sent when orders are marked as paid - Supports both single tickets and multi-ticket orders - Includes PDF ticket attachments - Professional HTML and text templates in French ### Event Reminder Emails - Automated reminders sent 7 days, 1 day, and day of events - Only sent to users with active tickets - Smart messaging based on time until event - Venue details and ticket information included ### Background Jobs - EventReminderJob: Sends reminders to all users for a specific event - EventReminderSchedulerJob: Daily scheduler to queue reminder jobs - Proper error handling and logging ### Email Templates - Responsive HTML templates with ApéroNight branding - Text fallbacks for better email client compatibility - Dynamic content based on number of tickets and time until event ### Configuration & Testing - Environment-based SMTP configuration for production - Development setup with MailCatcher support - Comprehensive test suite with mocking for PDF generation - Integration tests for end-to-end functionality - Documentation with usage examples ## Technical Implementation - Enhanced TicketMailer with new notification methods - Background job scheduling via Rails initializer - Order model integration for automatic purchase confirmations - Proper associations handling for user/ticket relationships - Configurable via environment variables ## Files Added/Modified - Enhanced app/mailers/ticket_mailer.rb with order support - Added app/jobs/event_reminder_*.rb for background processing - Updated email templates in app/views/ticket_mailer/ - Added automatic scheduling in config/initializers/ - Comprehensive test coverage in test/ directory - Complete documentation in docs/email-notifications.md 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
113 lines
6.3 KiB
Plaintext
Executable File
113 lines
6.3 KiB
Plaintext
Executable File
<div style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; max-width: 600px; margin: 0 auto; padding: 20px; background-color: #f8f9fa; border-radius: 8px;">
|
|
<div style="text-align: center; padding: 20px 0; border-bottom: 1px solid #e9ecef;">
|
|
<h1 style="color: #4c1d95; margin: 0; font-size: 28px;">ApéroNight</h1>
|
|
<p style="color: #6c757d; margin: 10px 0 0;">Confirmation de votre achat</p>
|
|
</div>
|
|
|
|
<div style="background-color: white; border-radius: 8px; padding: 30px; margin: 20px 0; box-shadow: 0 2px 4px rgba(0,0,0,0.1);">
|
|
<h2 style="color: #212529; margin-top: 0;">Bonjour <%= @user.email.split('@').first %>,</h2>
|
|
|
|
<p style="color: #495057; line-height: 1.6;">
|
|
<% if defined?(@order) && @order.present? %>
|
|
Merci pour votre achat ! Nous avons le plaisir de vous confirmer votre commande pour l'événement <strong><%= @event.name %></strong>.
|
|
<% else %>
|
|
Merci pour votre achat ! Nous avons le plaisir de vous confirmer votre billet pour l'événement <strong><%= @event.name %></strong>.
|
|
<% end %>
|
|
</p>
|
|
|
|
<div style="background-color: #f8f9fa; border-radius: 6px; padding: 20px; margin: 25px 0;">
|
|
<% if defined?(@order) && @order.present? %>
|
|
<h3 style="color: #4c1d95; margin-top: 0; border-bottom: 1px solid #e9ecef; padding-bottom: 10px;">Détails de votre commande</h3>
|
|
|
|
<div style="margin-bottom: 20px;">
|
|
<div style="display: flex; justify-content: space-between; margin-bottom: 15px;">
|
|
<div>
|
|
<p style="margin: 0; color: #6c757d; font-size: 14px;">Événement</p>
|
|
<p style="margin: 5px 0 0; font-weight: bold; color: #212529;"><%= @event.name %></p>
|
|
</div>
|
|
<div style="text-align: right;">
|
|
<p style="margin: 0; color: #6c757d; font-size: 14px;">Date & heure</p>
|
|
<p style="margin: 5px 0 0; font-weight: bold; color: #212529;"><%= @event.start_time.strftime("%d %B %Y à %H:%M") %></p>
|
|
</div>
|
|
</div>
|
|
|
|
<div style="display: flex; justify-content: space-between;">
|
|
<div>
|
|
<p style="margin: 0; color: #6c757d; font-size: 14px;">Nombre de billets</p>
|
|
<p style="margin: 5px 0 0; font-weight: bold; color: #212529;"><%= @tickets.count %></p>
|
|
</div>
|
|
<div style="text-align: right;">
|
|
<p style="margin: 0; color: #6c757d; font-size: 14px;">Total</p>
|
|
<p style="margin: 5px 0 0; font-weight: bold; color: #212529;"><%= number_to_currency(@order.total_amount_euros, unit: "€") %></p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<h4 style="color: #4c1d95; margin: 20px 0 15px;">Billets inclus :</h4>
|
|
<% @tickets.each_with_index do |ticket, index| %>
|
|
<div style="border: 1px solid #e9ecef; border-radius: 4px; padding: 15px; margin-bottom: 10px; background-color: white;">
|
|
<div style="display: flex; justify-content: space-between;">
|
|
<div>
|
|
<p style="margin: 0 0 5px; font-weight: bold; color: #212529;">Billet #<%= index + 1 %></p>
|
|
<p style="margin: 0; color: #6c757d; font-size: 14px;"><%= ticket.ticket_type.name %></p>
|
|
</div>
|
|
<div style="text-align: right;">
|
|
<p style="margin: 0; font-weight: bold; color: #212529;"><%= number_to_currency(ticket.price_cents / 100.0, unit: "€") %></p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<% end %>
|
|
<% else %>
|
|
<h3 style="color: #4c1d95; margin-top: 0; border-bottom: 1px solid #e9ecef; padding-bottom: 10px;">Détails de votre billet</h3>
|
|
|
|
<div style="display: flex; justify-content: space-between; margin-bottom: 15px;">
|
|
<div>
|
|
<p style="margin: 0; color: #6c757d; font-size: 14px;">Événement</p>
|
|
<p style="margin: 5px 0 0; font-weight: bold; color: #212529;"><%= @event.name %></p>
|
|
</div>
|
|
<div style="text-align: right;">
|
|
<p style="margin: 0; color: #6c757d; font-size: 14px;">Type de billet</p>
|
|
<p style="margin: 5px 0 0; font-weight: bold; color: #212529;"><%= @ticket.ticket_type.name %></p>
|
|
</div>
|
|
</div>
|
|
|
|
<div style="display: flex; justify-content: space-between;">
|
|
<div>
|
|
<p style="margin: 0; color: #6c757d; font-size: 14px;">Date & heure</p>
|
|
<p style="margin: 5px 0 0; font-weight: bold; color: #212529;"><%= @event.start_time.strftime("%d %B %Y à %H:%M") %></p>
|
|
</div>
|
|
<div style="text-align: right;">
|
|
<p style="margin: 0; color: #6c757d; font-size: 14px;">Prix</p>
|
|
<p style="margin: 5px 0 0; font-weight: bold; color: #212529;"><%= number_to_currency(@ticket.price_cents / 100.0, unit: "€") %></p>
|
|
</div>
|
|
</div>
|
|
<% end %>
|
|
</div>
|
|
|
|
<div style="text-align: center; margin: 30px 0;">
|
|
<% if defined?(@order) && @order.present? %>
|
|
<p style="color: #495057; margin-bottom: 20px;">Vos billets sont attachés à cet email en format PDF.</p>
|
|
<p style="color: #495057; margin-bottom: 20px;">Présentez-les à l'entrée de l'événement pour y accéder.</p>
|
|
<% else %>
|
|
<p style="color: #495057; margin-bottom: 20px;">Votre billet est attaché à cet email en format PDF.</p>
|
|
<p style="color: #495057; margin-bottom: 20px;">Présentez-le à l'entrée de l'événement pour y accéder.</p>
|
|
<% end %>
|
|
</div>
|
|
|
|
<div style="background-color: #fff3cd; border-radius: 6px; padding: 15px; border-left: 4px solid #ffc107;">
|
|
<p style="margin: 0; color: #856404; font-size: 14px;">
|
|
<strong>Important :</strong>
|
|
<% if defined?(@order) && @order.present? %>
|
|
Ces billets sont valables pour une seule entrée chacun. Conservez-les précieusement.
|
|
<% else %>
|
|
Ce billet est valable pour une seule entrée. Conservez-le précieusement.
|
|
<% end %>
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div style="text-align: center; color: #6c757d; font-size: 14px; padding: 20px 0;">
|
|
<p style="margin: 0;">Si vous avez des questions, contactez-nous à <a href="mailto:support@aperonight.com" style="color: #4c1d95; text-decoration: none;">support@aperonight.com</a></p>
|
|
<p style="margin: 10px 0 0;">© <%= Time.current.year %> ApéroNight. Tous droits réservés.</p>
|
|
</div>
|
|
</div> |