Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix email notification reply to #603

Merged
merged 1 commit into from
Oct 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 10 additions & 32 deletions api/app/Notifications/Forms/FormEmailNotification.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ class FormEmailNotification extends Notification implements ShouldQueue

public FormSubmitted $event;
public string $mailer;
private array $formattedData;

/**
* Create a new notification instance.
Expand All @@ -30,7 +29,6 @@ public function __construct(FormSubmitted $event, private $integrationData, stri
{
$this->event = $event;
$this->mailer = $mailer;
$this->formattedData = $this->formatSubmissionData();
}

/**
Expand All @@ -54,7 +52,7 @@ public function toMail($notifiable)
{
return (new MailMessage())
->mailer($this->mailer)
->replyTo($this->getReplyToEmail($notifiable->routes['mail']))
->replyTo($this->getReplyToEmail($this->event->form->creator->email))
->from($this->getFromEmail(), $this->getSenderName())
->subject($this->getSubject())
->withSymfonyMessage(function (Email $message) {
Expand All @@ -63,13 +61,15 @@ public function toMail($notifiable)
->markdown('mail.form.email-notification', $this->getMailData());
}

private function formatSubmissionData(): array
private function formatSubmissionData($createLinks = true): array
{
$formatter = (new FormSubmissionFormatter($this->event->form, $this->event->data))
->createLinks()
->outputStringsOnly()
->useSignedUrlForFiles();

if ($createLinks) {
$formatter->createLinks();
}
if ($this->integrationData->include_hidden_fields_submission_data ?? false) {
$formatter->showHiddenFields();
}
Expand Down Expand Up @@ -98,27 +98,25 @@ private function getSenderName(): string
private function getReplyToEmail($default): string
{
$replyTo = $this->integrationData->reply_to ?? null;

if ($replyTo) {
$parsedReplyTo = $this->parseReplyTo($replyTo);
if ($parsedReplyTo && $this->validateEmail($parsedReplyTo)) {
return $parsedReplyTo;
}
}

return $this->getRespondentEmail() ?? $default;
return $default;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Consider adding logging for invalid reply-to addresses

While the reply-to handling is improved, it would be helpful to log when an invalid reply-to address is provided to assist with debugging configuration issues.

 private function getReplyToEmail($default): string
 {
     $replyTo = $this->integrationData->reply_to ?? null;
     if ($replyTo) {
         $parsedReplyTo = $this->parseReplyTo($replyTo);
-        if ($parsedReplyTo && $this->validateEmail($parsedReplyTo)) {
+        if ($parsedReplyTo && $this->validateEmail($parsedReplyTo)) {
             return $parsedReplyTo;
+        } elseif ($parsedReplyTo) {
+            \Log::warning('Invalid reply-to email configured', [
+                'form_id' => $this->event->form->id,
+                'configured_reply_to' => $replyTo,
+                'parsed_reply_to' => $parsedReplyTo
+            ]);
         }
     }
     return $default;
 }

Also applies to: 112-113

}

private function parseReplyTo(string $replyTo): ?string
{
$parser = new MentionParser($replyTo, $this->formattedData);
$parser = new MentionParser($replyTo, $this->formatSubmissionData(false));
return $parser->parse();
}

private function getSubject(): string
{
$defaultSubject = 'New form submission';
$parser = new MentionParser($this->integrationData->subject ?? $defaultSubject, $this->formattedData);
$parser = new MentionParser($this->integrationData->subject ?? $defaultSubject, $this->formatSubmissionData(false));
return $parser->parse();
}

Expand Down Expand Up @@ -150,7 +148,7 @@ private function getMailData(): array
{
return [
'emailContent' => $this->getEmailContent(),
'fields' => $this->formattedData,
'fields' => $this->formatSubmissionData(),
'form' => $this->event->form,
'integrationData' => $this->integrationData,
'noBranding' => $this->event->form->no_branding,
Expand All @@ -160,7 +158,7 @@ private function getMailData(): array

private function getEmailContent(): string
{
$parser = new MentionParser($this->integrationData->email_content ?? '', $this->formattedData);
$parser = new MentionParser($this->integrationData->email_content ?? '', $this->formatSubmissionData());
return $parser->parse();
}

Expand All @@ -170,26 +168,6 @@ private function getEncodedSubmissionId(): ?string
return $submissionId ? Hashids::encode($submissionId) : null;
}

private function getRespondentEmail(): ?string
{
$emailFields = ['email', 'e-mail', 'mail'];

foreach ($this->formattedData as $field => $value) {
if (in_array(strtolower($field), $emailFields) && $this->validateEmail($value)) {
return $value;
}
}

// If no email field found, search for any field containing a valid email
foreach ($this->formattedData as $value) {
if ($this->validateEmail($value)) {
return $value;
}
}

return null;
}

public static function validateEmail($email): bool
{
return (bool)filter_var($email, FILTER_VALIDATE_EMAIL);
Expand Down
63 changes: 63 additions & 0 deletions api/tests/Feature/Forms/EmailNotificationTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
$notifiable->route('mail', '[email protected]');
$renderedMail = $mailable->toMail($notifiable);
expect($renderedMail->subject)->toBe('New form submission');
expect($renderedMail->replyTo[0][0])->toBe('[email protected]');
expect(trim($renderedMail->render()))->toContain('Test body');
});

Expand Down Expand Up @@ -163,3 +164,65 @@ function (FormEmailNotification $notification, $channels, $notifiable) {
expect($renderedMail->subject)->toBe('Custom Subject');
expect(trim($renderedMail->render()))->toContain('Custom content');
});

it('send email with mention as reply to', function () {
$user = $this->actingAsUser();
$workspace = $this->createUserWorkspace($user);
$form = $this->createForm($user, $workspace);

$emailProperty = collect($form->properties)->first(function ($property) {
return $property['type'] == 'email';
});

$integrationData = $this->createFormIntegration('email', $form->id, [
'send_to' => '[email protected]',
'sender_name' => 'OpnForm',
'subject' => 'New form submission',
'email_content' => 'Hello there 👋 <br>Test body',
'include_submission_data' => true,
'include_hidden_fields_submission_data' => false,
'reply_to' => '<span mention-field-id="' . $emailProperty['id'] . '" mention-field-name="' . $emailProperty['name'] . '" mention-fallback="" contenteditable="false" mention="true">' . $emailProperty['name'] . '</span>'
]);

$formData = [
$emailProperty['id'] => '[email protected]',
];

$event = new \App\Events\Forms\FormSubmitted($form, $formData);
$mailable = new FormEmailNotification($event, $integrationData, 'mail');
$notifiable = new AnonymousNotifiable();
$notifiable->route('mail', '[email protected]');
$renderedMail = $mailable->toMail($notifiable);
expect($renderedMail->replyTo[0][0])->toBe('[email protected]');
});

it('send email with empty reply to', function () {
$user = $this->actingAsUser();
$workspace = $this->createUserWorkspace($user);
$form = $this->createForm($user, $workspace);

$emailProperty = collect($form->properties)->first(function ($property) {
return $property['type'] == 'email';
});

$integrationData = $this->createFormIntegration('email', $form->id, [
'send_to' => '[email protected]',
'sender_name' => 'OpnForm',
'subject' => 'New form submission',
'email_content' => 'Hello there 👋 <br>Test body',
'include_submission_data' => true,
'include_hidden_fields_submission_data' => false,
'reply_to' => null,
]);

$formData = [
$emailProperty['id'] => '[email protected]',
];

$event = new \App\Events\Forms\FormSubmitted($form, $formData);
$mailable = new FormEmailNotification($event, $integrationData, 'mail');
$notifiable = new AnonymousNotifiable();
$notifiable->route('mail', '[email protected]');
$renderedMail = $mailable->toMail($notifiable);
expect($renderedMail->replyTo[0][0])->toBe($form->creator->email);
});
Loading