Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

[Mailer] Dispatch event for Postmark's "inactive recipient" API error#52916

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 ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Merged
fabpot merged 1 commit intosymfony:7.1fromvicdelfant:postmark-mailer-events
Dec 8, 2023

Conversation

@vicdelfant
Copy link
Contributor

QA
Branch?7.1
Bug fix?no
New feature?yes
Deprecations?no
IssuesFix#50165
LicenseMIT

Given the use of Postmark and a recipient that previously generated a bounce, attempting to send another email to that email address results in an HTTP 422 response, along with the error code406 - Inactive Recipient. In the real world, this situation can arise easily in cases of a typo or an email address that had temporary issues.

BecausePostmarkApiTransport requires an HTTP 200 and throws aHttpTransportException for any other HTTP code, something that's of minor interest to the application itself (i.e. apossibly inactive e-mail address) now causes an exception. Depending on the userland logic, this can halt a process that sends out survey reminders, cause the Messenger component to queue the message for retrying etc.

To handle this more elegantly, I'm proposing the following changes:

  • Add aPostmarkDeliveryEventFactory for casting any (future) delivery events to an instance ofPostmarkDeliveryEvent. Currently, only support the 406 'inactive recipient' error is included;
  • Adjust thePostmarkApiTransport so it checks for supported delivery events on an HTTP code other than 200, and if so, dispatches the event accordingly.

We cannot port this logic to the Postmark SMTP transport because, according to Postmark's own documentation, these error response codes are only provided by their API endpoints.

@carsonbot
Copy link

Hey!

I see that this is your first PR. That is great! Welcome!

Symfony has acontribution guide which I suggest you to read.

In short:

  • Always add tests
  • Keep backward compatibility (seehttps://symfony.com/bc).
  • Bug fixes must be submitted against the lowest maintained branch where they apply (seehttps://symfony.com/releases)
  • Features and deprecations must be submitted against the 7.1 branch.

Review the GitHub status checks of your pull request and try to solve the reported issues. If some tests are failing, try to see if they are failing because of this change.

When two Symfony core team members approve this change, it will be merged and you will become an official Symfony contributor!
If this PR is merged in a lower version branch, it will be merged up to all maintained branches within a few days.

I am going to sit back now and wait for the reviews.

Cheers!

Carsonbot

if (null !==$this->dispatcher &&$eventFactory->supports($result['ErrorCode'])) {
$this->dispatcher->dispatch(
$eventFactory->create($result['ErrorCode'],$result['Message'],$email),
PostmarkEvents::DELIVERY
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
PostmarkEvents::DELIVERY
PostmarkEvents::DELIVERY,

@fabpotfabpotforce-pushed thepostmark-mailer-events branch from4ae69c0 to6ffd173CompareDecember 8, 2023 14:19
@fabpot
Copy link
Member

Thank you@vicdelfant.

@vicdelfant
Copy link
ContributorAuthor

Thanks for merging@fabpot! On to the next one :)

@atymic
Copy link

Wooo! I assume this will be in the 7.1 release, will check if laravel needs a PR to handle.

return$this->headers->get('Message-ID')->getBodyAsString();
}

publicfunctionsetHeaders(Headers$headers):self
Copy link
Member

Choose a reason for hiding this comment

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

what is the reason for making this mutable instead of passing it in the constructor ?

Copy link
ContributorAuthor

Choose a reason for hiding this comment

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

No particular reason — it's a personal preference to keep the constructor as compact as possible, with the absolute minimal required data to set up the object in a valid, initialized state.

Copy link
Member

Choose a reason for hiding this comment

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

This setter means that listeners can replace headers in the event (which will at least impact next listeners). Anything that should not be mutable from listeners should not have setters.

}

if (200 !==$statusCode) {
$eventFactory =newPostmarkDeliveryEventFactory();
Copy link
Member

Choose a reason for hiding this comment

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

should we instantiate a new one each time or treat it as a dependency of the class stored in a property ?

Copy link
ContributorAuthor

Choose a reason for hiding this comment

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

Certainly, this could be a dependency that we instantiate in the constructor.


class PostmarkEvents
{
publicconstDELIVERY ='postmark.delivery';
Copy link
Member

Choose a reason for hiding this comment

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

I don't think we need this class with event names. For new code, we should rely on the event class name instead (by passing only 1 argument todispatch())

Copy link
ContributorAuthor

Choose a reason for hiding this comment

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

Fair point, should this be adjusted while the PR has already been merged@fabpot?

Copy link
Member

Choose a reason for hiding this comment

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

This has not been released yet, so we can still adjust it.


privateHeaders$headers;

private ?string$message;
Copy link
Member

Choose a reason for hiding this comment

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

why is this nullable when the constructor does not allow passing null ?

Copy link
ContributorAuthor

Choose a reason for hiding this comment

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

Good eye, this could indeed be a non-nullable property.

Copy link
Member

Choose a reason for hiding this comment

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

already fixed in#53242


class PostmarkDeliveryEvent
{
publicconstCODE_INACTIVE_RECIPIENT =406;
Copy link
Member

Choose a reason for hiding this comment

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

what is the reason to expose this constant in the public API of the bridge ?

Copy link
ContributorAuthor

Choose a reason for hiding this comment

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

No specific reason, except for keeping the event with the error code and the error code itself close together. Alternatively, the mapping/constant could be moved toPostmarkDeliveryEventFactory.

Copy link
Member

Choose a reason for hiding this comment

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

As the factory is the only usage of the constant (outside tests), I would suggest moving it there as a private constant (and making tests use the integer directly). Once we introduce a public API, it becomes covered by the BC promise of Symfony. So we should not introduce them without reason (it only make maintenance harder)

PostmarkEvents::DELIVERY,
);

return$response;
Copy link
Member

Choose a reason for hiding this comment

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

shouldn't we still throw an exception by default to report that sending the email failed ? Right now, if you have no listener doing something special, the failure will be silently ignored.
To me, skipping the exception should require that a listener marks the failure as handled in some way (by calling a method on the event object)

Copy link
ContributorAuthor

Choose a reason for hiding this comment

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

In my opinion, this would defeat the purpose of the PR. If we would take a traditional SMTP server as a reference, any e-mail that is valid gets sent - simple as that. This also means that you can send 100 e-mails to an e-mail address that bounced on the first try, because the SMTP server will simply accept the e-mails and try to deliver them. There would be no way for the SMTP server or the application to know that delivering the e-mail, in fact, failed (or would fail down the line). Ergo, no exceptions.

Postmark's API, on the other hand, has stricter policies and is kind enough to throw an 'inactive recipient' API error to let you know that the e-mail address bounced before, or is otherwise suppressed. This is where the problem lies: with the pre-PR implementation, the application all of a sudden has to deal with exceptions while trying to deliver an otherwise valid e-mail. Also because the exception means that the recipientcould be inactive, it's in fact not 100% sure that it still is.

Sure we could wrap every sending operation in a try/catch, but in my opinion we should keep the logic of the mailer as close to its core as possible: using a traditional SMTP server that just queues mails and tries to deliver them. Anything extra is, well, extra.

Then again this is my opinion, but if we would be throwing an exception I'd rather revert the PR so keep things as simple as possible, and rely on the webhooks to detect post-delivery issues.

Copy link
Member

Choose a reason for hiding this comment

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

but this won't be sent to webhooks either (as the API will reject the sending)

Copy link
ContributorAuthor

Choose a reason for hiding this comment

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

If the intent is to have an exception then reverting this PR would do just that, you'd get the exception back. The bounce will actually trigger a webhook, but only the first time:https://postmarkapp.com/developer/webhooks/bounce-webhook.

@fabpotfabpot mentioned this pull requestMay 2, 2024
Sign up for freeto join this conversation on GitHub. Already have an account?Sign in to comment

Reviewers

@stofstofstof left review comments

@OskarStarkOskarStarkOskarStark left review comments

@xabbuhxabbuhxabbuh left review comments

@fabpotfabpotfabpot approved these changes

Assignees

No one assigned

Projects

None yet

Milestone

7.1

Development

Successfully merging this pull request may close these issues.

[Mailer] Sending to a blocked email address through Postmark

7 participants

@vicdelfant@carsonbot@fabpot@atymic@stof@OskarStark@xabbuh

[8]ページ先頭

©2009-2025 Movatter.jp