Newsletter for Craft CMS plugin makes it possible to let end users subscribe with various emailing services from a frontend form.
It currently supports the following services:
- Mailchimp
- Mailjet
- Brevo (ex Sendinblue)
This plugin is GDPR compliant and requires the user to give its consent when subscribing to the newsletter.
💡 Similar to Craft Mailer adapters, you can even create your own adapter to connect to unsupported services.
This plugin requires Craft CMS 4.0.0 or later and PHP 8.0.2 or later.
In order to support automatic Google reCAPTCHA verification, you will need to install
jub/craft-google-recaptcha
plugin.
- Install with composer via
composer require jub/craft-newsletter
from your project directory. - Install the plugin in the Craft Control Panel under Settings → Plugins, or from the command line via
./craft install/plugin newsletter
.
You can manage configuration setting through the Control Panel by going to Settings → Newsletter
- From the Service type dropdown, select the service type you wish to use to handle newsletter users subscription
- Provide the required API keys and parameters as described below.
- If Google reCAPTCHA plugin is installed and enabled, choose whether to verify the submission or not using the Enable Google reCAPTCHA light-switch.
⚠️ When enabled, the Google reCAPTCHA feature will not render the frontend widget for you. You have to add{{ craft.googleRecaptcha.render() }}
somewhere in the form view.
- API Key (
apiKey
) - API Secret (
apiSecret
) - List ID (optional) (
listId
)
You can find these informations in your Mailjet account informations in the REST API keys section.
You can provide a contact list ID in order to subscribe the enduser to a specific one.
If no list ID is provided, user will only be created as a contact.
- API Key (
apiKey
) - List ID (optional, required if DOI is on) (
listId
) - Activate Double Opt-in (DOI) (optional) (
doi
) - Mail Template ID (required if DOI is on) (
doiTemplateId
) - Redirection URL (required if DOI is on) (
doiRedirectionUrl
)
You can find these informations in your Brevo account.
You can provide a contact list ID in order to subscribe the enduser to a specific one.
You can also enable Brevo Double Opt-in feature. You will need a Sendinblue template as described here.
If no list ID is provided, user will only be created as a contact.
- API Key (
apiKey
): You can find that information from your Mailchimp account. - Server prefix (
serverPrefix
): You can find that information by looking at the url when logged into your Mailchimp account. For instance,https://us4.admin.mailchimp.com/account/api/
indicate the server prefix to use isus4
- Audience ID (
listId
): You can find that information inAudience
>All contacts
>Settings
>Audience name and campaign defaults
You can create a newsletter.php
file in the config
folder of your project and provide the settings as follow (example):
return [
"adapterType" => \juban\newsletter\adapters\Mailjet::class,
"adapterTypeSettings" => [
'apiKey' => '',
'apiSecret' => '',
'listId' => ''
],
"recaptchaEnabled" => true
];
Depending on the service and its specific settings, adjust the adapterType
to the according service adapter class name and provide required parameters in the adapterTypeSettings
associative array (see Service configuration).
⚠️ Any value provided in that file will override the settings from the Control Panel.
You can use the following template as a starting point for your registration form:
{# @var craft \craft\web\twig\variables\CraftVariable #}
{# @var newsletterForm \juban\newsletter\models\NewsletterForm #}
{% if craft.app.plugins.plugin('newsletter') is not null %}
{# If there were any validation errors, a `newsletterForm` variable will be passed to the template, which contains the posted values and validation errors. If that’s not set, we’ll default to a new newsletterForm. #}
{% set newsletterForm = newsletterForm ?? create('juban\\newsletter\\models\\NewsletterForm') %}
{# success notification #}
{% if success is defined and success %}
<div role="alert">
<p>{{ 'Your newsletter subscription has been taken into account. Thank you.'|t }}</p>
</div>
{% endif %}
<form action="" method="post" accept-charset="UTF-8">
{{ csrfInput() }}
{# Subscription process is handled by the newsletter plugin controller #}
{{ actionInput('newsletter/newsletter/subscribe') }}
{# User will be redirected to the redirect input url upon successful subscription #}
{{ redirectInput('thank-you') }}
<label for="newsletter-consent">
<input type="checkbox" value="check" name="consent" id="newsletter-consent" required {% if newsletterForm.hasErrors('consent') %}aria-invalid="true" aria-describedby="consent-error"{% endif %}>
{{'I agree to receive your emails and confirm that I have read your privacy policy.'|t}}
</label>
{% if newsletterForm.hasErrors('consent') %}
<div id="consent-error" role="alert" class="text-sm text-error font-bold">{{ newsletterForm.getFirstError('consent') }}</div>
{% endif %}
<label for="newsletter-email">{{ 'Votre email'|t }}<span aria-hidden="true">*</span></label>
<input id="newsletter-email" required name="email" type="email" placeholder="j.dupont@gmail.com" value="{{ newsletterForm.email }}" {% if newsletterForm.hasErrors('email') %}aria-invalid="true" aria-describedby="email-error"{% endif %}>
{% if newsletterForm.hasErrors('email') %}
<div id="email-error" role="alert" class="text-sm text-error font-bold">{{ newsletterForm.getFirstError('email') }}</div>
{% endif %}
<button type="submit">{{ 'Subscribe'|t }}</button>
</form>
{% endif %}
The newsletter/newsletter/subscribe
action expects the following inputs submitted as POST
:
email
: User email to subscribeconsent
: Any value indicating that the user gives its consent to receive the newsletter
⚠️ Don’t forget to add{{ craft.googleRecaptcha.render() }}
in the form view if the Google reCAPTCHA verification is enabled.
Alternatively, you can submit the newsletter form with Javascript.
This gives you more freedom to provide visual effects to the user and prevents the page from reloading and scrolling to the top of the page.
The downside is that you need to write the ajax-request.
Use the example below as a reference, note the required application/json
header:
var data = new FormData();
data.append("consent", consent);
data.append("email", email);
var xhr = new XMLHttpRequest();
xhr.withCredentials = true;
xhr.addEventListener("readystatechange", function() {
if (this.readyState === 4) {
console.log(this.responseText);
}
});
xhr.open("POST", "https://{YOUR_DOMAIN}/actions/newsletter/newsletter/subscribe/");
xhr.setRequestHeader("Accept", "application/json");
xhr.send(data);
⚠️ If the Google reCAPTCHA verification is enabled, add thedata.append("g-recaptcha-response", "");
to the request as well!
You can provide your own validations on frontend form submission in a project module or a plugin using the afterValidate
event on the NewsletterForm
model:
use yii\base\Event;
use juban\googlerecaptcha\GoogleRecaptcha;
Event::on(
NewsletterForm::class,
NewsletterForm::EVENT_AFTER_VALIDATE,
static function (Event $event) {
$form = $event->sender;
$isSpam = // custom spam detection logic...
if($isSpam) {
$this->addError('email', 'Spam detected!');
}
}
);
To add a new adapter for unsupported services:
Create an adapter class that extends the juban\newsletter\adapters\BaseNewsletterAdapter
class.
Some small example:
namespace juban\newsletter\adapters;
use Craft;
class MySuperNewsletterAdapter extends BaseNewsletterAdapter
{
// Declare every attributes required by the service (ie API keys and secrets, etc...)
public $apiKey;
// Store any error occuring in the subscribe method here
private $_errorMessage;
/**
* @inheritdoc
*/
public function behaviors(): array
{
// Support for setting defined in environment variables or aliases
$behaviors = parent::behaviors();
$behaviors['parser'] = [
'class' => EnvAttributeParserBehavior::class,
'attributes' => [
'apiKey'
],
];
return $behaviors;
}
/**
* @inheritdoc
*/
public static function displayName(): string
{
return 'My Service Name'; // Service name as shown in the adapter type dropdown
}
/**
* @inheritdoc
*/
public function getSettingsHtml()
{
// Render the adapter settings templates
// Adapt the path according to your module / plugin
return Craft::$app->getView()->renderTemplate('newsletter/newsletterAdapters/MySuperNewsletterAdapter/settings', [
'adapter' => $this
]);
}
/**
* Try to subscribe the given email into the newsletter mailing list service
* @param string $email
* @return bool
*/
public function subscribe(string $email): bool
{
// Call the service API here
$this->_errorMessage = null;
if(!MySuperNewsletterService::subscribe($email)) {
// If something goes wrong, store the error message
$this->_errorMessage = MySuperNewsletterService::getError();
return false;
}
return true;
}
/**
* Return the latest error message after a call to the subscribe method
* @return null|string
*/
public function getSubscriptionError(): string
{
return $this->_errorMessage;
}
/**
* @inheritdoc
*/
protected function defineRules(): array
{
// Validation rules for the adapter settings should be defined here
$rules = parent::defineRules();
$rules[] = [['apiKey'], 'trim'];
$rules[] = [['apiKey'], 'required'];
return $rules;
}
}
The template settings view could look like this:
{% import "_includes/forms" as forms %}
{{ forms.autosuggestField({
label: "API Key"|t('newsletter'),
id: 'apiKey',
name: 'apiKey',
required: true,
suggestEnvVars: true,
value: adapter.apiKey,
errors: adapter.getErrors('apiKey')
}) }}
The adapter model can be accessed in the twig view using the
adapter
variable.
Last, in a module or a plugin, register the adapter as follow:
use craft\events\RegisterComponentTypesEvent;
use juban\newsletter\Newsletter;
Event::on(
Newsletter::class,
Newsletter::EVENT_REGISTER_NEWSLETTER_ADAPTER_TYPES,
static function (RegisterComponentTypesEvent $event) {
$event->types[] = MySuperNewsletterAdapter::class;
}
);
- Support additional form fields
- Support for multiple lists
Base plugin icon makes use of Font Awesome Free