One idea is to use JMS as the 'engine' and you can utilize JMS transactions (which can join existing transactions e.g. DB transaction). This starts to lead towards an async event-driven architecture which is probably a good thing unless we are talking simple apps - in which case you probably don't need to be asking the question.
An example of this would be simple account creation. For this you want to persist account information to the DB and also send the user an email for activation - but you want them in the same transaction for obvious reasons.
You should not put email sending code within the transaction because even though you may send the email - db transaction commit may fail for one reason or another.
You also should not put email sending outside of the transaction (after commit) because email sending may fail leading to an orphan account.
So to use JMS in this scenario - put JMS sending code within the DB transaction and have it join that transaction. You are guaranteed message delivery. On the other end have something consuming the queue sending emails out. In case of email send failure, best option is to log/raise an alert - JMS will roll back and put message back into the queue for later consumption. i.e. to try and resend email once you have hopefully fixed whatever the issue is.
The critical thing is - DB record is consistent and email is eventually sent.