Skip to content

Commit 0d6e859

Browse files
committed
feature #44311 [Mime] add DraftEmail (kbond)
This PR was squashed before being merged into the 6.1 branch. Discussion ---------- [Mime] add DraftEmail | Q | A | ------------- | --- | Branch? | 6.1 | Bug fix? | no | New feature? | yes | Deprecations? | no | Tickets | n/a | License | MIT | Doc PR | todo I've had the need to create "draft" emails pre-filled with content/attachments. User's can then download these as a `.eml`, open with their email client, manipulate, then send. Thought I'd share my solution to see if this is something that would be acceptable in core. Only needed a few minor adjustments to `Email` that I wrapped up into an `DraftEmail` object: 1. Add `X-Unsent: 1` header (this marks the email as "draft" for clients that support this) 2. Allow no From/To (user can add with their client) 3. Remove Message-ID/Date headers (these will be added by the client) Usage: ```php $content = (new DraftEmail() ->html($twig->render(...)) ->attach(...) ->toString() ; $response = new Response($message->toString()); $contentDisposition = $response->headers->makeDisposition( ResponseHeaderBag::DISPOSITION_ATTACHMENT, 'download.eml' ); $response->headers->set('Content-Type', 'message/rfc822'); $response->headers->set('Content-Disposition', $contentDisposition); ``` Commits ------- 5d6b4a0 [Mime] add DraftEmail
2 parents 4b598f4 + 5d6b4a0 commit 0d6e859

File tree

3 files changed

+115
-3
lines changed

3 files changed

+115
-3
lines changed
+45
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Mime;
13+
14+
use Symfony\Component\Mime\Header\Headers;
15+
use Symfony\Component\Mime\Part\AbstractPart;
16+
17+
/**
18+
* @author Kevin Bond <kevinbond@gmail.com>
19+
*/
20+
class DraftEmail extends Email
21+
{
22+
public function __construct(Headers $headers = null, AbstractPart $body = null)
23+
{
24+
parent::__construct($headers, $body);
25+
26+
$this->getHeaders()->addTextHeader('X-Unsent', '1');
27+
}
28+
29+
/**
30+
* Override default behavior as draft emails do not require From/Sender/Date/Message-ID headers.
31+
* These are added by the client that actually sends the email.
32+
*/
33+
public function getPreparedHeaders(): Headers
34+
{
35+
$headers = clone $this->getHeaders();
36+
37+
if (!$headers->has('MIME-Version')) {
38+
$headers->addTextHeader('MIME-Version', '1.0');
39+
}
40+
41+
$headers->remove('Bcc');
42+
43+
return $headers;
44+
}
45+
}

src/Symfony/Component/Mime/Email.php

+12-3
Original file line numberDiff line numberDiff line change
@@ -386,13 +386,22 @@ public function getBody(): AbstractPart
386386

387387
public function ensureValidity()
388388
{
389-
if (null === $this->text && null === $this->html && !$this->attachments) {
390-
throw new LogicException('A message must have a text or an HTML part or attachments.');
389+
$this->ensureBodyValid();
390+
391+
if ('1' === $this->getHeaders()->getHeaderBody('X-Unsent')) {
392+
throw new LogicException('Cannot send messages marked as "draft".');
391393
}
392394

393395
parent::ensureValidity();
394396
}
395397

398+
private function ensureBodyValid(): void
399+
{
400+
if (null === $this->text && null === $this->html && !$this->attachments) {
401+
throw new LogicException('A message must have a text or an HTML part or attachments.');
402+
}
403+
}
404+
396405
/**
397406
* Generates an AbstractPart based on the raw body of a message.
398407
*
@@ -415,7 +424,7 @@ public function ensureValidity()
415424
*/
416425
private function generateBody(): AbstractPart
417426
{
418-
$this->ensureValidity();
427+
$this->ensureBodyValid();
419428

420429
[$htmlPart, $attachmentParts, $inlineParts] = $this->prepareParts();
421430

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Mime\Tests;
13+
14+
use PHPUnit\Framework\TestCase;
15+
use Symfony\Component\Mime\DraftEmail;
16+
use Symfony\Component\Mime\Exception\LogicException;
17+
18+
/**
19+
* @author Kevin Bond <kevinbond@gmail.com>
20+
*/
21+
final class DraftEmailTest extends TestCase
22+
{
23+
public function testCanHaveJustBody()
24+
{
25+
$email = (new DraftEmail())->text('some text')->toString();
26+
27+
$this->assertStringContainsString('some text', $email);
28+
$this->assertStringContainsString('MIME-Version: 1.0', $email);
29+
$this->assertStringContainsString('X-Unsent: 1', $email);
30+
}
31+
32+
public function testBccIsRemoved()
33+
{
34+
$email = (new DraftEmail())->text('some text')->bcc('sam@example.com')->toString();
35+
36+
$this->assertStringNotContainsString('sam@example.com', $email);
37+
}
38+
39+
public function testMustHaveBody()
40+
{
41+
$this->expectException(LogicException::class);
42+
43+
(new DraftEmail())->toString();
44+
}
45+
46+
public function testEnsureValidityAlwaysFails()
47+
{
48+
$email = (new DraftEmail())
49+
->to('alice@example.com')
50+
->from('webmaster@example.com')
51+
->text('some text')
52+
;
53+
54+
$this->expectException(LogicException::class);
55+
56+
$email->ensureValidity();
57+
}
58+
}

0 commit comments

Comments
 (0)