Overview
The Restaurant Reservation System sends automated email notifications to customers and restaurant owners using the Firebase Trigger Email Extension. This extension monitors a Firestore collection and sends emails automatically.
How It Works
The app uses a queue-based email system:
- Your app writes an email document to the
mail collection in Firestore
- The Firebase Trigger Email extension detects the new document
- The extension sends the email via SMTP (Gmail, SendGrid, etc.)
- The extension updates the document with delivery status
Architecture
Install Firebase Trigger Email Extension
Install the Extension
- Go to Firebase Console
- Navigate to Extensions in the left sidebar
- Click Install Extension
- Search for “Trigger Email”
- Click Install on the official Firebase extension
Configure SMTP Settings
During installation, you’ll be prompted for:| Setting | Description | Example |
|---|
| SMTP Connection URI | Your email provider’s SMTP server | smtps://[email protected]:[email protected]:465 |
| Email Collection | Firestore collection to monitor | mail |
| Default FROM | Sender email address | [email protected] |
| Default REPLY-TO | Reply-to email address | [email protected] |
Gmail SMTP Example
SendGrid Example
Configure Firestore Collection
Set the email documents collection name to:This matches the collection used in ServicioEmail. Set Firestore Location
Choose the same location as your Firestore database (e.g., us-central1).
Email Service Implementation
The ServicioEmail class handles all email operations:
Service Location
lib/adaptadores/servicio_email.dart
Basic Usage
import 'package:app_restaurante/adaptadores/servicio_email.dart';
final emailService = ServicioEmail();
// Send reservation confirmation to customer
await emailService.enviarReservaConfirmada(
emailCliente: '[email protected]',
nombreCliente: 'Juan Pérez',
nombreNegocio: 'Restaurante El Buen Sabor',
fechaHora: DateTime(2024, 12, 25, 20, 0),
nombreMesa: 'Mesa VIP-1',
numeroPersonas: 4,
);
Email Types
The service provides these email methods:
1. Customer Emails
Reservation Confirmed
await emailService.enviarReservaConfirmada(
emailCliente: '[email protected]',
nombreCliente: 'María García',
nombreNegocio: 'Restaurante Mar y Tierra',
fechaHora: DateTime(2024, 12, 31, 21, 30),
nombreMesa: 'Mesa Terraza-3',
numeroPersonas: 2,
telefono: '+5491112345678',
reservaId: 'res_abc123',
);
Reservation Cancelled by Customer
await emailService.enviarReservaCanceladaPorCliente(
emailCliente: '[email protected]',
nombreCliente: 'Carlos López',
nombreNegocio: 'Restaurante El Mirador',
fechaHora: DateTime(2024, 12, 20, 19, 0),
nombreMesa: 'Mesa Salón-2',
numeroPersonas: 6,
);
Reservation Cancelled by Restaurant
await emailService.enviarReservaCanceladaPorRestaurante(
emailCliente: '[email protected]',
nombreCliente: 'Ana Martínez',
nombreNegocio: 'Restaurante La Esquina',
fechaHora: DateTime(2024, 12, 18, 20, 30),
nombreMesa: 'Mesa Patio-1',
numeroPersonas: 4,
motivo: 'Evento privado programado para esa fecha',
);
2. Owner Emails
New Reservation Notification
await emailService.enviarNuevaReservaAlDueno(
emailDueno: '[email protected]',
nombreCliente: 'Roberto Sánchez',
emailCliente: '[email protected]',
telefonoCliente: '+5491198765432',
fechaHora: DateTime(2024, 12, 22, 21, 0),
nombreMesa: 'Mesa VIP-2',
numeroPersonas: 8,
nombreNegocio: 'Restaurante Los Arcos',
);
Cancellation by Customer
await emailService.enviarCancelacionClienteAlDueno(
emailDueno: '[email protected]',
nombreCliente: 'Laura Fernández',
fechaHora: DateTime(2024, 12, 19, 19, 30),
nombreMesa: 'Mesa Jardín-4',
numeroPersonas: 3,
nombreNegocio: 'Restaurante Vista al Mar',
);
Convenience Methods
Use these methods with Reserva entities:
// Notify both customer and owner about new reservation
await emailService.notificarReservaConfirmada(
reserva,
nombreNegocio: 'Mi Restaurante',
nombreMesa: 'Mesa VIP-1',
emailDueno: '[email protected]',
);
// Notify about customer cancellation
await emailService.notificarReservaCanceladaPorCliente(
reserva,
nombreNegocio: 'Mi Restaurante',
nombreMesa: 'Mesa Terraza-2',
emailDueno: '[email protected]',
);
// Notify customer about restaurant cancellation
await emailService.notificarReservaCanceladaPorRestaurante(
reserva,
nombreNegocio: 'Mi Restaurante',
nombreMesa: 'Mesa Salón-5',
motivo: 'Reparaciones de emergencia',
);
Email Templates
All emails use responsive HTML templates with:
- Professional design with restaurant branding
- Color-coded status indicators (green for confirmed, red for cancelled)
- Reservation details table
- Important reminders and tips
- Auto-generated footer with year and disclaimer
Template Structure
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body style="margin: 0; padding: 0; font-family: Arial, sans-serif; background-color: #f5f5f5;">
<div style="max-width: 600px; margin: 0 auto; background-color: white;">
<!-- Header with title -->
<div style="background-color: #27AE60; padding: 30px; text-align: center;">
<h1 style="color: white; margin: 0;">✅ Reserva Confirmada</h1>
</div>
<!-- Content with reservation details -->
<div style="padding: 30px;">
<!-- Email-specific content -->
</div>
<!-- Footer -->
<div style="background-color: #f5f5f5; padding: 20px; text-align: center;">
<p style="margin: 0; color: #666;">© 2024 Sistema de Reservas</p>
<p style="margin: 8px 0 0 0; color: #999;">Email automático, no responder</p>
</div>
</div>
</body>
</html>
Color Scheme
| Status | Color | Hex Code |
|---|
| Confirmed | Green | #27AE60 |
| Cancelled | Red | #F44336 |
| Warning | Orange | #FF9800 |
| Info | Blue | #2196F3 |
Integration with Use Cases
Emails are automatically sent from use cases:
Creating a Reservation
lib/aplicacion/crear_reserva.dart
class CrearReserva {
final ServicioEmail? servicioEmail;
Future<Reserva> ejecutar(...) async {
// ... validation and creation logic ...
final reserva = await reservaRepositorio.crearReserva(reservaTemporal);
// Send emails automatically
if (servicioEmail != null) {
try {
await servicioEmail!.notificarReservaConfirmada(
reserva,
nombreNegocio: nombreNegocio,
nombreMesa: nombreMesa,
emailDueno: emailDueno,
);
} catch (e) {
// Don't fail reservation if email fails
print('⚠️ Error sending email: $e');
}
}
return reserva;
}
}
Cancelling a Reservation
lib/aplicacion/cancelar_reserva.dart
class CancelarReserva {
Future<void> ejecutar(String reservaId) async {
// ... cancellation logic ...
// Notify via email
await servicioEmail.notificarReservaCanceladaPorCliente(
reserva,
nombreNegocio: negocio.nombre,
nombreMesa: mesa.nombre,
emailDueno: negocio.email,
);
}
}
Monitoring Email Delivery
The Firebase Trigger Email extension updates each email document with delivery status:
{
"to": ["[email protected]"],
"message": {
"subject": "✅ Reserva confirmada",
"html": "..."
},
"delivery": {
"state": "SUCCESS",
"startTime": "2024-12-10T15:30:00.000Z",
"endTime": "2024-12-10T15:30:02.123Z",
"info": {
"messageId": "<[email protected]>"
}
}
}
Delivery States
| State | Description |
|---|
PENDING | Email queued, not yet processed |
PROCESSING | Extension is sending the email |
SUCCESS | Email sent successfully |
ERROR | Failed to send (check error field) |
Query Email Status
final mailDoc = await FirebaseFirestore.instance
.collection('mail')
.doc('email_doc_id')
.get();
final delivery = mailDoc.data()?['delivery'];
final state = delivery?['state']; // SUCCESS, ERROR, etc.
Testing Emails
Test in Development
void main() async {
// Initialize Firebase
await Firebase.initializeApp();
final emailService = ServicioEmail();
// Send test email
await emailService.enviarReservaConfirmada(
emailCliente: '[email protected]',
nombreCliente: 'Test User',
nombreNegocio: 'Test Restaurant',
fechaHora: DateTime.now().add(Duration(days: 7)),
nombreMesa: 'Test Table',
numeroPersonas: 2,
);
print('✅ Test email sent! Check your inbox.');
}
Verify Email Delivery
- Check the
mail collection in Firestore Console
- Look for the document you just created
- Verify the
delivery.state is SUCCESS
- Check your email inbox
Use a service like MailHog or Mailtrap for testing emails in development without sending real emails.
Troubleshooting
Emails not sending
- Check Firestore Collection: Verify documents are being created in
mail collection
- Check Extension Logs: Firebase Console → Extensions → Trigger Email → Logs
- Verify SMTP Credentials: Test SMTP connection manually
- Check Security Rules: Ensure the extension can read/write to
mail collection
Gmail SMTP issues
- Use App Password, not your regular password
- Enable “Less secure app access” (if using old Gmail)
- Check for “suspicious activity” alerts from Google
SendGrid issues
- Verify API key has “Mail Send” permission
- Check SendGrid activity logs
- Ensure sender email is verified in SendGrid
Production Best Practices
Never hardcode SMTP credentials in your code. Use Firebase Remote Config or environment variables.
- Use a custom domain: Configure a professional sender email (e.g.,
[email protected])
- Set up SPF/DKIM: Prevent emails from going to spam
- Monitor delivery rates: Track
SUCCESS vs ERROR states in Firestore
- Rate limiting: Be aware of SMTP provider limits (Gmail: 500/day, SendGrid varies by plan)
- Handle failures gracefully: Don’t fail reservations if emails fail
Next Steps