Skip to content
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
4 changes: 4 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,10 @@ MAIL_FROM_NAME="Pterodactyl Panel"
# @see: https://github.com/pterodactyl/panel/pull/3110
# MAIL_EHLO_DOMAIN=panel.example.com

# Resend API key (required when MAIL_MAILER=resend)
# Get your API key at https://resend.com/api-keys
# RESEND_KEY=

##
# CAPTCHA Configuration
# Supported providers: recaptcha, turnstile, none
Expand Down
28 changes: 27 additions & 1 deletion app/Console/Commands/Environment/EmailSettingsCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ class EmailSettingsCommand extends Command
{--port=}
{--endpoint=}
{--username=}
{--password=}';
{--password=}
{--resend-key= : API key for Resend mail transport.}';

protected array $variables = [];

Expand All @@ -48,6 +49,7 @@ public function handle()
'mailgun' => 'Mailgun Transactional Email',
'mandrill' => 'Mandrill Transactional Email',
'postmark' => 'Postmark Transactional Email',
'resend' => 'Resend Transactional Email',
],
$this->config->get('mail.default', 'smtp')
);
Expand All @@ -62,6 +64,12 @@ public function handle()
$this->config->get('mail.from.address')
);

if (empty($this->variables['MAIL_FROM_ADDRESS'])) {
$this->output->error('A from address is required for sending emails.');

return 1;
}
Comment on lines +67 to +71

$this->variables['MAIL_FROM_NAME'] = $this->option('from') ?? $this->ask(
trans('command/messages.environment.mail.ask_mail_name'),
$this->config->get('mail.from.name')
Expand Down Expand Up @@ -149,4 +157,22 @@ private function setupPostmarkDriverVariables()
$this->config->get('mail.username')
);
}

/**
* Handle variables for resend driver.
*/
private function setupResendDriverVariables()
{
$this->variables['MAIL_DRIVER'] = 'resend';

$this->variables['RESEND_KEY'] = $this->option('resend-key') ?? $this->ask(
trans('command/messages.environment.mail.ask_resend_key'),
$this->config->get('services.resend.key')
);

if (empty($this->variables['RESEND_KEY'])) {
$this->output->error('A Resend API key is required when using the Resend mail driver.');
$this->output->comment('You can obtain an API key at https://resend.com/api-keys');
}
Comment on lines +168 to +176
}
}
1 change: 1 addition & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
"predis/predis": "^3.3.0",
"prologue/alerts": "^1.3.0",
"psr/cache": "^3.0.0",
"resend/resend-php": "^0.10.0",
"s1lentium/iptools": "~1.2.0",
"spatie/laravel-fractal": "^6.3.2",
"spatie/laravel-query-builder": "^6.3.6",
Expand Down
6 changes: 5 additions & 1 deletion config/mail.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
| mailers below. You are free to add additional mailers as required.
|
| Supported: "smtp", "sendmail", "mailgun", "ses", "ses-v2",
| "postmark", "log", "array", "failover"
| "postmark", "resend", "log", "array", "failover"
|
*/

Expand Down Expand Up @@ -56,6 +56,10 @@
'transport' => 'postmark',
],

'resend' => [
'transport' => 'resend',
],

'sendmail' => [
'transport' => 'sendmail',
'path' => env('MAIL_SENDMAIL_PATH', '/usr/sbin/sendmail -bs -i'),
Expand Down
1 change: 1 addition & 0 deletions resources/lang/en/command/messages.php
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@
'ask_mailgun_secret' => 'Mailgun Secret',
'ask_mandrill_secret' => 'Mandrill Secret',
'ask_postmark_username' => 'Postmark API Key',
'ask_resend_key' => 'Resend API Key',
'ask_driver' => 'Which driver should be used for sending emails?',
'ask_mail_from' => 'Email address emails should originate from',
'ask_mail_name' => 'Name that emails should appear from',
Expand Down
3 changes: 3 additions & 0 deletions resources/views/admin/settings/mail.blade.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@
<div class="col-xs-12">
<div class="alert alert-info no-margin-bottom">
This interface is limited to instances using SMTP as the mail driver. Please either use <code>php artisan p:environment:mail</code> command to update your email settings, or set <code>MAIL_DRIVER=smtp</code> in your environment file.
@if(config('mail.default') === 'resend')
<br /><br />You are currently using the <strong>Resend</strong> mail driver. To configure it, set <code>RESEND_KEY</code> in your environment file or run <code>php artisan p:environment:mail</code>.
@endif
</div>
</div>
</div>
Expand Down
119 changes: 119 additions & 0 deletions tests/Integration/Commands/Environment/EmailSettingsCommandTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
<?php

namespace Pterodactyl\Tests\Integration\Commands\Environment;

use Pterodactyl\Tests\Integration\IntegrationTestCase;

class EmailSettingsCommandTest extends IntegrationTestCase
{
/**
* Test that the SMTP driver can be selected and configured.
*/
public function test_smtp_driver_selection(): void
{
$this->artisan('p:environment:mail', [
'--driver' => 'smtp',
'--host' => 'smtp.test.com',
'--port' => '587',
'--username' => 'user@test.com',
'--password' => 'secret',
'--encryption' => 'tls',
'--email' => 'panel@test.com',
'--from' => 'Test Panel',
])->assertExitCode(0);

// Verify the .env file was updated
$env = file_get_contents(base_path('.env'));
$this->assertStringContainsString('MAIL_DRIVER=smtp', $env);
$this->assertStringContainsString('MAIL_HOST=smtp.test.com', $env);
$this->assertStringContainsString('MAIL_PORT=587', $env);
$this->assertStringContainsString('MAIL_FROM_ADDRESS=panel@test.com', $env);
}

/**
* Test that the Resend driver can be selected and configured.
*/
public function test_resend_driver_selection(): void
{
$this->artisan('p:environment:mail', [
'--driver' => 'resend',
'--resend-key' => 're_test_123456789',
'--email' => 'panel@test.com',
'--from' => 'Test Panel',
])->assertExitCode(0);

$env = file_get_contents(base_path('.env'));
$this->assertStringContainsString('MAIL_DRIVER=resend', $env);
$this->assertStringContainsString('RESEND_KEY=re_test_123456789', $env);
$this->assertStringContainsString('MAIL_FROM_ADDRESS=panel@test.com', $env);
}

/**
* Test that the Resend driver shows an error when no key is provided.
*/
public function test_resend_driver_warns_without_key(): void
{
$this->artisan('p:environment:mail', [
'--driver' => 'resend',
'--resend-key' => '',
'--email' => 'panel@test.com',
'--from' => 'Test Panel',
])->assertExitCode(0);

// The command should still write the env (it warns but doesn't abort)
$env = file_get_contents(base_path('.env'));
$this->assertStringContainsString('MAIL_DRIVER=resend', $env);
}
Comment on lines +14 to +66

/**
* Test that the mail config includes the resend mailer.
*/
public function test_resend_mailer_is_configured(): void
{
$mailers = config('mail.mailers');

$this->assertArrayHasKey('resend', $mailers);
$this->assertEquals('resend', $mailers['resend']['transport']);
}

/**
* Test that the services config includes the resend key.
*/
public function test_resend_service_config_exists(): void
{
$this->assertNotNull(config('services.resend'));
$this->assertArrayHasKey('key', config('services.resend'));
}

/**
* Test that existing SMTP configuration is not affected.
*/
public function test_smtp_config_unchanged(): void
{
$smtp = config('mail.mailers.smtp');

$this->assertEquals('smtp', $smtp['transport']);
$this->assertArrayHasKey('host', $smtp);
$this->assertArrayHasKey('port', $smtp);
$this->assertArrayHasKey('encryption', $smtp);
$this->assertArrayHasKey('username', $smtp);
$this->assertArrayHasKey('password', $smtp);
}

/**
* Test that the from address validation works.
*/
public function test_empty_from_address_returns_error(): void
{
$this->artisan('p:environment:mail', [
'--driver' => 'smtp',
'--host' => 'smtp.test.com',
'--port' => '587',
'--username' => 'user@test.com',
'--password' => 'secret',
'--encryption' => 'tls',
'--email' => '',
'--from' => 'Test Panel',
])->assertExitCode(1);
}
}
Loading