Send the newsletter in batches the mail provider accepts

Java · Spring Boot · beginner · modification

Adds the send button's backend for the admin console. `SubscriberRepository` gains `findConfirmedEmails()`, a JPQL projection of confirmed subscribers' emails ordered by id. `NewsletterSendService.send` loads the issue (404 if missing), loads the confirmed emails, cuts them into `subList` batches of `MailProviderClient.MAX_RECIPIENTS` and hands each batch to the provider under the campaign key `newsletter-<issueId>`, then returns a `SendSummary`. `NewsletterAdminController` exposes it as `POST /api/admin/newsletters/{issueId}/send`.

Newsletter backend on Spring Boot 3.3 (Java 21), Spring Data JPA (Hibernate 6) on PostgreSQL, Spring Security. The existing `SecurityConfig` (unchanged) restricts `/api/admin/**` to the `ADMIN` role, and the admin console sends the CSRF token on every POST. Existing and unchanged: the `Subscriber` entity (`id`, `email`, `status`; `email` is non-null and unique), the `SubscriberStatus` enum (`PENDING`, `CONFIRMED`, `UNSUBSCRIBED`), the `NewsletterIssue` entity (`id`, non-null `subject` and `html`) with its `NewsletterIssueRepository`, and the `MailProviderClient` interface with `MAX_RECIPIENTS = 500`. The provider deduplicates recipients per campaign key, so sending the same issue again (for example a retry after a failed call, whose exception propagates and fails the request with a 5xx) does not mail anyone twice. There are at most 200,000 subscribers, so holding their emails in one list is fine. The provider has no rate limit at this volume, and a full 200,000-recipient send finishes well inside the admin endpoint's request timeout (moving the send to a background job is a later change).

Requirements

Files touched

--- src/main/java/com/acme/news/SubscriberRepository.java
 
 import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.Query;
+import java.util.List;
 import java.util.Optional;
 
 
     Optional<Subscriber> findByEmail(String email);
+
+    @Query("select s.email from Subscriber s where s.status = com.acme.news.SubscriberStatus.CONFIRMED order by s.id")
+    List<String> findConfirmedEmails();
 }
 
--- src/main/java/com/acme/news/NewsletterSendService.java
+package com.acme.news;
+
+import org.springframework.http.HttpStatus;
+import org.springframework.stereotype.Service;
+import org.springframework.web.server.ResponseStatusException;
+import java.util.List;
+
+@Service
+public class NewsletterSendService {
+
+    public record SendSummary(int recipients, int batches) {
+    }
+
+    private static final int BATCH_SIZE = MailProviderClient.MAX_RECIPIENTS;
+

Review this PR

Java practice