Bladeren bron

Updated Rules

Will Browning 1 jaar geleden
bovenliggende
commit
bcc2c4329d

+ 5 - 0
README.md

@@ -43,6 +43,7 @@ This is the source code for self-hosting addy.io.
 - [What is the max email size limit?](#what-is-the-max-email-size-limit)
 - [What happens if I have a subscription but then cancel it?](#what-happens-if-i-have-a-subscription-but-then-cancel-it)
 - [If I subscribe will Stripe see my real email address?](#if-i-subscribe-will-stripe-see-my-real-email-address)
+- [Do you offer student discount?](#do-you-offer-student-discount)
 - [How do you prevent spammers?](#how-do-you-prevent-spammers)
 - [What do you use to do DNS lookups on domain names?](#what-do-you-use-to-do-dns-lookups-on-domain-names)
 - [Is there a limit to how many emails I can forward?](#is-there-a-limit-to-how-many-emails-i-can-forward)
@@ -363,6 +364,10 @@ You will not be able to activate any of the above again until you resubscribe.
 
 When you subscribe you can choose which email to provide to Stripe, feel free to use an alias. This email will be used for notifications from Stripe such as; if your card payment fails or if your card has expired.
 
+## Do you offer student discount?
+
+Currently, addy.io does not offer any student discounts.
+
 ## How do you prevent spammers?
 
 The following is in place to help prevent spam:

+ 1 - 1
app/Console/Commands/EmailUsersWithTokenExpiringSoon.php

@@ -41,7 +41,7 @@ class EmailUsersWithTokenExpiringSoon extends Command
      */
     public function handle()
     {
-        User::with(['defaultUsername', 'defaultRecipient'])
+        User::with(['defaultUsername', 'defaultRecipient', 'tokens'])
             ->whereHas('tokens', function ($query) {
                 $query->whereDate('expires_at', now()->addWeek());
             })

+ 7 - 12
app/Console/Commands/ReceiveEmail.php

@@ -200,9 +200,9 @@ class ReceiveEmail extends Command
                     }
 
                     if ($this->parser->getHeader('In-Reply-To') && $alias) {
-                        $this->handleReply($user, $recipient, $alias);
+                        $this->handleReply($user, $alias, $validEmailDestination);
                     } else {
-                        $this->handleSendFrom($user, $recipient, $alias ?? null, $aliasable ?? null);
+                        $this->handleSendFrom($user, $recipient, $alias ?? null, $aliasable ?? null, $validEmailDestination);
                     }
                 } elseif ($verifiedRecipient?->can_reply_send === false) {
                     // Notify user that they have not allowed this recipient to reply and send from aliases
@@ -232,18 +232,16 @@ class ReceiveEmail extends Command
         }
     }
 
-    protected function handleReply($user, $recipient, $alias)
+    protected function handleReply($user, $alias, $destination)
     {
-        $sendTo = Str::replaceLast('=', '@', $recipient['extension']);
-
         $emailData = new EmailData($this->parser, $this->option('sender'), $this->size, 'R');
 
         $message = new ReplyToEmail($user, $alias, $emailData);
 
-        Mail::to($sendTo)->queue($message);
+        Mail::to($destination)->queue($message);
     }
 
-    protected function handleSendFrom($user, $recipient, $alias, $aliasable)
+    protected function handleSendFrom($user, $recipient, $alias, $aliasable, $destination)
     {
         if (is_null($alias)) {
             $alias = $user->aliases()->create([
@@ -258,13 +256,11 @@ class ReceiveEmail extends Command
             $alias->refresh();
         }
 
-        $sendTo = Str::replaceLast('=', '@', $recipient['extension']);
-
         $emailData = new EmailData($this->parser, $this->option('sender'), $this->size, 'S');
 
         $message = new SendFromEmail($user, $alias, $emailData);
 
-        Mail::to($sendTo)->queue($message);
+        Mail::to($destination)->queue($message);
     }
 
     protected function handleForward($user, $recipient, $alias, $aliasable, $isSpam)
@@ -470,8 +466,7 @@ class ReceiveEmail extends Command
             ->allow(config('anonaddy.limit'))
             ->every(3600)
             ->then(
-                function () {
-                },
+                function () {},
                 function () use ($user) {
                     $user->update(['defer_until' => now()->addHour()]);
 

+ 24 - 6
app/CustomMailDriver/CustomMailer.php

@@ -25,6 +25,8 @@ use Symfony\Component\Mime\Email;
 
 class CustomMailer extends Mailer
 {
+    private $data;
+
     /**
      * Send a new message using a view.
      *
@@ -34,6 +36,8 @@ class CustomMailer extends Mailer
      */
     public function send($view, array $data = [], $callback = null)
     {
+        $this->data = $data;
+
         if ($view instanceof MailableContract) {
             return $this->sendMailable($view);
         }
@@ -134,9 +138,9 @@ class CustomMailer extends Mailer
 
             // If the message is a forward, reply or send then use the verp domain
             if (isset($data['emailType']) && in_array($data['emailType'], ['F', 'R', 'S'])) {
-                $message->returnPath($verpLocalPart.'@'.$data['verpDomain']);
+                $symfonyMessage->returnPath($verpLocalPart.'@'.$data['verpDomain']);
             } else {
-                $message->returnPath($verpLocalPart.'@'.config('anonaddy.domain'));
+                $symfonyMessage->returnPath($verpLocalPart.'@'.config('anonaddy.domain'));
             }
 
             try {
@@ -246,10 +250,24 @@ class CustomMailer extends Mailer
     {
         try {
             $envelopeMessage = clone $message;
-            // This allows us to have the To: header set as the alias whilst still delivering to the correct RCPT TO.
-            if ($aliasTo = $message->getHeaders()->get('Alias-To')) {
-                $message->to($aliasTo->getValue());
-                $message->getHeaders()->remove('Alias-To');
+
+            // Add in original Tos that have been updated
+            if ($tos = $this->data['tos'] ?? null) {
+                foreach ($tos as $key => $to) {
+                    if ($key === 0) {
+                        // This allows us to have the To: header set as the alias whilst still delivering to the correct RCPT TO for forwards.
+                        $message->to($to); // In order to override recipient email for forwards
+                    } else {
+                        $message->addTo($to);
+                    }
+                }
+            }
+
+            // Add in original CCs that have been updated
+            if ($ccs = $this->data['ccs'] ?? null) {
+                foreach ($ccs as $cc) {
+                    $message->addCc($cc);
+                }
             }
 
             // Add the original sender header here to prevent it altering the envelope from address

+ 15 - 0
app/Helpers/Helper.php

@@ -1,6 +1,7 @@
 <?php
 
 use Illuminate\Support\Carbon;
+use Illuminate\Support\Str;
 
 function user()
 {
@@ -25,3 +26,17 @@ function randomString(int $length): string
 
     return $str;
 }
+
+function stripEmailExtension(string $email): string
+{
+    if (! Str::contains($email, '@')) {
+        return $email;
+    }
+
+    // Strip the email of extensions
+    [$localPart, $domain] = explode('@', strtolower($email));
+    // Remove plus extension from local part if present
+    $localPart = Str::contains($localPart, '+') ? Str::before($localPart, '+') : $localPart;
+
+    return $localPart.'@'.$domain;
+}

+ 1 - 0
app/Http/Controllers/ShowRuleController.php

@@ -22,6 +22,7 @@ class ShowRuleController extends Controller
                 })
                 ->orderBy('order')
                 ->get(),
+            'recipientOptions' => user()->verifiedRecipients()->select(['id', 'email'])->get(),
             'search' => $validated['search'] ?? null,
         ]);
     }

+ 14 - 5
app/Http/Requests/StoreRuleRequest.php

@@ -41,6 +41,7 @@ class StoreRuleRequest extends FormRequest
                     'subject',
                     'sender',
                     'alias',
+                    'alias_description',
                 ]),
             ],
             'conditions.*.match' => [
@@ -79,13 +80,21 @@ class StoreRuleRequest extends FormRequest
                     'encryption',
                     'banner',
                     'block',
-                    'webhook',
+                    'removeAttachments',
+                    'forwardTo',
+                    //'webhook',
                 ]),
             ],
-            'actions.*.value' => [
-                'required',
-                'max:50',
-            ],
+            'actions.*.value' => Rule::forEach(function ($value, $attribute, $data, $action) {
+                if ($action['type'] === 'forwardTo') {
+                    return [Rule::in(user()->verifiedRecipients()->pluck('id')->toArray())]; // Must be a valid verified recipient
+                }
+
+                return [
+                    'required',
+                    'max:50',
+                ];
+            }),
             'operator' => [
                 'required',
                 'in:AND,OR',

+ 67 - 4
app/Mail/ForwardEmail.php

@@ -33,6 +33,10 @@ class ForwardEmail extends Mailable implements ShouldBeEncrypted, ShouldQueue
 
     protected $sender;
 
+    protected $ccs;
+
+    protected $tos;
+
     protected $originalCc;
 
     protected $originalTo;
@@ -105,8 +109,69 @@ class ForwardEmail extends Mailable implements ShouldBeEncrypted, ShouldQueue
         $this->user = $alias->user;
         $this->alias = $alias;
         $this->sender = $emailData->sender;
+        $this->ccs = $emailData->ccs;
+        $this->tos = $emailData->tos;
         $this->originalCc = $emailData->originalCc ?? null;
         $this->originalTo = $emailData->originalTo ?? null;
+
+        // Create and swap with alias reply-to addresses to allow easy reply-all
+        if (count($this->ccs)) {
+            $this->ccs = collect($this->ccs)
+                ->map(function ($cc) {
+                    // Leave alias email Cc as it is
+                    if (stripEmailExtension($cc['address']) === $this->alias->email) {
+                        return [
+                            'display' => $cc['display'] != $cc['address'] ? $cc['display'] : null,
+                            'address' => $this->alias->email,
+                        ];
+                    }
+
+                    return [
+                        'display' => $cc['display'] != $cc['address'] ? $cc['display'] : null,
+                        'address' => $this->alias->local_part.'+'.Str::replaceLast('@', '=', $cc['address']).'@'.$this->alias->domain,
+                    ];
+                })
+                ->filter(fn ($cc) => filter_var($cc['address'], FILTER_VALIDATE_EMAIL))
+                ->map(function ($cc) {
+                    // Only add in display if it exists
+                    if ($cc['display']) {
+                        return $cc['display'].' <'.$cc['address'].'>';
+                    }
+
+                    return '<'.$cc['address'].'>';
+                })
+                ->toArray();
+        }
+
+        // Create and swap with alias reply-to addresses to allow easy reply-all
+        if (count($this->tos)) {
+            $this->tos = collect($this->tos)
+                ->map(function ($to) {
+                    // Leave alias email To as it is
+                    if (stripEmailExtension($to['address']) === $this->alias->email) {
+                        return [
+                            'display' => $to['display'] != $to['address'] ? $to['display'] : null,
+                            'address' => $this->alias->email,
+                        ];
+                    }
+
+                    return [
+                        'display' => $to['display'] != $to['address'] ? $to['display'] : null,
+                        'address' => $this->alias->local_part.'+'.Str::replaceLast('@', '=', $to['address']).'@'.$this->alias->domain,
+                    ];
+                })
+                ->filter(fn ($to) => filter_var($to['address'], FILTER_VALIDATE_EMAIL))
+                ->map(function ($to) {
+                    // Only add in display if it exists
+                    if ($to['display']) {
+                        return $to['display'].' <'.$to['address'].'>';
+                    }
+
+                    return '<'.$to['address'].'>';
+                })
+                ->toArray();
+        }
+
         $this->displayFrom = $emailData->display_from;
         $this->replyToAddress = $emailData->reply_to_address ?? $this->sender;
         $this->emailSubject = $emailData->subject;
@@ -173,10 +238,6 @@ class ForwardEmail extends Mailable implements ShouldBeEncrypted, ShouldQueue
                 $message->getHeaders()
                     ->addTextHeader('Feedback-ID', 'F:'.$this->alias->id.':anonaddy');
 
-                // This header is used to set the To: header as the alias just before sending.
-                $message->getHeaders()
-                    ->addTextHeader('Alias-To', $this->alias->email);
-
                 $message->getHeaders()->remove('Message-ID');
 
                 if ($this->messageId) {
@@ -327,6 +388,8 @@ class ForwardEmail extends Mailable implements ShouldBeEncrypted, ShouldQueue
             'shouldBlock' => $this->size === 0,
             'needsDkimSignature' => $this->needsDkimSignature(),
             'verpDomain' => $this->verpDomain ?? $this->alias->domain,
+            'ccs' => $this->ccs,
+            'tos' => $this->tos,
         ]);
 
         if (isset($replyToEmail)) {

+ 60 - 0
app/Mail/ReplyToEmail.php

@@ -31,6 +31,10 @@ class ReplyToEmail extends Mailable implements ShouldBeEncrypted, ShouldQueue
 
     protected $sender;
 
+    protected $ccs;
+
+    protected $tos;
+
     protected $emailSubject;
 
     protected $emailText;
@@ -65,6 +69,52 @@ class ReplyToEmail extends Mailable implements ShouldBeEncrypted, ShouldQueue
         $this->user = $user;
         $this->alias = $alias;
         $this->sender = $emailData->sender;
+
+        $this->ccs = $emailData->ccs;
+        $this->tos = $emailData->tos;
+
+        // Replace alias reply/send CCs back to proper emails
+        if (count($this->ccs)) {
+            $this->ccs = collect($this->ccs)
+                ->map(function ($cc) {
+                    return [
+                        'display' => null,
+                        'address' => Str::replaceLast('=', '@', Str::between($cc['address'], $this->alias->local_part.'+', '@'.$this->alias->domain)),
+                    ];
+                })
+                ->filter(fn ($cc) => filter_var($cc['address'], FILTER_VALIDATE_EMAIL))
+                ->map(function ($cc) {
+                    // Only add in display if it exists
+                    if ($cc['display']) {
+                        return $cc['display'].' <'.$cc['address'].'>';
+                    }
+
+                    return '<'.$cc['address'].'>';
+                })
+                ->toArray();
+        }
+
+        // Replace alias reply/send Tos back to proper emails
+        if (count($this->tos)) {
+            $this->tos = collect($this->tos)
+                ->map(function ($to) {
+                    return [
+                        'display' => null,
+                        'address' => Str::replaceLast('=', '@', Str::between($to['address'], $this->alias->local_part.'+', '@'.$this->alias->domain)),
+                    ];
+                })
+                ->filter(fn ($to) => filter_var($to['address'], FILTER_VALIDATE_EMAIL))
+                ->map(function ($to) {
+                    // Only add in display if it exists
+                    if ($to['display']) {
+                        return $to['display'].' <'.$to['address'].'>';
+                    }
+
+                    return '<'.$to['address'].'>';
+                })
+                ->toArray();
+        }
+
         $this->emailSubject = $emailData->subject;
         $this->emailText = $emailData->text;
         $this->emailHtml = $emailData->html;
@@ -168,6 +218,8 @@ class ReplyToEmail extends Mailable implements ShouldBeEncrypted, ShouldQueue
             'needsDkimSignature' => $this->needsDkimSignature(),
             'aliasDomain' => $this->alias->domain,
             'verpDomain' => $this->verpDomain ?? $this->alias->domain,
+            'ccs' => $this->ccs,
+            'tos' => $this->tos,
         ]);
 
         if ($this->alias->isCustomDomain() && ! $this->needsDkimSignature()) {
@@ -229,14 +281,22 @@ class ReplyToEmail extends Mailable implements ShouldBeEncrypted, ShouldQueue
 
     private function removeRealEmailAndTextBanner($text)
     {
+        // Replace <alias+hello=example.com@johndoe.anonaddy.com> with <hello@example.com>
+        $destination = $this->email->to[0]['address'];
+
         return Str::of(str_ireplace($this->sender, '', $text))
+            ->replace($this->alias->local_part.'+'.Str::replaceLast('@', '=', $destination).'@'.$this->alias->domain, $destination)
             ->replaceMatches('/(?s)((<|&lt;)!--banner-info--(&gt;|>)).*?((<|&lt;)!--banner-info--(&gt;|>))/mi', '');
     }
 
     private function removeRealEmailAndHtmlBanner($html)
     {
+        // Replace <alias+hello=example.com@johndoe.anonaddy.com> with <hello@example.com>
+        $destination = $this->email->to[0]['address'];
+
         // Reply may be HTML but have a plain text banner
         return Str::of(str_ireplace($this->sender, '', $html))
+            ->replace($this->alias->local_part.'+'.Str::replaceLast('@', '=', $destination).'@'.$this->alias->domain, $destination)
             ->replaceMatches('/(?s)((<|&lt;)!--banner-info--(&gt;|>)).*?((<|&lt;)!--banner-info--(&gt;|>))/mi', '')
             ->replaceMatches('/(?s)(<tr((?!<tr).)*?'.preg_quote(Str::of(config('app.url'))->after('://')->rtrim('/'), '/')."(\/|%2F)deactivate(\/|%2F).*?\/tr>)/mi", '');
     }

+ 52 - 0
app/Mail/SendFromEmail.php

@@ -31,6 +31,10 @@ class SendFromEmail extends Mailable implements ShouldBeEncrypted, ShouldQueue
 
     protected $sender;
 
+    protected $ccs;
+
+    protected $tos;
+
     protected $emailSubject;
 
     protected $emailText;
@@ -61,6 +65,52 @@ class SendFromEmail extends Mailable implements ShouldBeEncrypted, ShouldQueue
         $this->user = $user;
         $this->alias = $alias;
         $this->sender = $emailData->sender;
+
+        $this->ccs = $emailData->ccs;
+        $this->tos = $emailData->tos;
+
+        // Replace alias reply/send CCs back to proper emails
+        if (count($this->ccs)) {
+            $this->ccs = collect($this->ccs)
+                ->map(function ($cc) {
+                    return [
+                        'display' => null,
+                        'address' => Str::replaceLast('=', '@', Str::between($cc['address'], $this->alias->local_part.'+', '@'.$this->alias->domain)),
+                    ];
+                })
+                ->filter(fn ($cc) => filter_var($cc['address'], FILTER_VALIDATE_EMAIL))
+                ->map(function ($cc) {
+                    // Only add in display if it exists
+                    if ($cc['display']) {
+                        return $cc['display'].' <'.$cc['address'].'>';
+                    }
+
+                    return '<'.$cc['address'].'>';
+                })
+                ->toArray();
+        }
+
+        // Replace alias reply/send Tos back to proper emails
+        if (count($this->tos)) {
+            $this->tos = collect($this->tos)
+                ->map(function ($to) {
+                    return [
+                        'display' => null,
+                        'address' => Str::replaceLast('=', '@', Str::between($to['address'], $this->alias->local_part.'+', '@'.$this->alias->domain)),
+                    ];
+                })
+                ->filter(fn ($to) => filter_var($to['address'], FILTER_VALIDATE_EMAIL))
+                ->map(function ($to) {
+                    // Only add in display if it exists
+                    if ($to['display']) {
+                        return $to['display'].' <'.$to['address'].'>';
+                    }
+
+                    return '<'.$to['address'].'>';
+                })
+                ->toArray();
+        }
+
         $this->emailSubject = $emailData->subject;
         $this->emailText = $emailData->text;
         $this->emailHtml = $emailData->html;
@@ -152,6 +202,8 @@ class SendFromEmail extends Mailable implements ShouldBeEncrypted, ShouldQueue
             'needsDkimSignature' => $this->needsDkimSignature(),
             'aliasDomain' => $this->alias->domain,
             'verpDomain' => $this->verpDomain ?? $this->alias->domain,
+            'ccs' => $this->ccs,
+            'tos' => $this->tos,
         ]);
 
         if ($this->alias->isCustomDomain() && ! $this->needsDkimSignature()) {

+ 4 - 0
app/Mail/TokenExpiringSoon.php

@@ -19,6 +19,8 @@ class TokenExpiringSoon extends Mailable implements ShouldBeEncrypted, ShouldQue
 
     protected $recipient;
 
+    protected $token;
+
     /**
      * Create a new message instance.
      *
@@ -28,6 +30,7 @@ class TokenExpiringSoon extends Mailable implements ShouldBeEncrypted, ShouldQue
     {
         $this->user = $user;
         $this->recipient = $user->defaultRecipient;
+        $this->token = $user->tokens()->whereDate('expires_at', now()->addWeek())->first();
     }
 
     /**
@@ -45,6 +48,7 @@ class TokenExpiringSoon extends Mailable implements ShouldBeEncrypted, ShouldQue
                 'recipientId' => $this->user->default_recipient_id,
                 'emailType' => 'TES',
                 'fingerprint' => $this->recipient->should_encrypt ? $this->recipient->fingerprint : null,
+                'tokenName' => $this->token?->name,
             ])
             ->withSymfonyMessage(function (Email $message) {
                 $message->getHeaders()

+ 7 - 0
app/Models/EmailData.php

@@ -16,6 +16,10 @@ class EmailData
 
     public $reply_to_address;
 
+    public $ccs;
+
+    public $tos;
+
     public $originalCc;
 
     public $originalTo;
@@ -81,6 +85,9 @@ class EmailData
             }
         }
 
+        $this->ccs = collect($parser->getAddresses('cc'))->all();
+        $this->tos = collect($parser->getAddresses('to'))->all();
+
         if ($originalCc = $parser->getHeader('cc')) {
             $this->originalCc = $originalCc;
         }

+ 26 - 2
app/Traits/CheckUserRules.php

@@ -6,8 +6,12 @@ use Illuminate\Support\Str;
 
 trait CheckUserRules
 {
+    protected $emailType;
+
     public function checkRules(string $emailType)
     {
+        $this->emailType = $emailType;
+
         $method = "activeRulesFor{$emailType}Ordered";
         $this->user->{$method}->each(function ($rule) {
             // Check if the conditions of the rule are satisfied
@@ -53,8 +57,8 @@ trait CheckUserRules
             case 'alias':
                 return $this->conditionSatisfied($this->alias->email, $condition);
                 break;
-            case 'displayFrom':
-                return $this->conditionSatisfied($this->displayFrom, $condition);
+            case 'alias_description':
+                return $this->conditionSatisfied($this->alias->description, $condition);
                 break;
         }
     }
@@ -143,6 +147,26 @@ trait CheckUserRules
                 $this->size = 0;
                 exit(0);
                 break;
+            case 'removeAttachments':
+                $this->email->rawAttachments = [];
+                break;
+            case 'forwardTo':
+                // Only apply on forwards
+                if ($this->emailType !== 'Forwards') {
+                    break;
+                }
+
+                $recipient = $this->user->verifiedRecipients()->select(['id', 'email', 'should_encrypt', 'fingerprint'])->find($action['value']);
+
+                if (! $recipient) {
+                    break;
+                }
+
+                $this->recipientId = $recipient->id;
+                $this->fingerprint = $recipient->should_encrypt && ! $this->isAlreadyEncrypted() ? $recipient->fingerprint : null;
+
+                $this->email->to[0]['address'] = $recipient->email;
+                break;
             case 'webhook':
                 // http payload to url
                 break;

File diff suppressed because it is too large
+ 163 - 210
composer.lock


File diff suppressed because it is too large
+ 274 - 261
package-lock.json


+ 68 - 69
postfix/composer.lock

@@ -290,16 +290,16 @@
         },
         {
             "name": "illuminate/collections",
-            "version": "v11.8.0",
+            "version": "v11.12.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/illuminate/collections.git",
-                "reference": "dad22e648ae0f4973470d82b4ae5f809540a87ff"
+                "reference": "6923dc8c83b8c065c3b89cb4c2a4b10a4288ce79"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/illuminate/collections/zipball/dad22e648ae0f4973470d82b4ae5f809540a87ff",
-                "reference": "dad22e648ae0f4973470d82b4ae5f809540a87ff",
+                "url": "https://api.github.com/repos/illuminate/collections/zipball/6923dc8c83b8c065c3b89cb4c2a4b10a4288ce79",
+                "reference": "6923dc8c83b8c065c3b89cb4c2a4b10a4288ce79",
                 "shasum": ""
             },
             "require": {
@@ -341,11 +341,11 @@
                 "issues": "https://github.com/laravel/framework/issues",
                 "source": "https://github.com/laravel/framework"
             },
-            "time": "2024-05-20T13:26:28+00:00"
+            "time": "2024-06-21T15:52:51+00:00"
         },
         {
             "name": "illuminate/conditionable",
-            "version": "v11.8.0",
+            "version": "v11.12.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/illuminate/conditionable.git",
@@ -391,7 +391,7 @@
         },
         {
             "name": "illuminate/container",
-            "version": "v11.8.0",
+            "version": "v11.12.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/illuminate/container.git",
@@ -442,16 +442,16 @@
         },
         {
             "name": "illuminate/contracts",
-            "version": "v11.8.0",
+            "version": "v11.12.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/illuminate/contracts.git",
-                "reference": "8782f75e80ab3e6036842d24dbeead34a16f3a79"
+                "reference": "86c1331d0b06c59ca21723d8bfc9faaa19430b46"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/illuminate/contracts/zipball/8782f75e80ab3e6036842d24dbeead34a16f3a79",
-                "reference": "8782f75e80ab3e6036842d24dbeead34a16f3a79",
+                "url": "https://api.github.com/repos/illuminate/contracts/zipball/86c1331d0b06c59ca21723d8bfc9faaa19430b46",
+                "reference": "86c1331d0b06c59ca21723d8bfc9faaa19430b46",
                 "shasum": ""
             },
             "require": {
@@ -486,20 +486,20 @@
                 "issues": "https://github.com/laravel/framework/issues",
                 "source": "https://github.com/laravel/framework"
             },
-            "time": "2024-04-17T14:09:55+00:00"
+            "time": "2024-05-21T17:42:34+00:00"
         },
         {
             "name": "illuminate/database",
-            "version": "v11.8.0",
+            "version": "v11.12.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/illuminate/database.git",
-                "reference": "a4e73c5ad7678d5ec934374e8522bf62a4d75d99"
+                "reference": "d2bd095eab3d7a1e869b5824bcada7492e447ec7"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/illuminate/database/zipball/a4e73c5ad7678d5ec934374e8522bf62a4d75d99",
-                "reference": "a4e73c5ad7678d5ec934374e8522bf62a4d75d99",
+                "url": "https://api.github.com/repos/illuminate/database/zipball/d2bd095eab3d7a1e869b5824bcada7492e447ec7",
+                "reference": "d2bd095eab3d7a1e869b5824bcada7492e447ec7",
                 "shasum": ""
             },
             "require": {
@@ -554,11 +554,11 @@
                 "issues": "https://github.com/laravel/framework/issues",
                 "source": "https://github.com/laravel/framework"
             },
-            "time": "2024-05-21T15:24:23+00:00"
+            "time": "2024-06-24T20:24:42+00:00"
         },
         {
             "name": "illuminate/macroable",
-            "version": "v11.8.0",
+            "version": "v11.12.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/illuminate/macroable.git",
@@ -604,16 +604,16 @@
         },
         {
             "name": "illuminate/support",
-            "version": "v11.8.0",
+            "version": "v11.12.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/illuminate/support.git",
-                "reference": "8deb8ba65ed7dc4e3f7b9b64ab70456250454824"
+                "reference": "1ea237b71cd2e181af36074e2a9dd47f268e5cda"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/illuminate/support/zipball/8deb8ba65ed7dc4e3f7b9b64ab70456250454824",
-                "reference": "8deb8ba65ed7dc4e3f7b9b64ab70456250454824",
+                "url": "https://api.github.com/repos/illuminate/support/zipball/1ea237b71cd2e181af36074e2a9dd47f268e5cda",
+                "reference": "1ea237b71cd2e181af36074e2a9dd47f268e5cda",
                 "shasum": ""
             },
             "require": {
@@ -674,20 +674,20 @@
                 "issues": "https://github.com/laravel/framework/issues",
                 "source": "https://github.com/laravel/framework"
             },
-            "time": "2024-05-21T15:24:23+00:00"
+            "time": "2024-06-24T20:24:42+00:00"
         },
         {
             "name": "nesbot/carbon",
-            "version": "3.4.0",
+            "version": "3.6.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/briannesbitt/Carbon.git",
-                "reference": "8eab8983c83c30e0bacbef8d311e3f3b8172727f"
+                "reference": "39c8ef752db6865717cc3fba63970c16f057982c"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/8eab8983c83c30e0bacbef8d311e3f3b8172727f",
-                "reference": "8eab8983c83c30e0bacbef8d311e3f3b8172727f",
+                "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/39c8ef752db6865717cc3fba63970c16f057982c",
+                "reference": "39c8ef752db6865717cc3fba63970c16f057982c",
                 "shasum": ""
             },
             "require": {
@@ -705,13 +705,13 @@
             "require-dev": {
                 "doctrine/dbal": "^3.6.3 || ^4.0",
                 "doctrine/orm": "^2.15.2 || ^3.0",
-                "friendsofphp/php-cs-fixer": "^3.52.1",
+                "friendsofphp/php-cs-fixer": "^3.57.2",
                 "kylekatarnls/multi-tester": "^2.5.3",
                 "ondrejmirtes/better-reflection": "^6.25.0.4",
                 "phpmd/phpmd": "^2.15.0",
                 "phpstan/extension-installer": "^1.3.1",
-                "phpstan/phpstan": "^1.10.65",
-                "phpunit/phpunit": "^10.5.15",
+                "phpstan/phpstan": "^1.11.2",
+                "phpunit/phpunit": "^10.5.20",
                 "squizlabs/php_codesniffer": "^3.9.0"
             },
             "bin": [
@@ -780,7 +780,7 @@
                     "type": "tidelift"
                 }
             ],
-            "time": "2024-05-24T14:26:34+00:00"
+            "time": "2024-06-20T15:52:59+00:00"
         },
         {
             "name": "paragonie/constant_time_encoding",
@@ -1078,16 +1078,16 @@
         },
         {
             "name": "symfony/clock",
-            "version": "v7.0.7",
+            "version": "v7.1.1",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/clock.git",
-                "reference": "2008671acb4a30b01c453de193cf9c80549ebda6"
+                "reference": "3dfc8b084853586de51dd1441c6242c76a28cbe7"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/clock/zipball/2008671acb4a30b01c453de193cf9c80549ebda6",
-                "reference": "2008671acb4a30b01c453de193cf9c80549ebda6",
+                "url": "https://api.github.com/repos/symfony/clock/zipball/3dfc8b084853586de51dd1441c6242c76a28cbe7",
+                "reference": "3dfc8b084853586de51dd1441c6242c76a28cbe7",
                 "shasum": ""
             },
             "require": {
@@ -1132,7 +1132,7 @@
                 "time"
             ],
             "support": {
-                "source": "https://github.com/symfony/clock/tree/v7.0.7"
+                "source": "https://github.com/symfony/clock/tree/v7.1.1"
             },
             "funding": [
                 {
@@ -1148,20 +1148,20 @@
                     "type": "tidelift"
                 }
             ],
-            "time": "2024-04-18T09:29:19+00:00"
+            "time": "2024-05-31T14:57:53+00:00"
         },
         {
             "name": "symfony/polyfill-ctype",
-            "version": "v1.29.0",
+            "version": "v1.30.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/polyfill-ctype.git",
-                "reference": "ef4d7e442ca910c4764bce785146269b30cb5fc4"
+                "reference": "0424dff1c58f028c451efff2045f5d92410bd540"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/ef4d7e442ca910c4764bce785146269b30cb5fc4",
-                "reference": "ef4d7e442ca910c4764bce785146269b30cb5fc4",
+                "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/0424dff1c58f028c451efff2045f5d92410bd540",
+                "reference": "0424dff1c58f028c451efff2045f5d92410bd540",
                 "shasum": ""
             },
             "require": {
@@ -1211,7 +1211,7 @@
                 "portable"
             ],
             "support": {
-                "source": "https://github.com/symfony/polyfill-ctype/tree/v1.29.0"
+                "source": "https://github.com/symfony/polyfill-ctype/tree/v1.30.0"
             },
             "funding": [
                 {
@@ -1227,20 +1227,20 @@
                     "type": "tidelift"
                 }
             ],
-            "time": "2024-01-29T20:11:03+00:00"
+            "time": "2024-05-31T15:07:36+00:00"
         },
         {
             "name": "symfony/polyfill-mbstring",
-            "version": "v1.29.0",
+            "version": "v1.30.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/polyfill-mbstring.git",
-                "reference": "9773676c8a1bb1f8d4340a62efe641cf76eda7ec"
+                "reference": "fd22ab50000ef01661e2a31d850ebaa297f8e03c"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/9773676c8a1bb1f8d4340a62efe641cf76eda7ec",
-                "reference": "9773676c8a1bb1f8d4340a62efe641cf76eda7ec",
+                "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/fd22ab50000ef01661e2a31d850ebaa297f8e03c",
+                "reference": "fd22ab50000ef01661e2a31d850ebaa297f8e03c",
                 "shasum": ""
             },
             "require": {
@@ -1291,7 +1291,7 @@
                 "shim"
             ],
             "support": {
-                "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.29.0"
+                "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.30.0"
             },
             "funding": [
                 {
@@ -1307,20 +1307,20 @@
                     "type": "tidelift"
                 }
             ],
-            "time": "2024-01-29T20:11:03+00:00"
+            "time": "2024-06-19T12:30:46+00:00"
         },
         {
             "name": "symfony/polyfill-php80",
-            "version": "v1.29.0",
+            "version": "v1.30.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/polyfill-php80.git",
-                "reference": "87b68208d5c1188808dd7839ee1e6c8ec3b02f1b"
+                "reference": "77fa7995ac1b21ab60769b7323d600a991a90433"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/87b68208d5c1188808dd7839ee1e6c8ec3b02f1b",
-                "reference": "87b68208d5c1188808dd7839ee1e6c8ec3b02f1b",
+                "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/77fa7995ac1b21ab60769b7323d600a991a90433",
+                "reference": "77fa7995ac1b21ab60769b7323d600a991a90433",
                 "shasum": ""
             },
             "require": {
@@ -1371,7 +1371,7 @@
                 "shim"
             ],
             "support": {
-                "source": "https://github.com/symfony/polyfill-php80/tree/v1.29.0"
+                "source": "https://github.com/symfony/polyfill-php80/tree/v1.30.0"
             },
             "funding": [
                 {
@@ -1387,25 +1387,24 @@
                     "type": "tidelift"
                 }
             ],
-            "time": "2024-01-29T20:11:03+00:00"
+            "time": "2024-05-31T15:07:36+00:00"
         },
         {
             "name": "symfony/polyfill-php83",
-            "version": "v1.29.0",
+            "version": "v1.30.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/polyfill-php83.git",
-                "reference": "86fcae159633351e5fd145d1c47de6c528f8caff"
+                "reference": "dbdcdf1a4dcc2743591f1079d0c35ab1e2dcbbc9"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/86fcae159633351e5fd145d1c47de6c528f8caff",
-                "reference": "86fcae159633351e5fd145d1c47de6c528f8caff",
+                "url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/dbdcdf1a4dcc2743591f1079d0c35ab1e2dcbbc9",
+                "reference": "dbdcdf1a4dcc2743591f1079d0c35ab1e2dcbbc9",
                 "shasum": ""
             },
             "require": {
-                "php": ">=7.1",
-                "symfony/polyfill-php80": "^1.14"
+                "php": ">=7.1"
             },
             "type": "library",
             "extra": {
@@ -1448,7 +1447,7 @@
                 "shim"
             ],
             "support": {
-                "source": "https://github.com/symfony/polyfill-php83/tree/v1.29.0"
+                "source": "https://github.com/symfony/polyfill-php83/tree/v1.30.0"
             },
             "funding": [
                 {
@@ -1464,20 +1463,20 @@
                     "type": "tidelift"
                 }
             ],
-            "time": "2024-01-29T20:11:03+00:00"
+            "time": "2024-06-19T12:35:24+00:00"
         },
         {
             "name": "symfony/translation",
-            "version": "v7.0.7",
+            "version": "v7.1.1",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/translation.git",
-                "reference": "1515e03afaa93e6419aba5d5c9d209159317100b"
+                "reference": "cf5ae136e124fc7681b34ce9fac9d5b9ae8ceee3"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/translation/zipball/1515e03afaa93e6419aba5d5c9d209159317100b",
-                "reference": "1515e03afaa93e6419aba5d5c9d209159317100b",
+                "url": "https://api.github.com/repos/symfony/translation/zipball/cf5ae136e124fc7681b34ce9fac9d5b9ae8ceee3",
+                "reference": "cf5ae136e124fc7681b34ce9fac9d5b9ae8ceee3",
                 "shasum": ""
             },
             "require": {
@@ -1542,7 +1541,7 @@
             "description": "Provides tools to internationalize your application",
             "homepage": "https://symfony.com",
             "support": {
-                "source": "https://github.com/symfony/translation/tree/v7.0.7"
+                "source": "https://github.com/symfony/translation/tree/v7.1.1"
             },
             "funding": [
                 {
@@ -1558,7 +1557,7 @@
                     "type": "tidelift"
                 }
             ],
-            "time": "2024-04-18T09:29:19+00:00"
+            "time": "2024-05-31T14:57:53+00:00"
         },
         {
             "name": "symfony/translation-contracts",

+ 65 - 8
resources/js/Pages/Rules.vue

@@ -159,7 +159,7 @@
     <Modal
       :open="createRuleModalOpen"
       @close="createRuleModalOpen = false"
-      max-width="md:max-w-2xl"
+      max-width="md:max-w-3xl"
     >
       <template v-slot:title> Create new rule </template>
       <template v-slot:content>
@@ -209,7 +209,7 @@
                 <div
                   class="w-full flex flex-col sm:flex-row sm:items-center space-y-2 sm:space-y-0"
                 >
-                  <span>If</span>
+                  <span>If the</span>
                   <span class="sm:ml-2">
                     <div class="relative">
                       <select
@@ -370,6 +370,23 @@
                     </div>
                   </span>
 
+                  <multiselect
+                    v-if="createRuleObject.actions[key].type === 'forwardTo'"
+                    class="sm:!ml-4 flex"
+                    v-model="createRuleObject.actions[key].value"
+                    :options="recipientOptions"
+                    mode="single"
+                    value-prop="id"
+                    :close-on-select="true"
+                    :clear-on-select="false"
+                    :searchable="false"
+                    :allow-empty="true"
+                    placeholder="Select recipient"
+                    label="email"
+                    track-by="email"
+                  >
+                  </multiselect>
+
                   <span
                     v-else-if="createRuleObject.actions[key].type === 'banner'"
                     class="sm:ml-4 flex"
@@ -468,7 +485,7 @@
       </template>
     </Modal>
 
-    <Modal :open="editRuleModalOpen" @close="closeEditModal" max-width="md:max-w-2xl">
+    <Modal :open="editRuleModalOpen" @close="closeEditModal" max-width="md:max-w-3xl">
       <template v-slot:title> Edit rule </template>
       <template v-slot:content>
         <p class="mt-4 text-grey-700">
@@ -517,7 +534,7 @@
                 <div
                   class="w-full flex flex-col sm:flex-row sm:items-center space-y-2 sm:space-y-0"
                 >
-                  <span>If</span>
+                  <span>If the</span>
                   <span class="sm:ml-2">
                     <div class="relative">
                       <select
@@ -675,6 +692,23 @@
                     </div>
                   </span>
 
+                  <multiselect
+                    v-if="editRuleObject.actions[key].type === 'forwardTo'"
+                    class="sm:!ml-4 flex"
+                    v-model="editRuleObject.actions[key].value"
+                    :options="recipientOptions"
+                    mode="single"
+                    value-prop="id"
+                    :close-on-select="true"
+                    :clear-on-select="false"
+                    :searchable="false"
+                    :allow-empty="true"
+                    placeholder="Select recipient"
+                    label="email"
+                    track-by="email"
+                  >
+                  </multiselect>
+
                   <span
                     v-else-if="editRuleObject.actions[key].type === 'banner'"
                     class="sm:ml-4 flex"
@@ -836,6 +870,7 @@ import Toggle from '../Components/Toggle.vue'
 import { roundArrow } from 'tippy.js'
 import tippy from 'tippy.js'
 import draggable from 'vuedraggable'
+import Multiselect from '@vueform/multiselect'
 import { notify } from '@kyvg/vue3-notification'
 import { InformationCircleIcon, FunnelIcon } from '@heroicons/vue/24/outline'
 import { PlusIcon } from '@heroicons/vue/20/solid'
@@ -845,6 +880,10 @@ const props = defineProps({
     type: Array,
     required: true,
   },
+  recipientOptions: {
+    type: Array,
+    required: true,
+  },
   search: {
     type: String,
   },
@@ -891,15 +930,19 @@ const conditionTypeOptions = [
   },
   {
     value: 'sender',
-    label: 'the sender',
+    label: 'sender email',
   },
   {
     value: 'subject',
-    label: 'the subject',
+    label: 'subject',
   },
   {
     value: 'alias',
-    label: 'the alias',
+    label: 'alias email',
+  },
+  {
+    value: 'alias_description',
+    label: 'alias description',
   },
 ]
 const actionTypeOptions = [
@@ -927,6 +970,14 @@ const actionTypeOptions = [
     value: 'block',
     label: 'block the email',
   },
+  {
+    value: 'removeAttachments',
+    label: 'remove attachments',
+  },
+  {
+    value: 'forwardTo',
+    label: 'forward to',
+  },
 ]
 
 const indexToHuman = {
@@ -1188,7 +1239,9 @@ const reorderRules = (displaySuccess = true) => {
 }
 
 const conditionMatchOptions = (object, key) => {
-  if (_.includes(['sender', 'subject', 'alias'], object.conditions[key].type)) {
+  if (
+    _.includes(['sender', 'subject', 'alias', 'alias_description'], object.conditions[key].type)
+  ) {
     return [
       'contains',
       'does not contain',
@@ -1280,6 +1333,10 @@ const ruleActionChange = action => {
     action.value = 'top'
   } else if (action.type === 'block') {
     action.value = true
+  } else if (action.type === 'removeAttachments') {
+    action.value = true
+  } else if (action.type === 'forwardTo') {
+    action.value = ''
   }
 }
 

+ 4 - 0
resources/views/mail/token_expiring_soon.blade.php

@@ -2,7 +2,11 @@
 
 # Your API key expires soon
 
+@if($tokenName)
+Your API key named "**{{ $tokenName }}**" on your addy.io account expires in **one weeks time**.
+@else
 One of the API keys on your addy.io account will expire in **one weeks time**.
+@endif
 
 If you are not using this API key for the browser extensions, mobile apps or to access the API then you do not need to take any action.
 

+ 1 - 0
tests/TestCase.php

@@ -13,6 +13,7 @@ use Ramsey\Uuid\Uuid;
 abstract class TestCase extends BaseTestCase
 {
     protected $user;
+
     protected $original;
 
     protected function setUp(): void

+ 120 - 0
tests/emails/email_windows-1250.eml

@@ -0,0 +1,120 @@
+Date: Wed, 20 Feb 2019 15:00:00 +0100 (CET)
+From: Will <will@anonaddy.com>
+To: <ebay@johndoe.anonaddy.com>
+Subject: Test Email
+Message-Id: <10736680l.3259911109l1696204l2095l@intime.sk>
+MIME-Version: 1.0
+Content-Type: multipart/alternative; boundary="--boundary.ipw.202312219143836.3659651272.2094"
+
+
+This is a message encoded in MIME format.
+
+----boundary.ipw.202312219143836.3659651272.2094
+Content-Type: text/plain; charset="WINDOWS-1250"
+Content-Transfer-Encoding: quoted-printable
+
+body { font-family: Tahoma, Verdana, Arial, Helvetica, Chicago, Sans-serif;=
+ font-size: 12px; margin: 0px; padding: 4px; }p { text-align: justify }tr, =
+td { font-size: 12px; }h1 {color: #ffffff; font-size: 14px; background: #45=
+5560; padding: 4px 5px; border-left: 20px solid #f89829; }.obsah { width: 6=
+60px; }.obsah p { margin: 12px; }.obsah h5 {margin: 12px; font-size: 12px; =
+}Odoslanie z=E1sielky: TRACKING-NUMBER-HERE
+
+  =20
+
+
+V=E1=9Een=FD z=E1kazn=EDk,
+
+
+Dnes bola spracovan=E1 Va=9Aa z=E1sielka od spolo=E8nosti CAINIAO - .
+Z=E1sielka
+
+
+  =C8=EDslo z=E1sielky:  TRACKING-NUMBER-HERE
+  Referen=E8n=E9 =E8=EDslo:  7REF NUMBER HERE
+  Dodacia adresa:  ADDRESS-HERE
+  Dobierkov=E1 suma:  0,00 EUR=20
+    =20
+
+Doru=E8enie
+
+Z=E1sielky obvykle doru=E8ujeme nasleduj=FAci pracovn=FD de=F2.
+Pre bli=9E=9Aie inform=E1cie o doru=E8en=ED tejto z=E1sielky pou=9Eite odka=
+z Kde je m=F4j bal=EDk.=20
+
+Prev=E1dzkovate=BEom je spolo=E8nos=9D Express One, s.r.o., I=C8O: 31 342 6=
+21 (=EFalej len ako Prev=E1dzkovate=BE).
+Va=9Ae osobn=E9 =FAdaje sprac=FAva Prev=E1dzkovate=BE za =FA=E8elom zabezpe=
+=E8enia doru=E8enia z=E1sielky. Prev=E1dzkovate=BEa m=F4=9Eete kontaktova=
+=9D na adrese Seneck=E1 cesta 1, 900 28 Ivanka pri Dunaji. Zodpovedn=FA oso=
+bu m=F4=9Eete kontaktova=9D na adrese Prev=E1dzkovate=BEa.
+Bli=9E=9Aie inform=E1cie o sprac=FAvan=ED Va=9Aich osobn=FDch =FAdajov o pr=
+=E1vach dotknutej osoby a z=E1sad=E1ch ochrany osobn=FDch =FAdajov n=E1jdet=
+e na na=9Aej internetovej str=E1nke: https://www.expressone.sk/sk/kontakt/i=
+nformacie-o-spracuvani-osobnych-udajov.html.=20
+
+S pozdravom,
+kuri=E9rska spolo=E8nos=9D
+
+
+T=E1to spr=E1va je generovan=E1 na z=E1klade po=9Eiadavky odosielate=BEa z=
+=E1sielky.
+
+Pros=EDm, neodpovedajte na doru=E8en=FA spr=E1vu e-mailom, kontakt pre =EFa=
+l=9Aie inform=E1cie je uveden=FD vy=9A=9Aie.
+----boundary.ipw.202312219143836.3659651272.2094
+Content-Type: text/html; charset="WINDOWS-1250"
+Content-Transfer-Encoding: quoted-printable
+
+<html><head><meta http-equiv=3D"Content-Type" content=3D"text/html;" charse=
+t=3D"Windows-1250"><style type=3D"text/css">body { font-family: Tahoma, Ver=
+dana, Arial, Helvetica, Chicago, Sans-serif; font-size: 12px; margin: 0px; =
+padding: 4px; }p { text-align: justify }tr, td { font-size: 12px; }h1 {colo=
+r: #ffffff; font-size: 14px; background: #455560; padding: 4px 5px; border-=
+left: 20px solid #f89829; }.obsah { width: 660px; }.obsah p { margin: 12px;=
+ }.obsah h5 {margin: 12px; font-size: 12px; }</style><title>Odoslanie z=E1s=
+ielky: TRACKING NUMBER HERE</title></head><body text=3D"#000000" bgcolor=3D"=
+#ffffff"><div class=3D"obsah"><table width=3D"100%" bgcolor=3D"#f89829"><tb=
+ody><tr><td>&nbsp;<br></td></tr></tbody></table><p>V=E1=9Een=FD z=E1kazn=ED=
+k,<br></p><p>Dnes bola spracovan=E1 Va=9Aa z=E1sielka od spolo=E8nosti CAIN=
+IAO - .</p><h1>Z=E1sielka</h1><p><table border=3D"0"><tbody><tr><td>=C8=EDs=
+lo z=E1sielky:</td><td><b><a href=3D"http://t-t.sps-sro.sk/result.php?cmd=
+=3DSDG_SEARCH&amp;sprache=3D&amp;sdg_landnr=3D703&amp;sdg_mandnr=3D310&amp;=
+sdg_lfdnr=3trackingnumber&amp;x=3D1&amp;y=3D1">TRACKING-NUMBER-HERE</a></b></td><=
+/tr><tr><td>Referen=E8n=E9 =E8=EDslo:</td><td>7365776804539</td></tr><tr><t=
+d>Dodacia adresa:</td><td>My Street Address redacted</t=
+d></tr><tr><td>Dobierkov=E1 suma:</td><td>0,00 EUR <i></i></td></tr><tr><td=
+>&nbsp;</td><td><br></td></tr></tbody></table></p><h1>Doru=E8enie</h1><p>Z=
+=E1sielky obvykle doru=E8ujeme nasleduj=FAci pracovn=FD de=F2.<br>
+
+Pre bli=9E=9Aie inform=E1cie o doru=E8en=ED tejto z=E1sielky pou=9Eite odka=
+z <a href=3D"https://www.expressone.sk/sk/moja-zasielka/kde-je-moj-balik-.h=
+tml"> Kde je m=F4j bal=EDk</a>.</p>
+
+<p><small><i>
+Prev=E1dzkovate=BEom je spolo=E8nos=9D <b>Express One, s.r.o.</b>, I=C8O: 3=
+1 342 621 (=EFalej len ako Prev=E1dzkovate=BE).<br>
+Va=9Ae osobn=E9 =FAdaje sprac=FAva Prev=E1dzkovate=BE <b>za =FA=E8elom zabe=
+zpe=E8enia doru=E8enia z=E1sielky</b>. Prev=E1dzkovate=BEa m=F4=9Eete konta=
+ktova=9D na adrese Seneck=E1 cesta 1, 900 28 Ivanka pri Dunaji<!-- alebo e-=
+mailom <a href=3Dmailto:info@expressone.sk?Subject=3DInforma=E8n=E1 povinno=
+s=9D prev=E1dzkovate=BEa target=3D_top>info@expressone.sk</a>-->.
+ Zodpovedn=FA osobu m=F4=9Eete kontaktova=9D na adrese Prev=E1dzkovate=BEa<=
+!-- alebo prostredn=EDctvom e-mailu <a href=3Dmailto:zodpovednaosoba@expres=
+sone.sk?Subject=3DInforma=E8n=E1 povinnos=9D prev=E1dzkovate=BEa target=3D_=
+top>zodpovednaosoba@expressone.sk</a>-->.<br>
+Bli=9E=9Aie inform=E1cie o sprac=FAvan=ED Va=9Aich osobn=FDch =FAdajov o pr=
+=E1vach dotknutej osoby a z=E1sad=E1ch ochrany osobn=FDch =FAdajov n=E1jdet=
+e na na=9Aej internetovej str=E1nke: <a href=3D"https://www.expressone.sk/s=
+k/kontakt/informacie-o-spracuvani-osobnych-udajov.html">https://www.express=
+one.sk/sk/kontakt/informacie-o-spracuvani-osobnych-udajov.html</a>.
+</i></small></p>
+
+<p>S pozdravom,<br>kuri=E9rska spolo=E8nos=9D<br><a href=3D"https://www.exp=
+ressone.sk"><img alt=3D"Express" One src=3D"https://t-t.sps-sro.sk/logo/exp=
+_one.png" height=3D"65" width=3D"225" hspace=3D"20" vspace=3D"20" border=3D=
+"0"></a></p><p><i><small>T=E1to spr=E1va je generovan=E1 na z=E1klade po=9E=
+iadavky odosielate=BEa z=E1sielky.<br><br>Pros=EDm, neodpovedajte na doru=
+=E8en=FA spr=E1vu e-mailom, kontakt pre =EFal=9Aie inform=E1cie je uveden=
+=FD vy=9A=9Aie.</small></i></p></div></body></html>
+----boundary.ipw.202312219143836.3659651272.2094--

Some files were not shown because too many files changed in this diff