Merge branch 'main' of git.bizmatch.net:aknuth/email-amazon
This commit is contained in:
commit
f372082512
|
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"name": "local/email_config",
|
||||
"type": "roundcube-plugin",
|
||||
"description": "Email Configuration - Manage OOO and Forwarding",
|
||||
"license": "MIT",
|
||||
"version": "1.0.0",
|
||||
"require": {
|
||||
"php": ">=7.0.0",
|
||||
"roundcube/plugin-installer": ">=0.1.3"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,75 @@
|
|||
<?php
|
||||
class email_config extends rcube_plugin
|
||||
{
|
||||
public $task = 'settings';
|
||||
|
||||
function init()
|
||||
{
|
||||
$this->add_texts('localization/', false);
|
||||
$this->add_hook('settings_actions', array($this, 'settings_actions'));
|
||||
$this->register_action('plugin.email_config', array($this, 'email_config_init'));
|
||||
}
|
||||
|
||||
function settings_actions($args)
|
||||
{
|
||||
$args['actions'][] = array(
|
||||
'action' => 'plugin.email_config',
|
||||
'class' => 'email-config',
|
||||
'label' => 'email_config',
|
||||
'domain' => 'email_config',
|
||||
);
|
||||
return $args;
|
||||
}
|
||||
|
||||
function email_config_init()
|
||||
{
|
||||
$rcmail = rcube::get_instance();
|
||||
$this->register_handler('plugin.body', array($this, 'email_config_form'));
|
||||
$rcmail->output->set_pagetitle('Email Configuration');
|
||||
$rcmail->output->send('plugin');
|
||||
}
|
||||
|
||||
function email_config_form()
|
||||
{
|
||||
$rcmail = rcube::get_instance();
|
||||
$email = $rcmail->user->get_username();
|
||||
$secret_key = 'SHARED_SECRET_KEY_987654321';
|
||||
$config_url = 'http://localhost:3008';
|
||||
$expires = time() + 3600;
|
||||
$data = $email . '|' . $expires;
|
||||
$signature = hash_hmac('sha256', $data, $secret_key);
|
||||
$url = $config_url . '/?email=' . urlencode($email) . '&expires=' . $expires . '&signature=' . $signature;
|
||||
|
||||
$out = '
|
||||
<div class="box" style="max-width: 600px; margin: 40px auto; padding: 30px; background: #fff; border-radius: 8px; box-shadow: 0 2px 8px rgba(0,0,0,0.1);">
|
||||
<div style="text-align: center; margin-bottom: 30px;">
|
||||
<svg width="64" height="64" viewBox="0 0 24 24" fill="none" style="margin: 0 auto 20px;">
|
||||
<path d="M20 4H4C2.9 4 2.01 4.9 2.01 6L2 18C2 19.1 2.9 20 4 20H20C21.1 20 22 19.1 22 18V6C22 4.9 21.1 4 20 4ZM20 8L12 13L4 8V6L12 11L20 6V8Z" fill="#4A90E2"/>
|
||||
</svg>
|
||||
<h2 style="margin: 0; color: #333; font-size: 24px; font-weight: 600;">Email Rules Configuration</h2>
|
||||
</div>
|
||||
|
||||
<div style="background: #f8f9fa; padding: 20px; border-radius: 6px; margin-bottom: 25px;">
|
||||
<p style="margin: 0 0 8px 0; color: #666; font-size: 14px;">Signed in as:</p>
|
||||
<p style="margin: 0; color: #333; font-size: 16px; font-weight: 500;">' . htmlspecialchars($email) . '</p>
|
||||
</div>
|
||||
|
||||
<p style="color: #666; line-height: 1.6; margin-bottom: 25px; text-align: center;">
|
||||
Configure out-of-office auto-replies and email forwarding rules for your account.
|
||||
</p>
|
||||
|
||||
<div style="text-align: center;">
|
||||
<a href="' . htmlspecialchars($url) . '" target="_blank"
|
||||
style="display: inline-block; background: #4A90E2; color: white; padding: 12px 32px;
|
||||
border-radius: 6px; text-decoration: none; font-weight: 500; font-size: 16px;
|
||||
transition: background 0.2s; box-shadow: 0 2px 4px rgba(74,144,226,0.3);"
|
||||
onmouseover="this.style.background=\'#357ABD\'"
|
||||
onmouseout="this.style.background=\'#4A90E2\'">
|
||||
Open Email Configuration →
|
||||
</a>
|
||||
</div>
|
||||
</div>';
|
||||
|
||||
return $out;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
<?php
|
||||
$labels['email_config'] = 'Email Configuration';
|
||||
$messages = array();
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
<?php
|
||||
$labels['email_config'] = 'Email Configuration';
|
||||
$messages = array();
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
#!/bin/bash
|
||||
# update-all-workers.sh (smart version)
|
||||
|
||||
DOMAINS=$(docker ps --filter "name=email-worker-" --format "{{.Names}}" | sed 's/email-worker-//')
|
||||
|
||||
if [ -z "$DOMAINS" ]; then
|
||||
echo "No workers found"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Found workers: $DOMAINS"
|
||||
echo ""
|
||||
|
||||
for domain in $DOMAINS; do
|
||||
echo "═══ $domain ═══"
|
||||
./manage-worker.sh "$domain" restart
|
||||
done
|
||||
|
||||
echo "✓ Done"
|
||||
53
worker.py
53
worker.py
|
|
@ -59,30 +59,45 @@ def is_ses_bounce_notification(parsed):
|
|||
return 'mailer-daemon@us-east-2.amazonses.com' in from_h
|
||||
|
||||
|
||||
def get_bounce_info_from_dynamodb(message_id):
|
||||
def get_bounce_info_from_dynamodb(message_id, max_retries=3, retry_delay=1):
|
||||
"""
|
||||
Sucht Bounce-Info in DynamoDB anhand der Message-ID
|
||||
Mit Retry-Logik für Timing-Issues
|
||||
Returns: dict mit bounce info oder None
|
||||
"""
|
||||
try:
|
||||
response = msg_table.get_item(Key={'MessageId': message_id})
|
||||
item = response.get('Item')
|
||||
|
||||
if not item:
|
||||
log(f"⚠ No bounce record found for Message-ID: {message_id}")
|
||||
return None
|
||||
|
||||
return {
|
||||
'original_source': item.get('original_source', ''),
|
||||
'bounceType': item.get('bounceType', 'Unknown'),
|
||||
'bounceSubType': item.get('bounceSubType', 'Unknown'),
|
||||
'bouncedRecipients': item.get('bouncedRecipients', []),
|
||||
'timestamp': item.get('timestamp', '')
|
||||
}
|
||||
import time
|
||||
|
||||
except Exception as e:
|
||||
log(f"⚠ DynamoDB Error: {e}", 'ERROR')
|
||||
return None
|
||||
for attempt in range(max_retries):
|
||||
try:
|
||||
response = msg_table.get_item(Key={'MessageId': message_id})
|
||||
item = response.get('Item')
|
||||
|
||||
if item:
|
||||
# Gefunden!
|
||||
return {
|
||||
'original_source': item.get('original_source', ''),
|
||||
'bounceType': item.get('bounceType', 'Unknown'),
|
||||
'bounceSubType': item.get('bounceSubType', 'Unknown'),
|
||||
'bouncedRecipients': item.get('bouncedRecipients', []),
|
||||
'timestamp': item.get('timestamp', '')
|
||||
}
|
||||
|
||||
# Nicht gefunden - Retry falls nicht letzter Versuch
|
||||
if attempt < max_retries - 1:
|
||||
log(f" Bounce record not found yet, retrying in {retry_delay}s (attempt {attempt + 1}/{max_retries})...")
|
||||
time.sleep(retry_delay)
|
||||
else:
|
||||
log(f"⚠ No bounce record found after {max_retries} attempts for Message-ID: {message_id}")
|
||||
return None
|
||||
|
||||
except Exception as e:
|
||||
log(f"⚠ DynamoDB Error (attempt {attempt + 1}/{max_retries}): {e}", 'ERROR')
|
||||
if attempt < max_retries - 1:
|
||||
time.sleep(retry_delay)
|
||||
else:
|
||||
return None
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def apply_bounce_logic(parsed, subject):
|
||||
|
|
|
|||
Loading…
Reference in New Issue