[PHP] Grote mailing versturen

Pagina: 1
Acties:

Onderwerpen


Acties:
  • 0 Henk 'm!

  • posttoast
  • Registratie: April 2000
  • Laatst online: 23:34
Dit topic is niet echt een concrete vraag, maar meer een controle om te kijken of ik mijn probleem op een goede manier heb opgelost.

Ik heb voor iemand een mailing-systeem gemaakt in PHP. Het is allemaal niet heel erg ingewikkeld, maar het gaat om een redelijk grote groep mensen (op dit moment ongeveer 3000 abonnees). Omdat een simpele send-knop met een while-loopje erachter mij hier niet echt een optie leek ben ik wat leeswerk gaan verrichten. Ik heb het een en ander geleerd over verschillende mailer-classes (Swift-mailer en PHPMailer, waarvan de eerste de netste is volgens mij) en over de globale opzet (hier op GoT kwam o.a. chris met wat goede tips).

Al met al denk ik dat ik tot een goede oplossing ben gekomen, maar ik ben benieuwd naar jullie mening. Even 3000 mails sturen als test is niet echt een optie, dus ik moet het vooral van de theorie hebben met het opzetten.

Dit is hoe ik het nu doe:

Een nieuwsbrief wordt opgemaakt en opgeslagen. Zodra hij wordt aangemerkt als "klaar om te versturen" wordt de database-tabel "mailqueue" gevuld. Hierin staan de volgende velden:
  • newsletter_id
  • subscriber_id
  • subscriber_email
  • newsletter_title
  • newsletter_content
Ik heb dan dus 3000 records in de mailqueue staan. Omdat iedere nieuwsbrief een klein beetje verschilt (ze hebben allemaal een unieke afmeld-code) staat dus voor iedere mail de inhoud van de nieuwsbrief opgeslagen. Ook bespaart dit mij later tijd omdat de content dan niet meer in de template gegoten hoeft te worden. Ik heb liever dat deze handeling (eenmalig) wat langer duurt dan dat het systeem later langer bezig is.

Vervolgens het versturen. Op de server draait een cronjob die iedere 5 minuten mailer.php aanroept. Mailer.php kijkt of er records in de tabel mailqueue staan. Zo ja: dan pakt hij de eerste 100 records en voert hij een foreach-loopje uit. Voor ieder record in de mailqueue voert het script de PHP mail()-funtie uit met de gegevens die hij uit de mailqueue-tabel heeft gehaald. De headers worden boven de foreach-loop al gedefinieerd, omdat die niet per mail uniek zijn.

Zodra de mail verstuurd is wordt het betreffende record uit de mailqueue verwijderd. Daarna wordt er een record aangemaakt in de tabel "mailhistory" met daarin de ID van de newsletter, de ID van de subscriber, het mailadres van de subscriber en de verzenddatum.

Nu heb ik er bewust voor gekozen om geen class als Swift-mailer te gebruiken, omdat volgens mij 100x de mail()-functie per 5 minuten uitvoeren voor een beetje server geen probleem hoeft te zijn. Ik zie dus ook niet echt de noodzaak om via een aparte SMTP-server te versturen.

Ik ben erg benieuwd naar jullie gedachten hierover. Pak ik dit handig aan? Zijn er zaken die veel slimmer kunnen? Ik hoor het graag! Er ontstaat dan natuurlijk direct een mooie tutorial voor anderen die hier tegenaan lopen.

Noot: ik heb bewust geen stukken code gepost, omdat het hier meer over de theorie dan de daadwerkelijke code gaat. Mocht iemand dit toch willen zien dan kan dat natuurlijk.

omniscale.nl


Acties:
  • 0 Henk 'm!

  • orf
  • Registratie: Augustus 2005
  • Laatst online: 00:44

orf

de functie mail() blijft wel de volgende handeling:

open smtp -> verstuur bericht -> sluit smtp.

Met Swiftmailer kun je dit doen:

open smtp -> verstuur 100 berichten -> sluit smtp.

Persoonlijk vind ik de implementatie van de functie mail() in PHP behoorlijk brak. Een class als Swiftmailer doet dat een stuk beter. Het meegeven van headers voor HTML mail is bijvoorbeeld een stuk beter geregeld.

Ik heb zelf voor een mailingsysteem wel gekozen voor Swiftmailer. Ik doe het zonder cronjob, maar met een exec naar een PHP script. Dit script verstuurd de mailings in batches en gebruikt sleep() om de mailserver te ontlasten.

Acties:
  • 0 Henk 'm!

  • posttoast
  • Registratie: April 2000
  • Laatst online: 23:34
Dat is wel een voordeel van Swiftmailer ja. Maar geldt het voordeel van het open laten staan ook wanneer ik 100 unieke berichten stuur? Werkt het dan zo:

open smtp -> verstuur bericht 1, verstuurd bericht 2, enz. -> sluit smtp?

Ik weet overigens dat Swift ook een optie heeft om je mails te personalizen, dus dat is ook nog een optie.

omniscale.nl


Acties:
  • 0 Henk 'm!

  • SKiLLa
  • Registratie: Februari 2002
  • Niet online

SKiLLa

Byte or nibble a bit ?

Is een aparte SMTP server toch geen goed idee i.v.m. mogelijke (terecht of niet) blacklisting ? Als je zoveel mailtjes verstuurd zijn er in het begin altijd wel een paar mensen bij die het als spam aanmerken en een klacht indienen. Als dat dan dezelfde SMTP betreft als "normale" zakelijke mail, dan ben je het bokje.

'Political Correctness is fascism pretending to be good manners.' - George Carlin


Acties:
  • 0 Henk 'm!

  • posttoast
  • Registratie: April 2000
  • Laatst online: 23:34
De mails worden verstuurd vanaf een server die verder niet voor mailafhandeling gebruikt wordt maar alleen als webserver. Het betreft een nieuwsbrief die alleen door mensen wordt ontvangen die zich hebben opgegeven en daarna nog op een bevestigingslink in een mail geklikt hebben, dus op zich denk ik dat dat probleem wel mee zal vallen.

omniscale.nl


Acties:
  • 0 Henk 'm!

  • orf
  • Registratie: Augustus 2005
  • Laatst online: 00:44

orf

posttoast schreef op zondag 11 januari 2009 @ 13:36:
open smtp -> verstuur bericht 1, verstuurd bericht 2, enz. -> sluit smtp?
Ja, dat kunnen unieke e-mails zijn. Het gaat erom dat tussendoor de connectie niet gesloten wordt.

Acties:
  • 0 Henk 'm!

  • posttoast
  • Registratie: April 2000
  • Laatst online: 23:34
OK, dan is dat sowieso een veel betere oplossing. Als ik dan niet zelf een SMTP-server opgeef dan gebruikt ie gewoon die van de webserver op dezelfde manier toch?

omniscale.nl


Acties:
  • 0 Henk 'm!

  • Rob
  • Registratie: Februari 2000
  • Niet online

Rob

posttoast schreef op zondag 11 januari 2009 @ 13:51:
OK, dan is dat sowieso een veel betere oplossing. Als ik dan niet zelf een SMTP-server opgeef dan gebruikt ie gewoon die van de webserver op dezelfde manier toch?
Dan zal de webserver rechtstreeks met de mailservers gaan praten.

Denk er wel aan dat sommige mailserver al zo zijn ingesteld dat ze heel kieskeurig zijn over email adressen en hostnames van mailtjes. Ze kijken dan welke hostname het email adres heeft en doen nog even een reverse lookup op het ip adres.

In the beginning the Internet was a bunch of smart users with dumb terminals. Now...


Acties:
  • 0 Henk 'm!

  • posttoast
  • Registratie: April 2000
  • Laatst online: 23:34
Rob schreef op zondag 11 januari 2009 @ 13:58:
[...]


Dan zal de webserver rechtstreeks met de mailservers gaan praten.

Denk er wel aan dat sommige mailserver al zo zijn ingesteld dat ze heel kieskeurig zijn over email adressen en hostnames van mailtjes. Ze kijken dan welke hostname het email adres heeft en doen nog even een reverse lookup op het ip adres.
Ha Rob, nog gefeliciteerd! :)

OK, dus als het from-adres in de headers anders is dan de server waarvandaan gestuurd wordt is dat al link? Of bedoel je iets anders?

omniscale.nl


Acties:
  • 0 Henk 'm!

  • Rob
  • Registratie: Februari 2000
  • Niet online

Rob

posttoast schreef op zondag 11 januari 2009 @ 14:00:
[...]

Ha Rob, nog gefeliciteerd! :)

OK, dus als het from-adres in de headers anders is dan de server waarvandaan gestuurd wordt is dat al link? Of bedoel je iets anders?
Link is iets anders. Het zal je een wat hogere rating geven bij spam-assassin bijvoorbeeld.

Als het een HTML mailing is, is het bijvoorbeeld ook weer handig om precies dezelfde tekst als in de HTML mail ook in de text-mail te zetten en zo zijn er nog meer "truukjes".

thanks

In the beginning the Internet was a bunch of smart users with dumb terminals. Now...


Acties:
  • 0 Henk 'm!

  • djluc
  • Registratie: Oktober 2002
  • Laatst online: 21-09 14:28
Ik zou niet met een archief tabel werken qua database opzet maar gewoon een veld sent=1 setten. Anders moet je weer een soort van transactie gaan starten om zeker te zijn dat het record verwijderd is, het nieuwe record is aangemaakt etc.

Verzenden zeker met een class doen zoals gemeld of zelf een SMTP implementatie doen (niet mijn voorkeur).

Acties:
  • 0 Henk 'm!

  • posttoast
  • Registratie: April 2000
  • Laatst online: 23:34
djluc schreef op zondag 11 januari 2009 @ 14:47:
Ik zou niet met een archief tabel werken qua database opzet maar gewoon een veld sent=1 setten. Anders moet je weer een soort van transactie gaan starten om zeker te zijn dat het record verwijderd is, het nieuwe record is aangemaakt etc.
Ja, dat is ook een manier. Alleen het nadeel is dat de mailqueue-tabel dan op een gegeven moment heel groot wordt en de query die erop uitgevoerd moet worden om te kijken of er mails klaar staan om te verzenden (WHERE sent = 0) steeds trager. Of is dat onzin?

omniscale.nl


Acties:
  • 0 Henk 'm!

  • djluc
  • Registratie: Oktober 2002
  • Laatst online: 21-09 14:28
Is zeker trager maar doe je maar 1x in de 5 min. In zo'n periode doe je anders tig keer een delete en een insert.

Daarnaast vind ik het mooier qua database structuur. Als je bijvoorbeeld een tabel hebt waarin je statistieken per link in een mail bijhoudt zijn bijvoorbeeld interessant. Je kan dan die gewoon netjes relateren aan je standaard id.

[ Voor 34% gewijzigd door djluc op 11-01-2009 15:29 ]


Acties:
  • 0 Henk 'm!

  • posttoast
  • Registratie: April 2000
  • Laatst online: 23:34
Goed punt! 1x per 5 minuten een zwaardere query is beter dan 200x per minuut een lichte query.

Wat betreft statistieken, in mailhistory kan ik alles netjes terugvinden achteraf. Daarin staat eigenlijk geen loze informatie. Alleen mailqueue is een beetje een "vieze" tabel, maar die is dan ook tijdelijk.

Maar goed, op het gebied van performance ben ik het zeker met je eens. Deze ga ik aanpassen dus.

omniscale.nl


Acties:
  • 0 Henk 'm!

  • djluc
  • Registratie: Oktober 2002
  • Laatst online: 21-09 14:28
Wij doen trouwens een 1000 mailtjes op 1 server per 10 minuten met een niet geoptimaliseerd systeem dus denk dat je als je even gaat meten je aantallen rustig op kan schroeven.

Acties:
  • 0 Henk 'm!

  • posttoast
  • Registratie: April 2000
  • Laatst online: 23:34
Op de server draaien meer websites dan alleen degene waar het om gaat (is gewoon een shared hostingpakket), dus met opschroeven ben ik maar een beetje voorzichtig.

Maar het is natuurlijk wel interessant om het e.e.a. te meten. Wat is een goede manier om dat te doen zonder dat de meting zelf het resultaat al teveel beïnvloedt*? Iedere keer als er een mail uitgevoerd is de tijd naar een logbestandje schrijven?

*: ik heb helaas niet de beschikking over een Heisenberg-compensator ;)

omniscale.nl


Acties:
  • 0 Henk 'm!

  • djluc
  • Registratie: Oktober 2002
  • Laatst online: 21-09 14:28
Ok, dat is inderdaad opletten! Wij hebben tijdens de testfase een test gedaan waarbij we de serverload gechecked hebben. Weet niet of je daar toegang toe hebt.

Acties:
  • 0 Henk 'm!

  • posttoast
  • Registratie: April 2000
  • Laatst online: 23:34
Zelfs als ik daar toegang toe heb, dan weet ik niet hoe relevant dat is. Ik weet natuurlijk nooit wat andere gebruikers op die server doen terwijl ik de mailing verstuur. Op zich is het wel interessant om te kijken hoe snel hij die 100 mails verstuurt. Als dat gemiddeld binnen 1 minuut is (bijvoorbeeld), dan weet ik dat 5 minuten ruim genoeg zijn. Als ie er 4 minuten over doet, dan moet ik het misschien omlaag schroeven voor de zekerheid.

omniscale.nl


Acties:
  • 0 Henk 'm!

  • Creepy
  • Registratie: Juni 2001
  • Laatst online: 21:47

Creepy

Tactical Espionage Splatterer

Met een shared hosting pakket zal een "optimalisatie" van het openhouden van de connectie zeer waarschijnlijk niet nuttig zijn. Als je meer dan 5 mailtjes over dezelfde smtp connectie mag laten sturen dan mag je al bij zijn.

Overleg voor de zekerheid ook even met je hoster. Grote mailings vanaf een shared hosting bak vinden ze vaak niet leuk. Je zou ook niet de eerste zijn die voor een blacklisting van een server zorgt en dus tegelijk alle uitgaande mail van die server voor alle accounts om zeep helpt.
posttoast schreef op zondag 11 januari 2009 @ 15:00:
[...]
Ja, dat is ook een manier. Alleen het nadeel is dat de mailqueue-tabel dan op een gegeven moment heel groot wordt en de query die erop uitgevoerd moet worden om te kijken of er mails klaar staan om te verzenden (WHERE sent = 0) steeds trager. Of is dat onzin?
Zet een index op dat veld. Dan zal het selecteren gewoon snel blijven verlopen. Eventueel maak je er een senddate van die je null laat als er niks is verstuurd.

Nog een tip om de zaken schoon te houden: check je bounces! Ga dus voor elke mail een VERP maken (googlen maar :P ). Zo kan je niet gebruikte adressen laten opschonen, wel zo netjes.

[ Voor 28% gewijzigd door Creepy op 11-01-2009 17:09 ]

"I had a problem, I solved it with regular expressions. Now I have two problems". That's shows a lack of appreciation for regular expressions: "I know have _star_ problems" --Kevlin Henney


Acties:
  • 0 Henk 'm!

  • posttoast
  • Registratie: April 2000
  • Laatst online: 23:34
De hoster heeft aangegeven hier geen problemen mee te hebben mits de adressen netjes verkregen zijn. Maar goed, ik begrijp dus dat ik net zo goed gewoon mail() kan gebruiken.

Wat betreft die bounces: ja, die ga ik checken. Ik had alleen geen idee dat dat ook geautomatiseerd kon. Ik heb nu de eigenaar van de site de opdracht gegeven om dit met de hand op te gaan schonen (zal vooral de eerste keer nogal een klus zijn). Ik ga eens zoeken op VERP, bedankt :)

omniscale.nl


Acties:
  • 0 Henk 'm!

  • gertjuhh
  • Registratie: April 2004
  • Laatst online: 26-04 09:14
Mijn excuses dat ik dit topic even "kaap", maar heb een vraagje over VERP.
Is het dan noodzakelijk om een mail-catcher in te stellen, aangezien je X aantal unieke mail adressen (die je natuurlijk niet allemaal aan gaat maken, ook niet als alias) op moet geven als "envelope sender".

Verder bedankt voor een leerzaam topic _/-\o_

Acties:
  • 0 Henk 'm!

  • posttoast
  • Registratie: April 2000
  • Laatst online: 23:34
@gertjuhh: geeft niets, het topic wordt er in het ergste geval interessanter van.

Ik denk dat je inderdaad een catch-all moet aanzetten. Vanaf het catch-all adres vang je alle mail op en strip je vanaf het = teken volgens mij. Klopt dat Creepy?

Overigens heeft degene waarvoor ik het systeem heb gebouwd gisteren iets te enthousiast door de 600 waarschuwingen heengeklikt en alvast een mailing verstuurd. Vervelend voor hem (want de mailing zat nog vol taalfouten), maar leuk voor mij, want ik heb kunnen meten. Dit was nog met een simpele mail() en verder precies zoals ik in de topicstart heb aangegeven (had nog niets aangepast). Resultaat: in 26 seconden zijn er 100 mails verstuurd. 5 minuten per 100 mails is dus ruim zat! (op deze server dan)

omniscale.nl


Acties:
  • 0 Henk 'm!

  • Creepy
  • Registratie: Juni 2001
  • Laatst online: 21:47

Creepy

Tactical Espionage Splatterer

Hoe je de VERP moet instellen is afhankelijk van je gebruikte mailsoftware en deels natuurlijk van je eigen geschreven software. Je moet er inderdaad wel voor zorgen dat ze allemaal op dezelfde plek terechtkomen en daar een "catcher" voor maken. Een extra adres aanmaken voor een eventuel bounce per adres is inderdaad onzinnig. Een echte catch-all is normaal gesproken niet nodig.

[ Voor 7% gewijzigd door Creepy op 12-01-2009 13:49 ]

"I had a problem, I solved it with regular expressions. Now I have two problems". That's shows a lack of appreciation for regular expressions: "I know have _star_ problems" --Kevlin Henney


Acties:
  • 0 Henk 'm!

  • S_tef
  • Registratie: December 2004
  • Laatst online: 20-09 12:29
Ik vindt het erg gevaarlijk om er op te vertrouwen dat je cron elke 5 minuten kan lopen.
Ik verstuur ook regelmatig een paar duizend mail op een deticated server via smtp, maar na een paar ladingen krijgt hij het wel zwaar hoor.

Ik zou eerder ergens in je database aangeven wanneer de huidige batch klaar is, waardoor je je cron wel nog steeds elke minuut / 5 minuten kan draaien.

Acties:
  • 0 Henk 'm!

  • posttoast
  • Registratie: April 2000
  • Laatst online: 23:34
S_tef schreef op maandag 12 januari 2009 @ 14:26:
Ik vindt het erg gevaarlijk om er op te vertrouwen dat je cron elke 5 minuten kan lopen.
Ik verstuur ook regelmatig een paar duizend mail op een deticated server via smtp, maar na een paar ladingen krijgt hij het wel zwaar hoor.

Ik zou eerder ergens in je database aangeven wanneer de huidige batch klaar is, waardoor je je cron wel nog steeds elke minuut / 5 minuten kan draaien.
Dat is helemaal niet zo'n gekke opmerking. Anders heb je inderdaad kans dat het zich gaat stapelen (eerste batch is nog niet klaar, ondertussen verder met de volgende, enz.)

Wat is een mooie oplossing? In de mailqueue bij de eerste 100 een veld "busy" op 1 zetten en dan de cronjob van tevoren laten checken of er mails op busy staan? Probleem is dan wel dat als de cronjob er eenmalig om wat voor reden dan ook mee ophoudt hij helemaal vast komt te staan. Iemand hier een idee over?

omniscale.nl


Acties:
  • 0 Henk 'm!

  • djluc
  • Registratie: Oktober 2002
  • Laatst online: 21-09 14:28
Wij controleren gewoon of het script nog loopt, dat kan op linux door een grep te doen op de processenlijst.

Acties:
  • 0 Henk 'm!

  • posttoast
  • Registratie: April 2000
  • Laatst online: 23:34
Dat is netter, maar dat gaat niet werken op die shared host denk ik.

omniscale.nl


Acties:
  • 0 Henk 'm!

  • Creepy
  • Registratie: Juni 2001
  • Laatst online: 21:47

Creepy

Tactical Espionage Splatterer

Desnoods laat je het script een een (lege) file aanmaken. Indien de file al bestaat runt het script al, anders maak je zelf de file aan, doe je je ding en verwijder je de file aan het einde.

"I had a problem, I solved it with regular expressions. Now I have two problems". That's shows a lack of appreciation for regular expressions: "I know have _star_ problems" --Kevlin Henney


Acties:
  • 0 Henk 'm!

  • posttoast
  • Registratie: April 2000
  • Laatst online: 23:34
Creepy schreef op maandag 12 januari 2009 @ 15:49:
Desnoods laat je het script een een (lege) file aanmaken. Indien de file al bestaat runt het script al, anders maak je zelf de file aan, doe je je ding en verwijder je de file aan het einde.
Ja, dat is hetzelfde als de database oplossing die ik eerder aandroeg. Enig probleem: als hij ermee stopt voordat hij het bestandje kan verwijderen (om wat voor reden dan ook, stroomuitval ofzo) dan zit hij vast. Want het bestandje bestaat al en zal niet verwijderd worden.

omniscale.nl


Acties:
  • 0 Henk 'm!

  • S_tef
  • Registratie: December 2004
  • Laatst online: 20-09 12:29
posttoast schreef op maandag 12 januari 2009 @ 14:50:
[...]

Dat is helemaal niet zo'n gekke opmerking. Anders heb je inderdaad kans dat het zich gaat stapelen (eerste batch is nog niet klaar, ondertussen verder met de volgende, enz.)

Wat is een mooie oplossing? In de mailqueue bij de eerste 100 een veld "busy" op 1 zetten en dan de cronjob van tevoren laten checken of er mails op busy staan? Probleem is dan wel dat als de cronjob er eenmalig om wat voor reden dan ook mee ophoudt hij helemaal vast komt te staan. Iemand hier een idee over?
Niet alleen dat het gaat stapelen, maar meer het feit dat dan een mail 2 x verstuurd zou kunnen worden (mits je geen transacties gebruikt).

Betreft je "stroomuitval" probleem o.i.d, je zou een andere cron kunnen laten controleren hoelang de andere cron al loopt, als dat bijv langer is dan één uur, dat jij dan gemailt had...

Maar misschien is er nog een nettere oplossing, alleen ik heb even geen idee..
Pagina: 1