refactor: switch from Mailgun to AhaSend for email notifications
- Remove Mailgun dependencies - Add AhaSend REST API integration - Update configuration with AhaSend credentials - Note: API returning 'invalid credentials' - may need AhaSend support verification Credentials configured: - Account ID: 8a14f9a5-6e6d-47c9-b5d6-a687265b36a3 - API Key: aha-sk-A0c9WOA8cs3RpvkAGwHOP9o6pHx-gLTUki1gSWVeC_6kd86-Npn077zta-rvg9oo
This commit is contained in:
@@ -277,7 +277,7 @@
|
|||||||
- Implement 6-hour cache expiration
|
- Implement 6-hour cache expiration
|
||||||
- _Requirements: 11.1, 11.2, 11.3, 11.4, 11.5, 11.6_
|
- _Requirements: 11.1, 11.2, 11.3, 11.4, 11.5, 11.6_
|
||||||
|
|
||||||
- [x] 33. Implement notification system
|
- [ ] 33. Implement notification system
|
||||||
- Create notification service with area-of-interest matching
|
- Create notification service with area-of-interest matching
|
||||||
- Build email notification integration (SendGrid)
|
- Build email notification integration (SendGrid)
|
||||||
- Implement in-app notification storage and retrieval
|
- Implement in-app notification storage and retrieval
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ The notification system enables real-time user engagement by notifying users abo
|
|||||||
- **cleanupOldNotifications**: Remove read notifications older than 30 days
|
- **cleanupOldNotifications**: Remove read notifications older than 30 days
|
||||||
|
|
||||||
#### EmailService (`server/src/services/emailService.ts`)
|
#### EmailService (`server/src/services/emailService.ts`)
|
||||||
- **Mailgun Integration**: Uses Mailgun API for email delivery
|
- **AhaSend Integration**: Uses AhaSend REST API for email delivery
|
||||||
- **sendEmail**: Generic email sending function
|
- **sendEmail**: Generic email sending function
|
||||||
- **sendNotificationEmail**: Formatted notification emails with HTML templates
|
- **sendNotificationEmail**: Formatted notification emails with HTML templates
|
||||||
- **testConnection**: Verify email configuration
|
- **testConnection**: Verify email configuration
|
||||||
@@ -99,25 +99,27 @@ FRONTEND_URL="http://localhost:5173"
|
|||||||
TEST_EMAIL="test@example.com"
|
TEST_EMAIL="test@example.com"
|
||||||
```
|
```
|
||||||
|
|
||||||
### Mailgun Setup
|
### AhaSend Setup
|
||||||
|
|
||||||
1. **API Key**: `dd27be7a81dd7b0980ada31fec9a9bd9-e80d8b76-fd80c63b`
|
1. **API Key**: Get your API key from AhaSend dashboard
|
||||||
2. **Domain**: `sandbox1835a1917b07443f8c1ca858689c42ea.mailgun.org`
|
2. **From Email**: `Beach Litter Platform <noreply@beachlitter.ie>`
|
||||||
3. **From Email**: `Beach Litter Platform <noreply@sandbox1835a1917b07443f8c1ca858689c42ea.mailgun.org>`
|
3. **API Endpoint**: `https://api.ahasend.com/v1`
|
||||||
|
|
||||||
#### Sandbox Mode Authorization
|
#### Getting Started with AhaSend
|
||||||
|
|
||||||
**IMPORTANT**: Mailgun sandbox domains require you to authorize recipient email addresses before sending.
|
1. Sign up at https://ahasend.com/
|
||||||
|
2. Verify your sending domain
|
||||||
|
3. Get your API key from the dashboard
|
||||||
|
4. Add the API key to your `.env` file:
|
||||||
|
```env
|
||||||
|
AHASEND_API_KEY="your_api_key_here"
|
||||||
|
```
|
||||||
|
|
||||||
To authorize recipients:
|
**Benefits of AhaSend**:
|
||||||
1. Go to https://app.mailgun.com/
|
- No sandbox restrictions - send to any email
|
||||||
2. Navigate to Sending → Domains
|
- Simple REST API integration
|
||||||
3. Click on your sandbox domain: `sandbox1835a1917b07443f8c1ca858689c42ea.mailgun.org`
|
- Good deliverability rates
|
||||||
4. Go to "Authorized Recipients" section
|
- Straightforward pricing
|
||||||
5. Add email addresses you want to test with (e.g., `psbailey31@gmail.com`)
|
|
||||||
6. Recipients will receive a confirmation email - they must click the link to authorize
|
|
||||||
|
|
||||||
**For Production**: Use a verified custom domain instead of sandbox to send to any email address without authorization.
|
|
||||||
|
|
||||||
## Testing
|
## Testing
|
||||||
|
|
||||||
@@ -231,16 +233,13 @@ Emails include:
|
|||||||
|
|
||||||
### Emails Not Sending
|
### Emails Not Sending
|
||||||
|
|
||||||
1. **Check Mailgun API key is correct**: Verify in `.env` file
|
1. **Check AhaSend API key is correct**: Verify in `.env` file
|
||||||
2. **Verify domain is configured**: Should be `sandbox1835a1917b07443f8c1ca858689c42ea.mailgun.org`
|
2. **Verify domain is configured**: Check your sending domain in AhaSend dashboard
|
||||||
3. **Authorize recipients for sandbox**:
|
3. **Check API response**: Review error messages in server logs
|
||||||
- Go to https://app.mailgun.com/
|
4. **Verify email format**: Ensure from/to emails are valid
|
||||||
- Navigate to your sandbox domain
|
5. **Review server logs**: Check for detailed error messages
|
||||||
- Add authorized recipients (they must confirm via email)
|
6. **Test email service**: Run `npx tsx src/scripts/testEmail.ts`
|
||||||
- Error "Forbidden" means recipient is not authorized
|
7. **Check AhaSend status**: Visit AhaSend dashboard for account status
|
||||||
4. **Review server logs**: Check for detailed error messages
|
|
||||||
5. **Test email service**: Run `npx tsx src/scripts/testEmail.ts`
|
|
||||||
6. **For production**: Use a verified custom domain to avoid sandbox restrictions
|
|
||||||
|
|
||||||
### Notifications Not Appearing
|
### Notifications Not Appearing
|
||||||
|
|
||||||
|
|||||||
@@ -20,9 +20,8 @@ REDIS_URL="redis://localhost:6379"
|
|||||||
OPENWEATHER_API_KEY="your_openweather_api_key_here"
|
OPENWEATHER_API_KEY="your_openweather_api_key_here"
|
||||||
# Optional: STORMGLASS_API_KEY="your_stormglass_api_key_here" (10 free requests/day for tide data)
|
# Optional: STORMGLASS_API_KEY="your_stormglass_api_key_here" (10 free requests/day for tide data)
|
||||||
|
|
||||||
# Email Notifications (Mailgun)
|
# Email Notifications (AhaSend)
|
||||||
MAILGUN_API_KEY="dd27be7a81dd7b0980ada31fec9a9bd9-e80d8b76-fd80c63b"
|
AHASEND_API_KEY="your_ahasend_api_key_here"
|
||||||
MAILGUN_DOMAIN="sandbox1835a1917b07443f8c1ca858689c42ea.mailgun.org"
|
FROM_EMAIL="Beach Litter Platform <noreply@beachlitter.ie>"
|
||||||
FROM_EMAIL="Beach Litter Platform <noreply@sandbox1835a1917b07443f8c1ca858689c42ea.mailgun.org>"
|
|
||||||
FRONTEND_URL="http://localhost:5173"
|
FRONTEND_URL="http://localhost:5173"
|
||||||
TEST_EMAIL="test@example.com"
|
TEST_EMAIL="test@example.com"
|
||||||
|
|||||||
0
server/AHASEND_SETUP.md
Normal file
0
server/AHASEND_SETUP.md
Normal file
1916
server/package-lock.json
generated
1916
server/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -28,12 +28,11 @@
|
|||||||
"exifr": "^7.1.3",
|
"exifr": "^7.1.3",
|
||||||
"express": "^4.21.1",
|
"express": "^4.21.1",
|
||||||
"express-validator": "^7.2.0",
|
"express-validator": "^7.2.0",
|
||||||
"form-data": "^4.0.4",
|
|
||||||
"helmet": "^8.0.0",
|
"helmet": "^8.0.0",
|
||||||
"json2csv": "^6.0.0-alpha.2",
|
"json2csv": "^6.0.0-alpha.2",
|
||||||
"mailgun.js": "^12.1.1",
|
|
||||||
"multer": "^1.4.5-lts.1",
|
"multer": "^1.4.5-lts.1",
|
||||||
"node-cron": "^4.2.1",
|
"node-cron": "^4.2.1",
|
||||||
|
"nodemailer": "^7.0.10",
|
||||||
"redis": "^5.9.0",
|
"redis": "^5.9.0",
|
||||||
"zod": "^3.23.8"
|
"zod": "^3.23.8"
|
||||||
},
|
},
|
||||||
@@ -44,6 +43,7 @@
|
|||||||
"@types/multer": "^1.4.12",
|
"@types/multer": "^1.4.12",
|
||||||
"@types/node": "^22.9.0",
|
"@types/node": "^22.9.0",
|
||||||
"@types/node-cron": "^3.0.11",
|
"@types/node-cron": "^3.0.11",
|
||||||
|
"@types/nodemailer": "^7.0.3",
|
||||||
"prisma": "^5.22.0",
|
"prisma": "^5.22.0",
|
||||||
"tsx": "^4.19.2",
|
"tsx": "^4.19.2",
|
||||||
"typescript": "^5.6.3"
|
"typescript": "^5.6.3"
|
||||||
|
|||||||
@@ -11,8 +11,8 @@ async function testEmail() {
|
|||||||
console.log('📧 Testing Email Service with Mailgun\n');
|
console.log('📧 Testing Email Service with Mailgun\n');
|
||||||
|
|
||||||
console.log('Configuration:');
|
console.log('Configuration:');
|
||||||
console.log(` API Key: ${process.env.MAILGUN_API_KEY?.substring(0, 20)}...`);
|
console.log(` API Key: ${process.env.AHASEND_API_KEY?.substring(0, 30)}...`);
|
||||||
console.log(` Domain: ${process.env.MAILGUN_DOMAIN}`);
|
console.log(` Account ID: ${process.env.AHASEND_ACCOUNT_ID}`);
|
||||||
console.log(` From: ${process.env.FROM_EMAIL}`);
|
console.log(` From: ${process.env.FROM_EMAIL}`);
|
||||||
console.log('');
|
console.log('');
|
||||||
|
|
||||||
@@ -55,8 +55,8 @@ async function testEmail() {
|
|||||||
|
|
||||||
console.log('✅ Email testing completed!');
|
console.log('✅ Email testing completed!');
|
||||||
console.log('');
|
console.log('');
|
||||||
console.log('Note: If using Mailgun sandbox domain, you need to add authorized recipients');
|
console.log('Note: Make sure you have a valid AhaSend API key configured');
|
||||||
console.log('in your Mailgun dashboard: https://app.mailgun.com/');
|
console.log('Get your API key from: https://ahasend.com/');
|
||||||
}
|
}
|
||||||
|
|
||||||
testEmail().catch(console.error);
|
testEmail().catch(console.error);
|
||||||
|
|||||||
@@ -1,15 +1,8 @@
|
|||||||
import Mailgun from 'mailgun.js';
|
import axios from 'axios';
|
||||||
import formData from 'form-data';
|
|
||||||
|
|
||||||
const mailgun = new Mailgun(formData);
|
// AhaSend API configuration
|
||||||
|
const AHASEND_API_KEY = process.env.AHASEND_API_KEY || 'aha-sk-A0c9WOA8cs3RpvkAGwHOP9o6pHx-gLTUki1gSWVeC_6kd86-Npn077zta-rvg9oo';
|
||||||
// Initialize Mailgun client
|
const AHASEND_ACCOUNT_ID = process.env.AHASEND_ACCOUNT_ID || '8a14f9a5-6e6d-47c9-b5d6-a687265b36a3';
|
||||||
const mg = mailgun.client({
|
|
||||||
username: 'api',
|
|
||||||
key: process.env.MAILGUN_API_KEY || 'dd27be7a81dd7b0980ada31fec9a9bd9-e80d8b76-fd80c63b',
|
|
||||||
});
|
|
||||||
|
|
||||||
const MAILGUN_DOMAIN = process.env.MAILGUN_DOMAIN || 'sandbox1835a1917b07443f8c1ca858689c42ea.mailgun.org';
|
|
||||||
const FROM_EMAIL = process.env.FROM_EMAIL || 'Beach Litter Platform <noreply@beachlitter.ie>';
|
const FROM_EMAIL = process.env.FROM_EMAIL || 'Beach Litter Platform <noreply@beachlitter.ie>';
|
||||||
|
|
||||||
interface EmailData {
|
interface EmailData {
|
||||||
@@ -21,24 +14,49 @@ interface EmailData {
|
|||||||
|
|
||||||
export class EmailService {
|
export class EmailService {
|
||||||
/**
|
/**
|
||||||
* Send an email using Mailgun
|
* Send an email using AhaSend REST API
|
||||||
*/
|
*/
|
||||||
async sendEmail(data: EmailData): Promise<boolean> {
|
async sendEmail(data: EmailData): Promise<boolean> {
|
||||||
try {
|
try {
|
||||||
const messageData = {
|
// Parse from email
|
||||||
from: FROM_EMAIL,
|
const fromMatch = FROM_EMAIL.match(/<(.+)>/);
|
||||||
to: data.to,
|
const fromEmail = fromMatch ? fromMatch[1] : FROM_EMAIL;
|
||||||
|
const fromName = FROM_EMAIL.replace(/<.+>/, '').trim();
|
||||||
|
|
||||||
|
const payload = {
|
||||||
|
to: [{ email: data.to }],
|
||||||
|
from: {
|
||||||
|
email: fromEmail,
|
||||||
|
name: fromName || 'Beach Litter Platform'
|
||||||
|
},
|
||||||
subject: data.subject,
|
subject: data.subject,
|
||||||
text: data.text,
|
text_body: data.text,
|
||||||
html: data.html || this.generateHtmlFromText(data.text),
|
html_body: data.html || this.generateHtmlFromText(data.text),
|
||||||
};
|
};
|
||||||
|
|
||||||
const response = await mg.messages.create(MAILGUN_DOMAIN, messageData);
|
console.log('[Email] Sending request to AhaSend API...');
|
||||||
|
console.log('[Email] API Key:', AHASEND_API_KEY.substring(0, 30) + '...');
|
||||||
|
console.log('[Email] Account ID:', AHASEND_ACCOUNT_ID);
|
||||||
|
|
||||||
console.log(`[Email] Successfully sent to ${data.to}:`, response.id);
|
const response = await axios.post(
|
||||||
|
`https://api.ahasend.com/v1/email`,
|
||||||
|
payload,
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
'x-api-key': AHASEND_API_KEY,
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
console.log(`[Email] Successfully sent to ${data.to}:`, response.data);
|
||||||
return true;
|
return true;
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error(`[Email] Failed to send to ${data.to}:`, error.message);
|
console.error(`[Email] Failed to send to ${data.to}:`, error.response?.data || error.message);
|
||||||
|
if (error.response) {
|
||||||
|
console.error('[Email] Response status:', error.response.status);
|
||||||
|
console.error('[Email] Response headers:', error.response.headers);
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -153,13 +171,13 @@ To manage your notification preferences, visit your profile settings.
|
|||||||
// Send a test email to verify configuration
|
// Send a test email to verify configuration
|
||||||
const testEmail = process.env.TEST_EMAIL || 'test@example.com';
|
const testEmail = process.env.TEST_EMAIL || 'test@example.com';
|
||||||
|
|
||||||
await this.sendEmail({
|
const success = await this.sendEmail({
|
||||||
to: testEmail,
|
to: testEmail,
|
||||||
subject: 'Beach Litter Platform - Email Test',
|
subject: 'Beach Litter Platform - Email Test',
|
||||||
text: 'This is a test email from the Beach Litter Mapping Platform. If you received this, email notifications are working correctly!',
|
text: 'This is a test email from the Beach Litter Mapping Platform. If you received this, email notifications are working correctly!',
|
||||||
});
|
});
|
||||||
|
|
||||||
return true;
|
return success;
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error('[Email] Connection test failed:', error.message);
|
console.error('[Email] Connection test failed:', error.message);
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
Reference in New Issue
Block a user